diff --git a/lib/main.dart b/lib/main.dart index 382f789..cbf8a21 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,7 @@ final darkTheme = ThemeData( void main() { runApp(GetMaterialApp( - initialRoute: '/testing', + initialRoute: '/auth', title: 'HUACU', darkTheme: darkTheme, themeMode: ThemeMode.dark, diff --git a/lib/models/game.dart b/lib/models/game.dart index 386a5ba..49a2ddd 100644 --- a/lib/models/game.dart +++ b/lib/models/game.dart @@ -1,8 +1,9 @@ class Game { final String id; - final String player1; - final String player2; + final String guesser; + final String suggester; + final int tries; final Set colors; - Game(this.id, this.player1, this.player2, this.colors); + Game(this.id, this.guesser, this.suggester, this.tries, this.colors); } diff --git a/lib/ui/pages/game_page.dart b/lib/ui/pages/game_page.dart index f4f4d1a..b083893 100644 --- a/lib/ui/pages/game_page.dart +++ b/lib/ui/pages/game_page.dart @@ -184,7 +184,8 @@ class _GamePageState extends State { borderRadius: BorderRadius.only(bottomRight: Radius.circular(25)), ), - child: Obx(() => Text("${10 - guessedColors.length}/10")), + child: Obx( + () => Text("${guessedColors.length}/${gameInfo.tries}")), ), ), ], diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart index 9fce0b5..6d15251 100644 --- a/lib/ui/pages/home_page.dart +++ b/lib/ui/pages/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:huacu_mobile/models/auth_data.dart'; +import 'package:huacu_mobile/models/available_game.dart'; import 'package:huacu_mobile/models/game.dart'; import 'package:huacu_mobile/ui/widgets/socket_connection_indicator.dart'; import 'package:socket_io_client/socket_io_client.dart' as io; @@ -16,9 +17,7 @@ class _HomePageState extends State { final io.Socket socket = Get.find(); final AuthData authData = Get.find(); - var guessersPlayers = [].obs; - var suggestersPlayers = [].obs; - var incommingRequests = [].obs; + final availableGames = [].obs; @override void initState() { @@ -33,11 +32,13 @@ class _HomePageState extends State { bool ok = update[0]; if (ok) { var data = update[1]; - guessersPlayers.value = (data["guessers"] as List? ?? []) - .map((e) => e.toString()) - .toList(growable: false); - suggestersPlayers.value = (data["suggesters"] as List? ?? []) - .map((e) => e.toString()) + availableGames.value = (data["availableGames"] as List? ?? []) + .map((e) => AvailableGame( + id: e["id"], + opponentName: e["player"], + tries: e["tries"], + neededRole: e["neededRole"], + )) .toList(growable: false); } else { Get.snackbar("Error", "Update failed with message: ${update[1]}"); @@ -48,26 +49,23 @@ class _HomePageState extends State { socket.emit("getUpdate"); }); - socket.on("gameRequest", (requestFrom) { - setState(() { - incommingRequests.add(requestFrom); - }); - }); + socket.on("removeGameResponse", (data) => null); + socket.on("createGameResponse", (data) => null); - socket.on("requestResponseResult", (data) { + socket.on("joinGameResponse", (data) { bool ok = data[0]; if (ok) { socket.off("hello"); socket.off("update"); socket.off("updateNeeded"); - socket.off("gameRequest"); - socket.off("requestResponseResult"); + socket.off("someoneJoinedGame"); Get.put(authData); Get.put(socket); Get.put(Game( data[1]["id"], - data[1]["player1"], - data[1]["player2"], + data[1]["guesser"], + data[1]["suggester"], + data[1]["tries"], (data[2] as List).map((e) => e.toString()).toSet(), )); Get.offNamed("/game"); @@ -81,178 +79,184 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { - final shortestSide = MediaQuery.of(context).size.shortestSide; - final useMobileLayout = shortestSide < 600; - final elements = [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(16), - child: _buildPlayerList( - guessersPlayers, - "Guessers", - "guessers", - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(16), - child: _buildPlayerList( - suggestersPlayers, - "Suggesters", - "suggesters", - ), - ), - ), - ]; + final size = MediaQuery.of(context).size; return Scaffold( appBar: AppBar( - title: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("HUACU"), - SocketConnectionIndicator(socket: socket, size: 8), - ], - ), + title: Obx(() => Text("Available ${availableGames.length} game(s)")), ), - body: useMobileLayout - ? Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: elements, - ) - : Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: elements, - ), - ); - } - - Widget _buildPlayerList(RxList players, String title, String team) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title), - Obx(() => Text("Count: ${players.length}")), - Expanded( - child: ObxValue( - (data) => ListView.builder( - itemCount: data.length, - itemBuilder: (context, index) { - final username = data[index]; - final hasRequest = incommingRequests.contains(username); - final you = authData.login == username; - return Card( - child: ListTile( - title: Text( - "$username ${you ? "(you)" : ""}".trim(), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (hasRequest) - InkResponse( - onTap: () => - _handleRequestResponseAction(username, true), - child: const Icon(Icons.check), - ), - if (hasRequest) - InkResponse( - onTap: () => - _handleRequestResponseAction(username, false), - child: const Icon(Icons.close), - ), - if (!you && !hasRequest) - InkResponse( - onTap: () => _handleSendRequestClick(username), - child: const Icon(Icons.connect_without_contact), - ), - ], - ), - ), - ); - }, - ), - players, - ), - ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, + body: SizedBox( + width: size.width, + height: size.height, + child: Column( children: [ Expanded( child: ObxValue( - (data) => players.contains(authData.login) - ? ElevatedButton( - onPressed: () => _handleLeaveClick(team), - child: const Text("Leave"), - ) - : ElevatedButton( - onPressed: () => _handleJoinClick(team), - child: const Text("Join"), + (data) => ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + final game = data[index]; + final you = authData.login == game.opponentName; + return Card( + child: ListTile( + title: Text( + "Game of ${game.opponentName} ${you ? "(you)" : ""}" + .trim(), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Tries: ${game.tries}"), + Text("Needed role: ${game.neededRole}"), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (!you) + InkResponse( + onTap: () => _joinGame(game.id), + child: + const Icon(Icons.connect_without_contact), + ), + if (you) + InkResponse( + onTap: () => _deleteGame(game.id), + child: const Icon(Icons.delete), + ), + ], + ), ), - guessersPlayers, + ); + }, + ), + availableGames, ), ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: ObxValue( + (data) { + return ElevatedButton( + onPressed: data.firstWhereOrNull( + (g) => g.opponentName == authData.login) == + null + ? _createGame + : null, + child: const Text("Create"), + ); + }, + availableGames, + ), + ), + ], + ), ], ), - ], + ), ); } - void _joinResponseHandler(dynamic data) { - bool ok = data[0]; - if (!ok) { - int status = data[1]; - Get.snackbar("Error", "Join failed with status $status"); - } - socket.off("joinResponse", _joinResponseHandler); + void _createGame() async { + var tries = 20; + var role = "guesser"; + final data = await Get.dialog?>( + StatefulBuilder(builder: (buildContext, setState) { + return AlertDialog( + title: const Text("New game configuration"), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Tries: $tries"), + Slider( + value: tries.toDouble(), + label: tries.toString(), + onChanged: (value) { + setState(() { + tries = value.round(); + }); + }, + min: 10, + max: 50, + divisions: 40, + ), + const Text("Needed role:"), + Row( + children: [ + Expanded( + child: DropdownButton( + value: role, + items: const [ + DropdownMenuItem( + value: "guesser", + child: Text("Guesser"), + ), + DropdownMenuItem( + value: "suggester", + child: Text("Suggester"), + ), + ], + onChanged: (value) { + setState(() { + role = value ?? "guesser"; + }); + }, + ), + ), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: const Text("Cancel"), + ), + TextButton( + onPressed: () { + Get.back(result: { + "tries": tries, + "role": role, + }); + }, + child: const Text("Create"), + ), + ], + ); + })); + if (data == null) return; + + tries = data["tries"]; + role = data["role"]; + + socket.emit("createGame", [tries, role]); + + Get.snackbar("Game created", "Game created"); } - void _handleJoinClick(String side) { - socket.on("joinResponse", _joinResponseHandler); - socket.emit("join", side); + void _joinGame(String gameId) { + socket.emit("joinGame", gameId); } - void _leaveResponseHandler(dynamic data) { - bool ok = data[0]; - if (!ok) { - int status = data[1]; - Get.snackbar("Error", "Leaving failed with status $status"); - } - socket.off("leaveResponse", _leaveResponseHandler); - } - - void _handleLeaveClick(String side) { - socket.on("leaveResponse", _leaveResponseHandler); - socket.emit("leave", side); - } - - void _sendRequestResponseHandler(dynamic data) { - bool ok = data[0]; - if (ok) { - Get.snackbar("Success", "Request sent"); - } else { - Get.snackbar("Error", "Request failed"); - } - socket.off("sendRequestResponse", _leaveResponseHandler); - } - - void _handleSendRequestClick(String player) { - socket.on("sendRequestResponse", _sendRequestResponseHandler); - socket.emit("sendRequest", player); - } - - void _handleRequestResponseAction(String data, bool response) { - socket.emit("requestResponse", [data, response]); - setState(() { - incommingRequests.remove(data); - }); + void _deleteGame(String gameId) { + Get.defaultDialog( + title: "Delete game", + content: const Text("Are you sure you want to delete this game?"), + textConfirm: "Delete", + onConfirm: () { + socket.emit("removeGame", gameId); + Get.back(); + }, + textCancel: "Cancel", + onCancel: () { + Get.back(); + }, + ); } } diff --git a/lib/ui/pages/testing_grounds_page.dart b/lib/ui/pages/testing_grounds_page.dart index 09a4473..89e1930 100644 --- a/lib/ui/pages/testing_grounds_page.dart +++ b/lib/ui/pages/testing_grounds_page.dart @@ -14,18 +14,39 @@ class _TestingGroundsPageState extends State { final availableGames = [].obs; final AuthData authData = const AuthData("nuark", "123"); //Get.find(); + @override + void initState() { + super.initState(); + + availableGames.addAll([ + AvailableGame( + id: "1234", + opponentName: "nuark", + tries: 20, + neededRole: "guesser", + ), + AvailableGame( + id: "1235", + opponentName: "not_nuark", + tries: 20, + neededRole: "guesser", + ), + ]); + } + @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return Scaffold( + appBar: AppBar( + title: Obx(() => Text("Available ${availableGames.length} games")), + ), body: SizedBox( width: size.width, height: size.height, child: Column( children: [ - const Text("Available games"), - Obx(() => Text("Count: ${availableGames.length}")), Expanded( child: ObxValue( (data) => ListView.builder( @@ -49,10 +70,17 @@ class _TestingGroundsPageState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - InkResponse( - onTap: () {}, - child: const Icon(Icons.connect_without_contact), - ), + if (!you) + InkResponse( + onTap: () => _joinGame(game.id), + child: + const Icon(Icons.connect_without_contact), + ), + if (you) + InkResponse( + onTap: () => _deleteGame(game.id), + child: const Icon(Icons.delete), + ), ], ), ), @@ -90,42 +118,53 @@ class _TestingGroundsPageState extends State { } void _createGame() async { + var tries = 20; + var role = "guesser"; final data = await Get.dialog?>( - Builder(builder: (buildContext) { - var tries = 20; - var role = "guesser"; - + StatefulBuilder(builder: (buildContext, setState) { return AlertDialog( title: const Text("New game configuration"), content: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text("Tries:"), + Text("Tries: $tries"), Slider( - value: 20, + value: tries.toDouble(), + label: tries.toString(), onChanged: (value) { - tries = value.toInt(); + setState(() { + tries = value.round(); + }); }, min: 10, max: 50, divisions: 40, ), const Text("Needed role:"), - DropdownButton( - value: "guesser", - items: const [ - DropdownMenuItem( - value: "guesser", - child: Text("Guesser"), - ), - DropdownMenuItem( - value: "suggester", - child: Text("Suggester"), + Row( + children: [ + Expanded( + child: DropdownButton( + value: role, + items: const [ + DropdownMenuItem( + value: "guesser", + child: Text("Guesser"), + ), + DropdownMenuItem( + value: "suggester", + child: Text("Suggester"), + ), + ], + onChanged: (value) { + setState(() { + role = value ?? "guesser"; + }); + }, + ), ), ], - onChanged: (value) { - role = value ?? "guesser"; - }, ), ], ), @@ -150,6 +189,27 @@ class _TestingGroundsPageState extends State { })); if (data == null) return; + tries = data["tries"]; + role = data["role"]; + Get.snackbar("Game created", "Game created"); } + + void _joinGame(String gameId) {} + + void _deleteGame(String gameId) { + Get.defaultDialog( + title: "Delete game", + content: const Text("Are you sure you want to delete this game?"), + textConfirm: "Delete", + onConfirm: () { + availableGames.removeWhere((g) => g.id == gameId); + Get.back(); + }, + textCancel: "Cancel", + onCancel: () { + Get.back(); + }, + ); + } } diff --git a/lib/ui/widgets/color_plate.dart b/lib/ui/widgets/color_plate.dart index c1fe311..db8171d 100644 --- a/lib/ui/widgets/color_plate.dart +++ b/lib/ui/widgets/color_plate.dart @@ -64,7 +64,7 @@ class _ColorPlateState extends State { PopupMenuItem( enabled: oldState != PlateState.marked, value: "mark", - child: Text("Mark"), + child: const Text("Mark"), ), const PopupMenuItem( value: "cancel",