..
This commit is contained in:
+299
-126
@@ -1,10 +1,14 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:bonsoir/bonsoir.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:playwith_core/playwith_core.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:wifi_iot/wifi_iot.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class LobbyScreen extends StatefulWidget {
|
||||
const LobbyScreen({super.key});
|
||||
@@ -28,7 +32,6 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
_logs.add(log);
|
||||
if (_logs.length > 100) _logs.removeAt(0);
|
||||
});
|
||||
|
||||
if (SettingsNotifier().isShowDebugLog) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_scrollController.hasClients) {
|
||||
@@ -45,40 +48,89 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
_net.messageStream.listen((data) {
|
||||
if (data['type'] == 'GAME_START') {
|
||||
final String gameId = data['gameId'];
|
||||
|
||||
if (gameId == 'quiz_ox' || gameId == 'quiz_mix') {
|
||||
_startGameAndNavigate(QuizGame());
|
||||
}
|
||||
else if (gameId == 'sudoku_battle') {
|
||||
_startGameAndNavigate(SudokuMultiGame());
|
||||
}
|
||||
else if (gameId == 'spider_battle') {
|
||||
_startGameAndNavigate(SpiderMultiGame());
|
||||
}
|
||||
// [추가] 오목 & 장기 연결
|
||||
else if (gameId == 'omok') {
|
||||
_startGameAndNavigate(OmokGame());
|
||||
}
|
||||
else if (gameId == 'janggi') {
|
||||
_startGameAndNavigate(JanggiGame());
|
||||
}
|
||||
else if (gameId == 'yutnori') {
|
||||
_startGameAndNavigate(YutnoriGame());
|
||||
}
|
||||
else if (gameId == 'memory_battle') {
|
||||
_startGameAndNavigate(MemoryGame());
|
||||
}
|
||||
// [추가] 밸런스 & 터치 배틀 연결
|
||||
else if (gameId == 'balance_game') {
|
||||
_startGameAndNavigate(BalanceGame());
|
||||
}
|
||||
else if (gameId == 'tap_battle') {
|
||||
_startGameAndNavigate(TapBattleGame());
|
||||
}
|
||||
_routeToGame(gameId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Future<int?> _showSpiderDifficultyDialog() {
|
||||
return showDialog<int>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("스파이더 난이도"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("초급 (1가지 무늬)"),
|
||||
subtitle: const Text("스페이드만 사용"),
|
||||
leading: const Icon(Icons.looks_one, color: Colors.green),
|
||||
onTap: () => Navigator.pop(context, 1),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("중급 (2가지 무늬)"),
|
||||
subtitle: const Text("스페이드 + 하트"),
|
||||
leading: const Icon(Icons.looks_two, color: Colors.orange),
|
||||
onTap: () => Navigator.pop(context, 2),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("고급 (4가지 무늬)"),
|
||||
subtitle: const Text("모든 무늬 사용"),
|
||||
leading: const Icon(Icons.looks_4, color: Colors.red),
|
||||
onTap: () => Navigator.pop(context, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [TextButton(onPressed: () => Navigator.pop(context, null), child: const Text("취소"))],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _routeToGame(String gameId) {
|
||||
if (gameId == 'quiz_ox' || gameId == 'quiz_mix') {
|
||||
_startGameAndNavigate(QuizGame());
|
||||
} else if (gameId == 'sudoku_battle') {
|
||||
_startGameAndNavigate(SudokuMultiGame());
|
||||
} else if (gameId == 'spider_battle') {
|
||||
_startGameAndNavigate(SpiderMultiGame());
|
||||
} else if (gameId == 'omok') {
|
||||
_startGameAndNavigate(OmokGame());
|
||||
} else if (gameId == 'janggi') {
|
||||
_startGameAndNavigate(JanggiGame());
|
||||
} else if (gameId == 'yutnori') {
|
||||
_startGameAndNavigate(YutnoriGame());
|
||||
} else if (gameId == 'memory_battle') {
|
||||
_startGameAndNavigate(MemoryGame());
|
||||
} else if (gameId == 'balance_game') {
|
||||
_startGameAndNavigate(BalanceGame());
|
||||
} else if (gameId == 'tap_battle') {
|
||||
_startGameAndNavigate(TapBattleGame());
|
||||
} else if (gameId == 'world_tour') {
|
||||
_startGameAndNavigate(WorldTourGame());
|
||||
} else if (gameId == 'othello') {
|
||||
_startGameAndNavigate(OthelloGame());
|
||||
} else if (gameId == 'arkanoid') {
|
||||
_startGameAndNavigate(ArkanoidGame());
|
||||
}else if (gameId == 'math_run') {
|
||||
_startGameAndNavigate(MathRunGame());
|
||||
}
|
||||
else if (gameId == 'jump_battle') {
|
||||
_startGameAndNavigate(JumpGame());
|
||||
}
|
||||
// [추가] 아이엠그라운드 연결
|
||||
else if (gameId == 'iam_ground') {
|
||||
_startGameAndNavigate(IAmGroundGame());
|
||||
}
|
||||
else if (gameId == 'survivor') {
|
||||
_startGameAndNavigate(SurvivorGame());
|
||||
}
|
||||
else if (gameId == 'sequence_memory') {
|
||||
_startGameAndNavigate(SequenceMemoryGame());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startGameAndNavigate(BaseGame game) async {
|
||||
if (!mounted) return;
|
||||
game.onStart();
|
||||
@@ -96,7 +148,6 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
children: [
|
||||
gameView,
|
||||
const SafeArea(
|
||||
// 배너 광고 높이만큼 띄워서 채팅창 표시
|
||||
child: GameChatOverlay(bottomOffset: 60.0),
|
||||
),
|
||||
],
|
||||
@@ -104,8 +155,6 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
}),
|
||||
);
|
||||
|
||||
// [수정] 게임 종료 후 복귀 시 로직
|
||||
// 솔로 모드였다면 네트워크를 종료하고 초기 화면으로 돌아감
|
||||
if (_net.hostIp == "Solo Mode") {
|
||||
_net.stopNetwork();
|
||||
}
|
||||
@@ -117,16 +166,21 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GameSelectionScreen(
|
||||
onGameSelected: (gameId) async {
|
||||
// 스도쿠 선택 시 난이도 팝업
|
||||
Map<String, dynamic> config = {};
|
||||
if (gameId == 'sudoku_battle') {
|
||||
final difficulty = await _showDifficultyDialog();
|
||||
if (difficulty == null) return; // 취소함
|
||||
if (difficulty == null) return;
|
||||
config['difficulty'] = difficulty;
|
||||
}
|
||||
// [추가] 스파이더 난이도
|
||||
else if (gameId == 'spider_battle') {
|
||||
final suits = await _showSpiderDifficultyDialog();
|
||||
if (suits == null) return;
|
||||
config['difficulty'] = suits; // numSuits (1, 2, 4)
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context); // 선택 화면 닫기
|
||||
Navigator.pop(context);
|
||||
|
||||
if (isSolo) {
|
||||
_net.startSoloMode(gameId, config: config);
|
||||
@@ -144,7 +198,6 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// [추가] 난이도 선택 다이얼로그
|
||||
Future<int?> _showDifficultyDialog() {
|
||||
return showDialog<int>(
|
||||
context: context,
|
||||
@@ -154,39 +207,44 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("쉬움"),
|
||||
leading: const Icon(Icons.filter_1, color: Colors.green),
|
||||
onTap: () => Navigator.pop(context, 4),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("보통"),
|
||||
leading: const Icon(Icons.filter_2, color: Colors.blue),
|
||||
onTap: () => Navigator.pop(context, 5), // 4~5 레벨이 보통 9x9
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("약간 어려움"),
|
||||
leading: const Icon(Icons.filter_3, color: Colors.red),
|
||||
onTap: () => Navigator.pop(context, 6), // 7 레벨이 어려움
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("약간 어려움"),
|
||||
leading: const Icon(Icons.filter_4, color: Colors.red),
|
||||
onTap: () => Navigator.pop(context, 7), // 7 레벨이 어려움
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("개 어려움"),
|
||||
leading: const Icon(Icons.filter_5, color: Colors.red),
|
||||
onTap: () => Navigator.pop(context, 8), // 7 레벨이 어려움
|
||||
),
|
||||
ListTile(title: const Text("쉬움 (4x4)"), onTap: () => Navigator.pop(context, 1)),
|
||||
ListTile(title: const Text("보통 (9x9)"), onTap: () => Navigator.pop(context, 4)),
|
||||
ListTile(title: const Text("어려움 (9x9)"), onTap: () => Navigator.pop(context, 7)),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, null),
|
||||
child: const Text("취소"),
|
||||
)
|
||||
],
|
||||
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text("취소"))],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openGameSelector() {
|
||||
if (_net.role != NetworkRole.host) return;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GameSelectionScreen(
|
||||
onGameSelected: (gameId) async {
|
||||
Map<String, dynamic> config = {};
|
||||
if (gameId == 'sudoku_battle') {
|
||||
final difficulty = await _showDifficultyDialog();
|
||||
if (difficulty == null) return;
|
||||
config['difficulty'] = difficulty;
|
||||
}
|
||||
else if (gameId == 'spider_battle') {
|
||||
final suits = await _showSpiderDifficultyDialog();
|
||||
if (suits == null) return;
|
||||
config['difficulty'] = suits;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
|
||||
if (_net.role == NetworkRole.host) {
|
||||
_net.selectGame(gameId, config: config);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -228,9 +286,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
: _buildLobbyView()
|
||||
),
|
||||
const Divider(thickness: 1, height: 1),
|
||||
|
||||
if (SettingsNotifier().isShowDebugLog)
|
||||
_buildDebugConsole(),
|
||||
if (SettingsNotifier().isShowDebugLog) _buildDebugConsole(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -297,23 +353,33 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
|
||||
Widget _buildLobbyView() {
|
||||
final currentGame = AppGames.getById(_net.selectedGameId);
|
||||
final bool isHost = _net.role == NetworkRole.host;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
|
||||
color: Colors.indigo.withOpacity(0.1),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(currentGame.icon, size: 20, color: Colors.indigo),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"선택된 게임: ${currentGame.name}",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.indigo),
|
||||
),
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: isHost ? _openGameSelector : null,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
|
||||
color: Colors.indigo.withOpacity(0.1),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(currentGame.icon, size: 24, color: Colors.indigo),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"현재 게임: ${currentGame.name}",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.indigo),
|
||||
),
|
||||
if (isHost)
|
||||
const Text("(눌러서 변경)", style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -326,15 +392,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(_net.role == NetworkRole.host ? Icons.wifi_tethering : Icons.wifi, color: Colors.blue),
|
||||
Icon(isHost ? Icons.wifi_tethering : Icons.wifi, color: Colors.blue),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
_net.role == NetworkRole.host ? "👑 방장 (나)" : "참가자 (나)",
|
||||
isHost ? "👑 방장 (나)" : "참가자 (나)",
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_net.role == NetworkRole.host) ...[
|
||||
if (isHost) ...[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -350,8 +416,11 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
const Text("QR 코드를 눌러 친구를 초대하세요!", style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.wifi_password),
|
||||
label: const Text("핫스팟(야외용) QR 만들기"),
|
||||
onPressed: () => _showHotspotCreateDialog(),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 10),
|
||||
Text("방장 IP: ${_net.hostIp ?? '...'}", style: const TextStyle(color: Colors.grey)),
|
||||
@@ -369,7 +438,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
_buildUserTile(_net.me, isMe: true),
|
||||
..._net.guestList.map((guest) => _buildUserTile(guest, isMe: false)),
|
||||
|
||||
if (_net.guestList.isEmpty && _net.role == NetworkRole.host)
|
||||
if (_net.guestList.isEmpty && isHost)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(40.0),
|
||||
child: Center(child: Text("참가자를 기다리는 중...\nQR 코드를 보여주세요.", textAlign: TextAlign.center, style: TextStyle(color: Colors.grey))),
|
||||
@@ -412,6 +481,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
Widget _buildReadyButton() {
|
||||
bool isReady = _net.me.isReady;
|
||||
bool canReady = _net.role == NetworkRole.host ? _net.guestList.isNotEmpty : true;
|
||||
if (_net.hostIp == "Solo Mode") canReady = true;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
@@ -466,10 +536,52 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showHotspotCreateDialog() {
|
||||
final ssidCtrl = TextEditingController();
|
||||
final pwCtrl = TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("핫스팟 QR 만들기"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("스마트폰 설정에서 핫스팟을 켜고,\n그 정보를 입력해주세요.", style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
const SizedBox(height: 10),
|
||||
TextField(controller: ssidCtrl, decoration: const InputDecoration(labelText: "핫스팟 이름 (SSID)")),
|
||||
TextField(controller: pwCtrl, decoration: const InputDecoration(labelText: "비밀번호")),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("취소")),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (ssidCtrl.text.isEmpty) return;
|
||||
Navigator.pop(ctx);
|
||||
|
||||
final Map<String, dynamic> qrData = {
|
||||
'type': 'hotspot_invite',
|
||||
'ssid': ssidCtrl.text,
|
||||
'pwd': pwCtrl.text,
|
||||
'port': _net.hostPort ?? 0,
|
||||
};
|
||||
_showGeneratedQR(jsonEncode(qrData), "핫스팟 + 게임 접속 QR");
|
||||
},
|
||||
child: const Text("생성"),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showHostQRDialog() {
|
||||
if (_net.hostIp == null || _net.hostPort == null) return;
|
||||
final qrData = jsonEncode({'ip': _net.hostIp, 'port': _net.hostPort});
|
||||
|
||||
_showGeneratedQR(qrData, "초대 QR 코드 (같은 와이파이)");
|
||||
}
|
||||
|
||||
void _showGeneratedQR(String data, String title) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
@@ -479,16 +591,14 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("초대 QR 코드", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
|
||||
Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(10)),
|
||||
child: QrImageView(data: qrData, version: QrVersions.auto, size: 220.0),
|
||||
child: QrImageView(data: data, version: QrVersions.auto, size: 220.0),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SelectableText("IP: ${_net.hostIp}\nPort: ${_net.hostPort}", textAlign: TextAlign.center, style: const TextStyle(color: Colors.grey)),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(onPressed: () => Navigator.pop(context), child: const Text("닫기"))
|
||||
],
|
||||
),
|
||||
@@ -497,34 +607,6 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _openQRScanner() {
|
||||
bool isScanCompleted = false;
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(title: const Text("QR 스캔")),
|
||||
body: MobileScanner(
|
||||
onDetect: (capture) {
|
||||
if (isScanCompleted) return;
|
||||
for (final barcode in capture.barcodes) {
|
||||
if (barcode.rawValue != null) {
|
||||
try {
|
||||
final data = jsonDecode(barcode.rawValue!);
|
||||
if (data['ip'] != null && data['port'] != null) {
|
||||
isScanCompleted = true;
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("접속 중...")));
|
||||
_net.joinRoom(data['ip'], data['port']);
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
void _showRoomListDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -565,6 +647,97 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _openQRScanner() {
|
||||
bool isScanCompleted = false;
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(title: const Text("QR 스캔")),
|
||||
body: MobileScanner(
|
||||
onDetect: (capture) async {
|
||||
if (isScanCompleted) return;
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
|
||||
for (final barcode in barcodes) {
|
||||
final String? rawValue = barcode.rawValue;
|
||||
if (rawValue == null) continue;
|
||||
|
||||
try {
|
||||
final data = jsonDecode(rawValue);
|
||||
|
||||
// 핫스팟 자동 접속
|
||||
if (data['type'] == 'hotspot_invite') {
|
||||
isScanCompleted = true;
|
||||
await _connectToHotspotAndJoin(
|
||||
data['ssid'],
|
||||
data['pwd'],
|
||||
data['port']
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 일반 게임 접속
|
||||
if (data['ip'] != null && data['port'] != null) {
|
||||
isScanCompleted = true;
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("접속 중...")));
|
||||
_net.joinRoom(data['ip'], data['port']);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON 아님
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _connectToHotspotAndJoin(String ssid, String pwd, int port) async {
|
||||
if (Platform.isAndroid) {
|
||||
var status = await Permission.location.request();
|
||||
if (!status.isGranted) return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("핫스팟 '$ssid' 연결 시도 중...")),
|
||||
);
|
||||
|
||||
try {
|
||||
bool connected = await WiFiForIoTPlugin.connect(
|
||||
ssid,
|
||||
password: pwd,
|
||||
security: NetworkSecurity.WPA,
|
||||
joinOnce: true,
|
||||
withInternet: false,
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("와이파이 연결 성공! 방장을 찾는 중...")),
|
||||
);
|
||||
|
||||
final info = NetworkInfo();
|
||||
String? gatewayIp = await info.getWifiGatewayIP();
|
||||
|
||||
if (gatewayIp != null) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
_net.joinRoom(gatewayIp, port);
|
||||
} else {
|
||||
throw Exception("방장 IP를 찾을 수 없습니다.");
|
||||
}
|
||||
} else {
|
||||
throw Exception("와이파이 연결 실패.");
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("오류: $e")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showManualJoinDialog() {
|
||||
final ipCtrl = TextEditingController(text: "192.168.");
|
||||
final portCtrl = TextEditingController();
|
||||
|
||||
Reference in New Issue
Block a user