diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index d4eba51..8514bd6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; -import 'package:tallee/core/enums.dart'; import 'package:tallee/data/dto/game.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart'; @@ -11,19 +10,24 @@ import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.da class ChooseGameView extends StatefulWidget { /// A view that allows the user to choose a game from a list of available games - /// - [games]: A list of tuples containing the game name, description and ruleset - /// - [initialGameIndex]: The index of the initially selected game + /// - [games]: The list of available games + /// - [initialSelectedGameId]: The id of the initially selected game + /// - [onGamesUpdated]: Optional callback invoked when the games are updated const ChooseGameView({ super.key, required this.games, - required this.initialGameIndex, + required this.initialSelectedGameId, + this.onGamesUpdated, }); /// A list of tuples containing the game name, description and ruleset - final List<(String, String, Ruleset)> games; + final List games; /// The index of the initially selected game - final int initialGameIndex; + final String initialSelectedGameId; + + /// Optional callback invoked when the games are updated + final VoidCallback? onGamesUpdated; @override State createState() => _ChooseGameViewState(); @@ -34,11 +38,11 @@ class _ChooseGameViewState extends State { final TextEditingController searchBarController = TextEditingController(); /// Currently selected game index - late int selectedGameIndex; + late String selectedGameId; @override void initState() { - selectedGameIndex = widget.initialGameIndex; + selectedGameId = widget.initialSelectedGameId; super.initState(); } @@ -52,21 +56,28 @@ class _ChooseGameViewState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { - Navigator.of(context).pop(selectedGameIndex); + Navigator.of(context).pop(selectedGameId); }, ), actions: [ IconButton( icon: const Icon(Icons.add), onPressed: () async { - await Navigator.push( + Game? newGame = await Navigator.push( context, adaptivePageRoute( builder: (context) => CreateGameView( - callback: () {}, //TODO: implement callback + onGameCreatedOrEdited: () { + widget.onGamesUpdated?.call(); + }, ), ), ); + if (newGame != null) { + setState(() { + widget.games.insert(0, newGame); + }); + } }, ), ], @@ -80,7 +91,7 @@ class _ChooseGameViewState extends State { if (didPop) { return; } - Navigator.of(context).pop(selectedGameIndex); + Navigator.of(context).pop(selectedGameId); }, child: Column( children: [ @@ -97,40 +108,39 @@ class _ChooseGameViewState extends State { itemCount: widget.games.length, itemBuilder: (BuildContext context, int index) { return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, + title: widget.games[index].name, + description: widget.games[index].description, badgeText: translateRulesetToString( - widget.games[index].$3, + widget.games[index].ruleset, context, ), - isHighlighted: selectedGameIndex == index, + isHighlighted: selectedGameId == widget.games[index].id, onTap: () async { setState(() { - if (selectedGameIndex == index) { - selectedGameIndex = -1; + if (selectedGameId == widget.games[index].id) { + selectedGameId = ''; } else { - selectedGameIndex = index; + selectedGameId = widget.games[index].id; } }); }, onLongPress: () async { - await Navigator.push( + Game? newGame = await Navigator.push( context, adaptivePageRoute( builder: (context) => CreateGameView( - //TODO: implement callback & giving real game to create game view - gameToEdit: Game( - name: 'Cabo', - description: - 'Test Beschreibung mit sehr viel Inhalt', - ruleset: Ruleset.highestScore, - color: GameColor.blue, - icon: '', - ), - callback: () {}, + gameToEdit: widget.games[index], + onGameCreatedOrEdited: () { + widget.onGamesUpdated?.call(); + }, ), ), ); + if (newGame != null) { + setState(() { + widget.games[index] = newGame; + }); + } }, ); }, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 1bf732c..c7a3ab1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -50,6 +50,9 @@ class _CreateMatchViewState extends State { /// Hint text for the match name input field String? hintText; + /// List of all groups from the database + List gamesList = []; + /// List of all groups from the database List groupsList = []; @@ -59,9 +62,8 @@ class _CreateMatchViewState extends State { /// The currently selected group Group? selectedGroup; - /// The index of the currently selected game in [games] to mark it in - /// the [ChooseGameView] - int selectedGameIndex = -1; + /// The currently selected game + Game? selectedGame; /// The currently selected players List selectedPlayers = []; @@ -79,11 +81,14 @@ class _CreateMatchViewState extends State { db = Provider.of(context, listen: false); Future.wait([ + db.gameDao.getAllGames(), db.groupDao.getAllGroups(), db.playerDao.getAllPlayers(), ]).then((result) async { - groupsList = result[0] as List; - playerList = result[1] as List; + gamesList = result[0] as List; + gamesList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + groupsList = result[1] as List; + playerList = result[2] as List; // If a match is provided, prefill the fields if (widget.matchToEdit != null) { @@ -105,11 +110,6 @@ class _CreateMatchViewState extends State { hintText ??= loc.match_name; } - List<(String, String, Ruleset)> games = [ - ('Example Game 1', 'This is a description', Ruleset.lowestScore), - ('Example Game 2', '', Ruleset.singleWinner), - ]; - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -140,21 +140,32 @@ class _CreateMatchViewState extends State { ), ChooseTile( title: loc.game, - trailingText: selectedGameIndex == -1 + trailingText: selectedGame == null ? loc.none - : games[selectedGameIndex].$1, + : selectedGame!.name, onPressed: () async { - selectedGameIndex = await Navigator.of(context).push( - adaptivePageRoute( - builder: (context) => ChooseGameView( - games: games, - initialGameIndex: selectedGameIndex, - ), - ), - ); + final String? selectedGameId = await Navigator.of(context) + .push( + adaptivePageRoute( + builder: (context) => ChooseGameView( + games: gamesList, + initialSelectedGameId: selectedGame?.id ?? '', + onGamesUpdated: loadGames, + ), + ), + ); + + try { + selectedGame = gamesList.firstWhere( + (g) => g.id == selectedGameId, + ); + } catch (_) { + selectedGame = null; + } + setState(() { - if (selectedGameIndex != -1) { - hintText = games[selectedGameIndex].$1; + if (selectedGame != null) { + hintText = selectedGame!.name; } else { hintText = loc.match_name; } @@ -232,7 +243,7 @@ class _CreateMatchViewState extends State { /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || - (selectedPlayers.length > 1) && selectedGameIndex != -1); + (selectedPlayers.length > 1) && selectedGame != null); } // If a match was provided to the view, it updates the match in the database @@ -266,9 +277,6 @@ class _CreateMatchViewState extends State { /// Updates attributes of the existing match in the database based on the /// changes made in the edit view. Future updateMatch() async { - //TODO: Remove when Games implemented - final tempGame = await getTemporaryGame(); - final updatedMatch = Match( id: widget.matchToEdit!.id, name: _matchNameController.text.isEmpty @@ -276,7 +284,7 @@ class _CreateMatchViewState extends State { : _matchNameController.text.trim(), group: selectedGroup, players: selectedPlayers, - game: tempGame, + game: selectedGame!, winner: widget.matchToEdit!.winner, createdAt: widget.matchToEdit!.createdAt, endedAt: widget.matchToEdit!.endedAt, @@ -297,6 +305,13 @@ class _CreateMatchViewState extends State { ); } + if (widget.matchToEdit!.game.id != updatedMatch.game.id) { + await db.matchDao.updateMatchGame( + matchId: widget.matchToEdit!.id, + gameId: updatedMatch.game.id, + ); + } + // Add players who are in updatedMatch but not in the original match for (var player in updatedMatch.players) { if (!widget.matchToEdit!.players.any((p) => p.id == player.id)) { @@ -326,8 +341,6 @@ class _CreateMatchViewState extends State { // Creates a new match and adds it to the database. // Returns the created match. Future createMatch() async { - final tempGame = await getTemporaryGame(); - Match match = Match( name: _matchNameController.text.isEmpty ? (hintText ?? '') @@ -335,34 +348,18 @@ class _CreateMatchViewState extends State { createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, - game: tempGame, + game: selectedGame!, ); await db.matchDao.addMatch(match: match); return match; } - // TODO: Remove when games fully implemented - Future getTemporaryGame() async { - Game? game; - - final selectedGame = games[selectedGameIndex]; - game = Game( - name: selectedGame.$1, - description: selectedGame.$2, - ruleset: selectedGame.$3, - color: GameColor.blue, - icon: '', - ); - - await db.gameDao.addGame(game: game); - return game; - } - // If a match was provided to the view, this method prefills the input fields void prefillMatchDetails() { final match = widget.matchToEdit!; _matchNameController.text = match.name; selectedPlayers = match.players; + selectedGame = match.game; if (match.group != null) { selectedGroup = match.group; @@ -383,4 +380,16 @@ class _CreateMatchViewState extends State { }); } } + + // Loads all games from the database and updates the gamesList. + Future loadGames() async { + print("LOADING GAMES TRIGGERED"); + final result = await db.gameDao.getAllGames(); + result.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + setState(() { + gamesList = result; + }); + print(gamesList.map((g) => g.name).join(', ')); + print("GAMES LOADED"); + } } diff --git a/lib/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart index 7c83748..cbe224c 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/dto/game.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/game_view/choose_color_view.dart'; @@ -13,17 +15,24 @@ import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; import 'package:tallee/presentation/widgets/tiles/choose_tile.dart'; class CreateGameView extends StatefulWidget { - const CreateGameView({super.key, this.gameToEdit, required this.callback}); + const CreateGameView({ + super.key, + this.gameToEdit, + required this.onGameCreatedOrEdited, + }); + /// An optional game to prefill the fields final Game? gameToEdit; - final VoidCallback callback; + /// Callback to invoke when the game is created or edited + final VoidCallback onGameCreatedOrEdited; @override State createState() => _CreateGameViewState(); } class _CreateGameViewState extends State { + late final AppDatabase db; Ruleset? selectedRuleset; int selectedRulesetIndex = -1; late List<(Ruleset, String)> _rulesets; @@ -36,6 +45,7 @@ class _CreateGameViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); _gameNameController.addListener(() => setState(() {})); } @@ -171,9 +181,23 @@ class _CreateGameViewState extends State { _gameNameController.text.trim().isNotEmpty && selectedRulesetIndex != -1 && selectedColor != null - ? () { - //TODO: Handle saving to db & updating game selection view - Navigator.of(context).pop(); + ? () async { + Game newGame = Game( + name: _gameNameController.text.trim(), + description: _descriptionController.text.trim(), + ruleset: selectedRuleset!, + color: selectedColor!, + icon: '', + ); + if (isEditing) { + await handleGameUpdate(newGame); + } else { + await handleGameCreation(newGame); + } + widget.onGameCreatedOrEdited.call(); + if (context.mounted) { + Navigator.of(context).pop(newGame); + } } : null, ), @@ -184,4 +208,47 @@ class _CreateGameViewState extends State { ), ); } + + Future handleGameUpdate(Game newGame) async { + final oldGame = widget.gameToEdit!; + + if (oldGame.name != newGame.name) { + await db.gameDao.updateGameName( + gameId: oldGame.id, + newName: newGame.name, + ); + } + + if (oldGame.description != newGame.description) { + await db.gameDao.updateGameDescription( + gameId: oldGame.id, + newDescription: newGame.description, + ); + } + + if (oldGame.ruleset != newGame.ruleset) { + await db.gameDao.updateGameRuleset( + gameId: oldGame.id, + newRuleset: newGame.ruleset, + ); + } + + if (oldGame.color != newGame.color) { + await db.gameDao.updateGameColor( + gameId: oldGame.id, + newColor: newGame.color, + ); + } + + if (oldGame.icon != newGame.icon) { + await db.gameDao.updateGameIcon( + gameId: oldGame.id, + newIcon: newGame.icon, + ); + } + } + + Future handleGameCreation(Game newGame) async { + await db.gameDao.addGame(game: newGame); + } }