import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:mchess/api/register.dart'; import 'package:mchess/connection/ws_connection.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/utils/config.dart' as config; class LobbySelector extends StatefulWidget { const LobbySelector({super.key}); @override State createState() => _LobbySelectorState(); } class _LobbySelectorState extends State { final buttonStyle = const ButtonStyle(); final phraseController = TextEditingController(); late Future joinGameFuture; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () => buildJoinOrHostDialog(context), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.lock), SizedBox( width: 10, ), Text('Private game') ], ), ), ], ), ), ); } Future buildJoinOrHostDialog(BuildContext context) { //TODO: find a better place to disconnect old websocket connection ServerConnection.getInstance().disconnectExistingConnection(); return showDialog( context: context, builder: (BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: AlertDialog( title: const Text('Host or join?'), actions: [ TextButton( child: const Text('Cancel'), onPressed: () => context.pop(), ), TextButton( child: const Text('Host'), onPressed: () { context.pop(); //close dialog before going to host context.goNamed('host'); }), TextButton( child: const Text('Join'), onPressed: () { context.pop(); //close dialog before going to next dialog buildEnterPassphraseDialog(context); }, ), ], ), ); }, ); } Future buildEnterPassphraseDialog(BuildContext context) { return showDialog( context: context, builder: (BuildContext context) { return ScaffoldMessenger( child: Builder(builder: (builderContext) { return Scaffold( backgroundColor: Colors.transparent, body: AlertDialog( title: const Text('Enter the passphrase here:'), content: TextField( controller: phraseController, onSubmitted: (val) { submitPassphrase(builderContext); }, decoration: InputDecoration( hintText: 'Enter passphrase here', suffixIcon: IconButton( onPressed: () { submitPassphrase(builderContext); }, icon: const Icon(Icons.check), )), ), actions: [ TextButton( child: const Text('Cancel'), onPressed: () { builderContext.pop(); }, ), ], ), ); }), ); }, ); } void submitPassphrase(BuildContext ctx) { joinGameFuture = joinPrivateGame(ctx); joinGameFuture.then((value) { if (value != null) { phraseController.clear(); ctx.pop(); switchToGame(value); } }); } void switchToGame(PlayerInfo info) { var chessGameArgs = ChessGameArguments( lobbyID: info.lobbyID!, playerID: info.playerID!, passphrase: info.passphrase); ConnectionCubit.getInstance().connect( info.playerID!.uuid, info.lobbyID!.uuid, info.passphrase, ); if (!chessGameArgs.isValid()) { context.goNamed('lobbySelector'); const snackBar = SnackBar( backgroundColor: Colors.amberAccent, content: Text("Game information is corrupted"), ); ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(snackBar); return; } context.goNamed('game', extra: chessGameArgs); } Future joinPrivateGame(BuildContext context) async { http.Response response; var existingInfo = await PlayerInfo.get(); log("lobbyID: ${existingInfo?.lobbyID} and playerID: ${existingInfo?.playerID} and passphrase: ${existingInfo?.passphrase}"); PlayerInfo info; if (existingInfo?.passphrase == phraseController.text) { // We have player info for this exact passphrase info = PlayerInfo( playerID: existingInfo?.playerID, lobbyID: existingInfo?.lobbyID, passphrase: phraseController.text); } else { info = PlayerInfo( playerID: null, lobbyID: null, passphrase: phraseController.text); } var decodedInfo = jsonEncode(info); log("decodedInfo: $decodedInfo"); try { response = await http.post(Uri.parse(config.getJoinURL()), body: decodedInfo, headers: {"Accept": "application/json"}); } catch (e) { log(e.toString()); if (!context.mounted) return null; const snackBar = SnackBar( backgroundColor: Colors.amberAccent, content: Text("mChess server is not responding. Try again or give up"), ); ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(snackBar); return null; } if (response.statusCode == HttpStatus.notFound) { const snackBar = SnackBar( backgroundColor: Colors.amberAccent, content: Text("Passphrase could not be found."), ); if (!context.mounted) return null; ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(snackBar); return null; } if (response.statusCode == HttpStatus.ok) { var info = PlayerInfo.fromJson(jsonDecode(response.body)); info.store(); log('Player info received from server: '); log('lobbyID: ${info.lobbyID}'); log('playerID: ${info.playerID}'); log('passphrase: ${info.passphrase}'); return info; } return null; } }