From 3f0adb4c05a5a5ca4bdc90a836125a2d8b501ffa Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:20:27 +0100 Subject: [PATCH 01/54] use GameTile to display game history --- .../views/main_menu/game_history_view.dart | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index de75ae6..b840d70 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/game_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -182,16 +182,21 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { } else if (suggestedGameData.isEmpty) { return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); } - return ListView.builder( + return ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), itemCount: suggestedGameData.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; - return doubleRowInfoTile( - currentGame['game'] + ": ", - currentGame['title'], - currentGame['players'].toString() + " Spieler", - currentGame['group'], - currentGame['date'], + return GameTile( + gameTitle: currentGame['title'], + gameType: currentGame['game'], + ruleset: currentGame['date'], + players: '${currentGame['players']} Spieler', + winner: currentGame['group'], ); }, ); From 69e13e877e2d08fedc5bd8ed8a4c2cdbed09cce6 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:58:47 +0100 Subject: [PATCH 02/54] add game_history_tile --- .../views/main_menu/game_history_view.dart | 13 ++--- .../widgets/game_history_tile.dart | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 lib/presentation/widgets/game_history_tile.dart diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b840d70..b14244b 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; +import 'package:game_tracker/presentation/widgets/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { @@ -178,10 +178,11 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return TopCenteredMessage("Keine Spiele erstellt"); + return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele erstellt', message: '',); } else if (suggestedGameData.isEmpty) { - return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); + return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele mit den Suchparametern gefunden', message: '',); } + return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), itemCount: suggestedGameData.length, @@ -191,12 +192,12 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { ), itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; - return GameTile( + return GameHistoryTile( gameTitle: currentGame['title'], gameType: currentGame['game'], ruleset: currentGame['date'], - players: '${currentGame['players']} Spieler', - winner: currentGame['group'], + groupName: currentGame['group'], + winner: "ich", ); }, ); diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/game_history_tile.dart new file mode 100644 index 0000000..e461f04 --- /dev/null +++ b/lib/presentation/widgets/game_history_tile.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class GameHistoryTile extends StatefulWidget { + final String gameTitle; + final String gameType; + final String ruleset; + final String groupName; + final String winner; + + const GameHistoryTile({ + super.key, + required this.gameTitle, + required this.gameType, + required this.ruleset, + required this.groupName, + required this.winner, + }); + + @override + State createState() => _GameHistoryTileState(); +} + +class _GameHistoryTileState extends State { + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.gameTitle, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(width: 5), + Text( + widget.gameType, + style: const TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + ], + ); + } + +} From 95f0861a7967e1194f8ede4d7cbfa36a7642860e Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:44:27 +0100 Subject: [PATCH 03/54] add basic came history tile --- .../views/main_menu/game_history_view.dart | 10 ++-- .../widgets/game_history_tile.dart | 56 ++++++++++++++----- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b14244b..a0ea4a9 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -134,16 +134,16 @@ class _GameHistoryViewState extends State { children: [ Column( children: [ - Container(margin: EdgeInsets.only(bottom: 75)), + Container(margin: const EdgeInsets.only(bottom: 75)), Expanded( child: gameHistoryListView(allGameData, suggestedGameData), ), ], ), Container( - margin: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), + margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), child: SearchBar( - leading: Icon(Icons.search), + leading: const Icon(Icons.search), onChanged: (value) { if (value.isEmpty) { setState(() { @@ -195,9 +195,9 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { return GameHistoryTile( gameTitle: currentGame['title'], gameType: currentGame['game'], - ruleset: currentGame['date'], + date: currentGame['date'], groupName: currentGame['group'], - winner: "ich", + winner: 'ich', ); }, ); diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/game_history_tile.dart index e461f04..c21b2c2 100644 --- a/lib/presentation/widgets/game_history_tile.dart +++ b/lib/presentation/widgets/game_history_tile.dart @@ -5,7 +5,7 @@ import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryTile extends StatefulWidget { final String gameTitle; final String gameType; - final String ruleset; + final String date; final String groupName; final String winner; @@ -13,7 +13,7 @@ class GameHistoryTile extends StatefulWidget { super.key, required this.gameTitle, required this.gameType, - required this.ruleset, + required this.date, required this.groupName, required this.winner, }); @@ -29,18 +29,46 @@ class _GameHistoryTileState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - widget.gameTitle, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(width: 5), - Text( - widget.gameType, - style: const TextStyle(fontSize: 14, color: Colors.grey), - ), - ], + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Row( + children: [ + Text( + widget.gameTitle, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + ), + ], + ), + Row( + children: [ + Text( + widget.date, + style: const TextStyle(fontSize: 14, color: Colors.grey), + textAlign: TextAlign.left, + ), + const SizedBox(width: 5), + const Text('·'), + const SizedBox(width: 5), + Text( + widget.gameType, + style: const TextStyle(fontSize: 14, color: Colors.grey), + textAlign: TextAlign.left, + ), + ], + ), + const SizedBox(height: 15), + + ] + ) ), ], ); From 46d1c25bb56d0ebcfdd72e6d0fe8c6bbe7529aef Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 19:41:57 +0100 Subject: [PATCH 04/54] create GameResultView with basic structure and styling --- .../views/main_menu/game_result_view.dart | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/presentation/views/main_menu/game_result_view.dart diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart new file mode 100644 index 0000000..d953b0f --- /dev/null +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; + +class GameResultView extends StatefulWidget { + const GameResultView({super.key}); + + //TODO: Handle given game + + @override + State createState() => _GameResultViewState(); +} + +class _GameResultViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Game Result', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: SafeArea( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Select Winner", + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + //TODO: Add FutureBuilder + //TODO: Implement ListView.builder with RadioListTiles and Players from Game + //TODO Implement Save button with snackbar to confirm save/show error + CustomWidthButton(text: "Save", sizeRelativeToWidth: 0.95), + ], + ), + ), + ), + ); + } +} From 937f1e3ac876469080e30286f7f361bb2391f060 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 19:42:31 +0100 Subject: [PATCH 05/54] made settingsbutton redirect to game result view --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 71a072e..18561c4 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -56,7 +56,9 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute(builder: (_) => const SettingsView()), + MaterialPageRoute( + builder: (_) => const GameResultView(), + ), //TODO Replace with Settingsview ); setState(() { tabKeyCount++; From f21d0ba4e8210bf4501707fe2db00a074a63bfb8 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:04:56 +0100 Subject: [PATCH 06/54] move game_history_tile.dart --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/widgets/{ => tiles}/game_history_tile.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/presentation/widgets/{ => tiles}/game_history_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index a0ea4a9..8cd7882 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart similarity index 100% rename from lib/presentation/widgets/game_history_tile.dart rename to lib/presentation/widgets/tiles/game_history_tile.dart From 6dc74ca82ec58043d7881131bfdfa82cd3ed48fb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:18:26 +0100 Subject: [PATCH 07/54] Implement basic logic and UI for selecting game winners in GameResultView --- .../views/main_menu/game_result_view.dart | 107 ++++++++++++++---- 1 file changed, 85 insertions(+), 22 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index d953b0f..e39a4f3 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; class GameResultView extends StatefulWidget { @@ -12,6 +15,30 @@ class GameResultView extends StatefulWidget { } class _GameResultViewState extends State { + late final List allPlayers; + final exampleGame = Game( + name: "Test Game", + players: [ + Player(name: "Petrus"), + Player(name: "Peter"), + Player(name: "Petra"), + ], + group: Group( + name: "Die Petris", + members: [ + Player(name: "Petralia"), + Player(name: "Petrenlia"), + Player(name: "Petrumlia"), + ], + ), + ); + + @override + void initState() { + allPlayers = getAllPlayers(exampleGame); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -26,32 +53,68 @@ class _GameResultViewState extends State { centerTitle: true, ), body: SafeArea( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: const Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Select Winner", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: Column( + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Select Winner:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Expanded( + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + //TODO: Implement Custom RadioListTile, see text_icon_list_tile + return RadioListTile( + title: Text(allPlayers[index].name), + value: allPlayers[index], + ); + }, + ), + ), + ], ), ), - //TODO: Add FutureBuilder - //TODO: Implement ListView.builder with RadioListTiles and Players from Game - //TODO Implement Save button with snackbar to confirm save/show error - CustomWidthButton(text: "Save", sizeRelativeToWidth: 0.95), - ], - ), + ), + CustomWidthButton( + text: "Save", + sizeRelativeToWidth: 0.95, + onPressed: null, + ), + SizedBox(height: 10), + ], ), ), ); } + + List getAllPlayers(Game game) { + if (game.group == null && game.players != null) { + return [...game.players!]; + } else if (game.group != null && game.players != null) { + return [...game.players!, ...game.group!.members]; + } + return [...game.group!.members]; + } } From 424a258df17dc87e9a115a3fa23a0b84e1210815 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:22:03 +0100 Subject: [PATCH 08/54] Update GameResultView with dummy Game data in CustomNavigationBar --- .../main_menu/custom_navigation_bar.dart | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 18561c4..b20f80e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; @@ -57,7 +60,24 @@ class _CustomNavigationBarState extends State await Navigator.push( context, MaterialPageRoute( - builder: (_) => const GameResultView(), + builder: (_) => GameResultView( + game: Game( + name: "Test Game", + players: [ + Player(name: "Petrus"), + Player(name: "Peter"), + Player(name: "Petra"), + ], + group: Group( + name: "Die Petris", + members: [ + Player(name: "Petralia"), + Player(name: "Petrenlia"), + Player(name: "Petrumlia"), + ], + ), + ), + ), ), //TODO Replace with Settingsview ); setState(() { From bd616c510a59d1cf278a8030f3f14239496a1a02 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:22:26 +0100 Subject: [PATCH 09/54] update GameResultView to accept and use a given Game instance --- .../views/main_menu/game_result_view.dart | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index e39a4f3..9278641 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; class GameResultView extends StatefulWidget { - const GameResultView({super.key}); + final Game game; - //TODO: Handle given game + const GameResultView({super.key, required this.game}); @override State createState() => _GameResultViewState(); @@ -16,26 +15,10 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; - final exampleGame = Game( - name: "Test Game", - players: [ - Player(name: "Petrus"), - Player(name: "Peter"), - Player(name: "Petra"), - ], - group: Group( - name: "Die Petris", - members: [ - Player(name: "Petralia"), - Player(name: "Petrenlia"), - Player(name: "Petrumlia"), - ], - ), - ); @override void initState() { - allPlayers = getAllPlayers(exampleGame); + allPlayers = getAllPlayers(widget.game); super.initState(); } From bbd200e24529923ecddb92a9fd721bb3f0ed9f36 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:34:16 +0100 Subject: [PATCH 10/54] use Skeletonizer for Layout --- .../views/main_menu/game_history_view.dart | 264 ++++++------------ 1 file changed, 83 insertions(+), 181 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 8cd7882..e3b2aeb 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -11,194 +15,92 @@ class GameHistoryView extends StatefulWidget { } class _GameHistoryViewState extends State { - final allGameData = [ - { - 'game': 'Schach', - 'title': 'Abendpartie', - 'players': 2, - 'group': 'Familie', - 'date': '01.06.2024', - }, - { - 'game': 'Monopoly', - 'title': 'Wochenendspaß mit Gras du Saas', - 'players': 4, - 'group': 'Freunde', - 'date': '28.05.2024', - }, - { - 'game': 'Catan', - 'title': 'Strategieabend', - 'players': 3, - 'group': 'Brettspieler', - 'date': '25.05.2024', - }, - { - 'game': 'Uno', - 'title': 'Schnelle Runde', - 'players': 5, - 'group': 'Kollegen', - 'date': '22.05.2024', - }, - { - 'game': 'Poker', - 'title': 'Freitagspoker', - 'players': 6, - 'group': 'Pokerclub', - 'date': '20.05.2024', - }, - { - 'game': 'Scrabble', - 'title': 'Wortschlacht', - 'players': 4, - 'group': 'Familie', - 'date': '18.05.2024', - }, - { - 'game': 'Risiko', - 'title': 'Weltherrschaft', - 'players': 5, - 'group': 'Strategiegruppe', - 'date': '15.05.2024', - }, - { - 'game': 'Zug um Zug', - 'title': 'Zug-Abenteuer', - 'players': 4, - 'group': 'Reisende', - 'date': '12.05.2024', - }, - { - 'game': 'Carcassonne', - 'title': 'Plättchenlegen', - 'players': 3, - 'group': 'Brettspieler', - 'date': '10.05.2024', - }, - { - 'game': 'Pandemie', - 'title': 'Welt retten', - 'players': 4, - 'group': 'Koop-Team', - 'date': '08.05.2024', - }, - { - 'game': 'Cluedo', - 'title': 'Krimiabend', - 'players': 6, - 'group': 'Detektive', - 'date': '05.05.2024', - }, - { - 'game': 'Dixit', - 'title': 'Fantasiespiel', - 'players': 5, - 'group': 'Künstler', - 'date': '02.05.2024', - }, - { - 'game': 'Azul', - 'title': 'Plättchenmeister', - 'players': 4, - 'group': 'Familie', - 'date': '30.04.2024', - }, - { - 'game': 'Splendor', - 'title': 'Edelsteinhändler', - 'players': 3, - 'group': 'Freunde', - 'date': '28.04.2024', - }, - { - 'game': '7 Wonders', - 'title': 'Antike Reiche', - 'players': 7, - 'group': 'Geschichtsfreunde', - 'date': '25.04.2024', - }, - ]; - late List> suggestedGameData; + late Future> _gameListFuture; + late final AppDatabase db; + + late final List skeletonData = List.filled( + 2, + Game( + name: 'Skeleton Game', + group: Group( + name: 'Skeleton Group', + members: [ + Player(name: 'Skeleton Player 1'), + Player(name: 'Skeleton Player 2'), + ], + ), + winner: Player(name: 'Skeleton Player 1'), + ), + ); @override void initState() { super.initState(); - suggestedGameData = List.from(allGameData); + db = Provider.of(context, listen: false); + _gameListFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.gameDao.getAllGames(), + ); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - body: Stack( - children: [ - Column( - children: [ - Container(margin: const EdgeInsets.only(bottom: 75)), - Expanded( - child: gameHistoryListView(allGameData, suggestedGameData), - ), - ], - ), - Container( - margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), - child: SearchBar( - leading: const Icon(Icons.search), - onChanged: (value) { - if (value.isEmpty) { - setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(allGameData); - }); - return; - } - final suggestions = allGameData.where((currentGame) { - return currentGame['game'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['title'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['group'].toString().toLowerCase().contains( - value.toLowerCase(), - ); - }); - setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(suggestions); - }); - }, + return FutureBuilder>( + future: _gameListFuture, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + heightFactor: 4, + child: Text( + 'Error while loading recent games.', ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + heightFactor: 4, + child: Text('No recent games available.'), + ); + } + + final bool isLoading = snapshot.connectionState == ConnectionState.waiting; + final List games = (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); + + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), ), - ], - ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, + ); + } + return GameHistoryTile(game: games[index]); + }, + ), + ); + }, ); } -} - -Widget gameHistoryListView(allGameData, suggestedGameData) { - if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele erstellt', message: '',); - } else if (suggestedGameData.isEmpty) { - return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele mit den Suchparametern gefunden', message: '',); - } - - return ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - itemCount: suggestedGameData.length, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - itemBuilder: (context, index) { - final currentGame = suggestedGameData[index]; - return GameHistoryTile( - gameTitle: currentGame['title'], - gameType: currentGame['game'], - date: currentGame['date'], - groupName: currentGame['group'], - winner: 'ich', - ); - }, - ); -} +} \ No newline at end of file From 290948e50d001427af6f6528edfacd39ad33c995 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:05:25 +0100 Subject: [PATCH 11/54] add intl for date formatting --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index fa4c213..07e4df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 + intl: ^0.18.0 dev_dependencies: flutter_test: From 4341c2509ea5cd41ca22a527c8ce84ac9f987aae Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:07:40 +0100 Subject: [PATCH 12/54] fix bug where only last 2 games were shown --- .../views/main_menu/game_history_view.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index e3b2aeb..45a17b5 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -19,7 +19,7 @@ class _GameHistoryViewState extends State { late final AppDatabase db; late final List skeletonData = List.filled( - 2, + 10, Game( name: 'Skeleton Game', group: Group( @@ -30,6 +30,9 @@ class _GameHistoryViewState extends State { ], ), winner: Player(name: 'Skeleton Player 1'), + players: [ + Player(name: 'Skeleton Player 3') + ], ), ); @@ -37,10 +40,11 @@ class _GameHistoryViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.gameDao.getAllGames(), - ); + _gameListFuture = db.gameDao.getAllGames(); + + Future.wait([_gameListFuture]).then((result) async { + await Future.delayed(const Duration(milliseconds: 250)); + }); } @override @@ -69,7 +73,6 @@ class _GameHistoryViewState extends State { ? skeletonData : (snapshot.data ?? []) ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) .toList(); return Skeletonizer( From 32c7d458090b13f29651bb3bec6336bc94756a34 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:34:22 +0100 Subject: [PATCH 13/54] made game_history_tile prettier :) --- .../widgets/tiles/game_history_tile.dart | 208 +++++++++++++----- 1 file changed, 159 insertions(+), 49 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index c21b2c2..fdc9584 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,21 +1,15 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { - final String gameTitle; - final String gameType; - final String date; - final String groupName; - final String winner; + final Game game; const GameHistoryTile({ super.key, - required this.gameTitle, - required this.gameType, - required this.date, - required this.groupName, - required this.winner, + required this.game, }); @override @@ -23,55 +17,171 @@ class GameHistoryTile extends StatefulWidget { } class _GameHistoryTileState extends State { + String _formatDate(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays == 0) { + return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays == 1) { + return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM d, yyyy').format(dateTime); + } + } + + List _getAllPlayers() { + final allPlayers = []; + final playerIds = {}; + + // Add players from game.players + if (widget.game.players != null) { + for (var player in widget.game.players!) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + // Add players from game.group.players + if (widget.game.group?.members != null) { + for (var player in widget.game.group!.members) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + return allPlayers; + } @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( + final group = widget.game.group; + final winner = widget.game.winner; + final allPlayers = _getAllPlayers(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Text( - widget.gameTitle, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, + Expanded( + child: Text( + widget.game.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + + const SizedBox(height: 8), + + if (group != null) + Row( + children: [ + const Icon( + Icons.group, + size: 16, + color: Colors.grey, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + group.name, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, ), - ], + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + + if (group != null) const SizedBox(height: 12), + + if (winner != null) + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), ), - Row( + child: Row( children: [ - Text( - widget.date, - style: const TextStyle(fontSize: 14, color: Colors.grey), - textAlign: TextAlign.left, + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, ), - const SizedBox(width: 5), - const Text('·'), - const SizedBox(width: 5), + const SizedBox(width: 8), Text( - widget.gameType, - style: const TextStyle(fontSize: 14, color: Colors.grey), - textAlign: TextAlign.left, + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), ), ], ), - const SizedBox(height: 15), - - ] - ) - ), - ], + ), + + if (winner != null) const SizedBox(height: 12), + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players:', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + final isWinner = winner != null && player.id == winner.id; + return TextIconTile( + text: player.name, + iconEnabled: false, + ); + }).toList(), + ), + ], + ], + ), ); } - -} +} \ No newline at end of file From 4591a6857d212abaf9f00361c47e2b79f6b2c9fb Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:39:56 +0100 Subject: [PATCH 14/54] change skeleton names --- lib/presentation/views/main_menu/game_history_view.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 45a17b5..60dcd90 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -25,13 +25,16 @@ class _GameHistoryViewState extends State { group: Group( name: 'Skeleton Group', members: [ - Player(name: 'Skeleton Player 1'), - Player(name: 'Skeleton Player 2'), + Player(name: 'Player 1'), + Player(name: 'Player 2'), + Player(name: 'Player 3'), + Player(name: 'Long Name Player 4'), + Player(name: 'Player 5'), ], ), winner: Player(name: 'Skeleton Player 1'), players: [ - Player(name: 'Skeleton Player 3') + Player(name: 'Skeleton Player 6') ], ), ); From 07d81d687bffd43034696fdca0f39f142785cbce Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:23:03 +0100 Subject: [PATCH 15/54] Implement CustomRadioListTile and update GameResultView to select a winner currently without saving to the db --- .../views/main_menu/game_result_view.dart | 50 +++++++++++++++---- .../widgets/tiles/custom_radio_list_tile.dart | 49 ++++++++++++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 lib/presentation/widgets/tiles/custom_radio_list_tile.dart diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 9278641..aa15c2a 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -3,6 +3,8 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameResultView extends StatefulWidget { final Game game; @@ -15,6 +17,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; + Player? _player; @override void initState() { @@ -64,16 +67,36 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - Expanded( - child: ListView.builder( - itemCount: allPlayers.length, - itemBuilder: (context, index) { - //TODO: Implement Custom RadioListTile, see text_icon_list_tile - return RadioListTile( - title: Text(allPlayers[index].name), - value: allPlayers[index], - ); - }, + Visibility( + visible: allPlayers.isNotEmpty, + replacement: TopCenteredMessage( + icon: Icons.info, + title: "Info", + message: "No players in this game.", + ), + child: Expanded( + child: RadioGroup( + groupValue: _player, + onChanged: (Player? value) { + setState(() { + _player = value; + }); + }, + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) { + setState(() { + _player = value; + }); + }, + ); + }, + ), + ), ), ), ], @@ -83,7 +106,12 @@ class _GameResultViewState extends State { CustomWidthButton( text: "Save", sizeRelativeToWidth: 0.95, - onPressed: null, + onPressed: _player != null + ? () { + print("Selected Winner: ${_player!.name}"); + Navigator.pop(context); + } + : null, ), SizedBox(height: 10), ], diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart new file mode 100644 index 0000000..1350239 --- /dev/null +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomRadioListTile extends StatelessWidget { + final String text; + final T value; + final ValueChanged onContainerTap; + + const CustomRadioListTile({ + super.key, + required this.text, + required this.value, + required this.onContainerTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onContainerTap(value), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + children: [ + Radio(value: value, activeColor: CustomTheme.primaryColor), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ); + } +} From 00fd6880e98cf53ba1ff868e2bf561db7d97072b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:24:35 +0100 Subject: [PATCH 16/54] add todo comment --- lib/presentation/views/main_menu/game_result_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index aa15c2a..94d7182 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -108,7 +108,9 @@ class _GameResultViewState extends State { sizeRelativeToWidth: 0.95, onPressed: _player != null ? () { - print("Selected Winner: ${_player!.name}"); + print( + "Selected Winner: ${_player!.name}", + ); //TODO: Add winner to db Navigator.pop(context); } : null, From d97871d15b134ef6bb2c9034ffd2a0853f233bb3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:29:21 +0100 Subject: [PATCH 17/54] fix lint --- .../views/main_menu/custom_navigation_bar.dart | 16 ++++++++-------- .../views/main_menu/game_result_view.dart | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index b20f80e..4003a5e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -62,18 +62,18 @@ class _CustomNavigationBarState extends State MaterialPageRoute( builder: (_) => GameResultView( game: Game( - name: "Test Game", + name: 'Test Game', players: [ - Player(name: "Petrus"), - Player(name: "Peter"), - Player(name: "Petra"), + Player(name: 'Petrus'), + Player(name: 'Peter'), + Player(name: 'Petra'), ], group: Group( - name: "Die Petris", + name: 'Die Petris', members: [ - Player(name: "Petralia"), - Player(name: "Petrenlia"), - Player(name: "Petrumlia"), + Player(name: 'Petralia'), + Player(name: 'Petrenlia'), + Player(name: 'Petrumlia'), ], ), ), diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 94d7182..2dcabc4 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -61,7 +61,7 @@ class _GameResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Select Winner:", + 'Select Winner:', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -69,10 +69,10 @@ class _GameResultViewState extends State { ), Visibility( visible: allPlayers.isNotEmpty, - replacement: TopCenteredMessage( + replacement: const TopCenteredMessage( icon: Icons.info, - title: "Info", - message: "No players in this game.", + title: 'Info', + message: 'No players in this game.', ), child: Expanded( child: RadioGroup( @@ -104,18 +104,18 @@ class _GameResultViewState extends State { ), ), CustomWidthButton( - text: "Save", + text: 'Save', sizeRelativeToWidth: 0.95, onPressed: _player != null ? () { print( - "Selected Winner: ${_player!.name}", + 'Selected Winner: ${_player!.name}', ); //TODO: Add winner to db Navigator.pop(context); } : null, ), - SizedBox(height: 10), + const SizedBox(height: 10), ], ), ), From 479e9a2575195b2bb580464ba1f98ccacf2ffc83 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 22:01:49 +0100 Subject: [PATCH 18/54] add spacing between title and list and rename appbar title to game name --- .../views/main_menu/game_result_view.dart | 7 ++--- .../widgets/tiles/custom_radio_list_tile.dart | 27 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 2dcabc4..07f200c 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -17,7 +17,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; - Player? _player; + Player? _player; //TODO: Set last winner as selected @override void initState() { @@ -32,8 +32,8 @@ class _GameResultViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Game Result', + title: Text( + widget.game.name, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -67,6 +67,7 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), + SizedBox(height: 2), Visibility( visible: allPlayers.isNotEmpty, replacement: const TopCenteredMessage( diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 1350239..5081bad 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -25,23 +25,20 @@ class CustomRadioListTile extends StatelessWidget { border: Border.all(color: CustomTheme.boxBorder), borderRadius: BorderRadius.circular(12), ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: Row( - children: [ - Radio(value: value, activeColor: CustomTheme.primaryColor), - Expanded( - child: Text( - text, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + child: Row( + children: [ + Radio(value: value, activeColor: CustomTheme.primaryColor), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), ), - ], - ), + ), + ], ), ), ); From 86ec4de5c063732a2ddbeca7bd7f8747b9c32242 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 22:03:38 +0100 Subject: [PATCH 19/54] add textoverflow behaviour --- lib/presentation/views/main_menu/game_result_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 07f200c..4103379 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -34,7 +34,11 @@ class _GameResultViewState extends State { scrolledUnderElevation: 0, title: Text( widget.game.name, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, + ), ), centerTitle: true, ), From 9ba3dd7909028ef6365607f395e72ada5f4a3794 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 23:22:02 +0100 Subject: [PATCH 20/54] added missing consts --- lib/presentation/views/main_menu/game_result_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 4103379..f86ded1 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -34,7 +34,7 @@ class _GameResultViewState extends State { scrolledUnderElevation: 0, title: Text( widget.game.name, - style: TextStyle( + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, @@ -71,7 +71,7 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - SizedBox(height: 2), + const SizedBox(height: 2), Visibility( visible: allPlayers.isNotEmpty, replacement: const TopCenteredMessage( From 099e587d458049ff4fe6c51ad92181eefffc8e79 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 16:45:47 +0100 Subject: [PATCH 21/54] remove useless skeleton data --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 60dcd90..df4b7a0 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -19,7 +19,7 @@ class _GameHistoryViewState extends State { late final AppDatabase db; late final List skeletonData = List.filled( - 10, + 4, Game( name: 'Skeleton Game', group: Group( From b443230285f3f9844d02781aa798cbf621da70b3 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 16:58:02 +0100 Subject: [PATCH 22/54] fixed loading too fast --- lib/presentation/views/main_menu/game_history_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index df4b7a0..f6f47ef 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -17,6 +17,7 @@ class GameHistoryView extends StatefulWidget { class _GameHistoryViewState extends State { late Future> _gameListFuture; late final AppDatabase db; + late bool isLoading = true; late final List skeletonData = List.filled( 4, @@ -47,6 +48,9 @@ class _GameHistoryViewState extends State { Future.wait([_gameListFuture]).then((result) async { await Future.delayed(const Duration(milliseconds: 250)); + setState(() { + isLoading = false; + }); }); } @@ -71,7 +75,6 @@ class _GameHistoryViewState extends State { ); } - final bool isLoading = snapshot.connectionState == ConnectionState.waiting; final List games = (isLoading ? skeletonData : (snapshot.data ?? []) From ae348499d410c7e584cb06d971c94a8adb30b9cc Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:00:13 +0100 Subject: [PATCH 23/54] moved functionality methods to the bottom of the file --- .../widgets/tiles/game_history_tile.dart | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index fdc9584..83e0ba0 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -17,47 +17,6 @@ class GameHistoryTile extends StatefulWidget { } class _GameHistoryTileState extends State { - String _formatDate(DateTime dateTime) { - final now = DateTime.now(); - final difference = now.difference(dateTime); - - if (difference.inDays == 0) { - return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; - } else if (difference.inDays == 1) { - return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; - } else if (difference.inDays < 7) { - return '${difference.inDays} days ago'; - } else { - return DateFormat('MMM d, yyyy').format(dateTime); - } - } - - List _getAllPlayers() { - final allPlayers = []; - final playerIds = {}; - - // Add players from game.players - if (widget.game.players != null) { - for (var player in widget.game.players!) { - if (!playerIds.contains(player.id)) { - allPlayers.add(player); - playerIds.add(player.id); - } - } - } - - // Add players from game.group.players - if (widget.game.group?.members != null) { - for (var player in widget.game.group!.members) { - if (!playerIds.contains(player.id)) { - allPlayers.add(player); - playerIds.add(player.id); - } - } - } - - return allPlayers; - } @override Widget build(BuildContext context) { @@ -172,7 +131,6 @@ class _GameHistoryTileState extends State { spacing: 6, runSpacing: 6, children: allPlayers.map((player) { - final isWinner = winner != null && player.id == winner.id; return TextIconTile( text: player.name, iconEnabled: false, @@ -184,4 +142,47 @@ class _GameHistoryTileState extends State { ), ); } + + String _formatDate(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays == 0) { + return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays == 1) { + return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM d, yyyy').format(dateTime); + } + } + + List _getAllPlayers() { + final allPlayers = []; + final playerIds = {}; + + // Add players from game.players + if (widget.game.players != null) { + for (var player in widget.game.players!) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + // Add players from game.group.players + if (widget.game.group?.members != null) { + for (var player in widget.game.group!.members) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + return allPlayers; + } + } \ No newline at end of file From cc50e497c9e671e669fdc9d519cfdfbde26834fd Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:04:08 +0100 Subject: [PATCH 24/54] merge duplicate if statements for group and winner sections --- .../widgets/tiles/game_history_tile.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 83e0ba0..066603e 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -60,7 +60,7 @@ class _GameHistoryTileState extends State { const SizedBox(height: 8), - if (group != null) + if (group != null) ...[ Row( children: [ const Icon( @@ -81,10 +81,10 @@ class _GameHistoryTileState extends State { ), ], ), + const SizedBox(height: 12), + ], - if (group != null) const SizedBox(height: 12), - - if (winner != null) + if (winner != null) ...[ Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: BoxDecoration( @@ -114,8 +114,8 @@ class _GameHistoryTileState extends State { ], ), ), - - if (winner != null) const SizedBox(height: 12), + const SizedBox(height: 12), + ], if (allPlayers.isNotEmpty) ...[ const Text( From 8c005d6e5e074fcbe5aa23d632786f6bf30e2d12 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:06:32 +0100 Subject: [PATCH 25/54] fix possible render overflow --- .../widgets/tiles/game_history_tile.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 066603e..684ba48 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -103,12 +103,15 @@ class _GameHistoryTileState extends State { color: Colors.amber, ), const SizedBox(width: 8), - Text( - 'Winner: ${winner.name}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, ), ), ], From a29123c964cd9c95f22456e7ba987cce6987091f Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:25:58 +0100 Subject: [PATCH 26/54] fix error messages --- lib/presentation/views/main_menu/game_history_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index f6f47ef..b5813cf 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -63,7 +63,7 @@ class _GameHistoryViewState extends State { return const Center( heightFactor: 4, child: Text( - 'Error while loading recent games.', + 'Error while loading games.', ), ); } @@ -71,7 +71,7 @@ class _GameHistoryViewState extends State { (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( heightFactor: 4, - child: Text('No recent games available.'), + child: Text('No games available.'), ); } From aa208bb2efabe86109b2202df069d3db7b6c42db Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 22:56:22 +0100 Subject: [PATCH 27/54] use standardized TopCenteredMessage --- .../views/main_menu/game_history_view.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b5813cf..3acc186 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -61,17 +62,21 @@ class _GameHistoryViewState extends State { builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return const Center( - heightFactor: 4, - child: Text( - 'Error while loading games.', + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', ), ); } if (snapshot.connectionState == ConnectionState.done && (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( - heightFactor: 4, - child: Text('No games available.'), + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), ); } From 9ee9da2ac8015bb77b0c14debd34a453122265ab Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 22:59:09 +0100 Subject: [PATCH 28/54] Made space at the bottom of the list smaller --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3acc186..7f7e943 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -107,7 +107,7 @@ class _GameHistoryViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == games.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 20, + height: MediaQuery.paddingOf(context).bottom - 80, ); } return GameHistoryTile(game: games[index]); From 516c2afd1ee3b56b9dc5c6eff6548dfdb1489b2e Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 12:14:22 +0100 Subject: [PATCH 29/54] remove colon behind players --- lib/presentation/widgets/tiles/game_history_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 684ba48..3cdd1ad 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -122,7 +122,7 @@ class _GameHistoryTileState extends State { if (allPlayers.isNotEmpty) ...[ const Text( - 'Players:', + 'Players', style: TextStyle( fontSize: 13, color: Colors.grey, From 40a3c1b82e27e065ea1170955aa7b4733298357d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 13:56:24 +0100 Subject: [PATCH 30/54] Removed comment --- lib/data/dao/group_game_dao.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 3f7a146..12bd1ce 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -11,8 +11,7 @@ class GroupGameDao extends DatabaseAccessor GroupGameDao(super.db); /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. If there is already group associated to the game, - /// it will be replaced. + /// [GroupGameTable]. Future addGroupToGame({ required String gameId, required String groupId, From 126dc7ed9723f8374aede9bd2edae38c768a4c2f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:04 +0100 Subject: [PATCH 31/54] Added exception --- lib/data/dao/group_game_dao.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 12bd1ce..da95607 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -16,6 +16,9 @@ class GroupGameDao extends DatabaseAccessor required String gameId, required String groupId, }) async { + if (await gameHasGroup(gameId: gameId)) { + throw Exception('Game already has a group'); + } await into(groupGameTable).insert( GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace, From d2d6852f31b71aa24d1648a430f48f62a77166e9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:26 +0100 Subject: [PATCH 32/54] removed comment --- lib/data/dao/game_dao.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index daee4b7..5d05914 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -65,7 +65,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Adds a new [Game] to the database. /// Also adds associated players and group if they exist. - /// If a game, player, or group already exists, it will be replaced. Future addGame({required Game game}) async { await db.transaction(() async { await into(gameTable).insert( From 71b2f30d290b31ad3c78887a3b17921065ed8519 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:36 +0100 Subject: [PATCH 33/54] removed comment --- lib/data/dao/game_dao.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 5d05914..c941499 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -100,7 +100,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Adds multiple [Game]s to the database in a batch operation. /// Also adds associated players and groups if they exist. /// If the [games] list is empty, the method returns immediately. - /// If a game, player, or group already exists, it will be replaced. Future addGamesAsList({required List games}) async { if (games.isEmpty) return; await db.transaction(() async { From f713bd6fb78cd54e1f8fd0819932689cb33d92cf Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 14:35:20 +0100 Subject: [PATCH 34/54] use custom app skeleton --- .../views/main_menu/game_history_view.dart | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 7f7e943..b5d894d 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,8 +5,8 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; // Add this import import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -86,21 +86,8 @@ class _GameHistoryViewState extends State { ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .toList(); - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), itemCount: games.length + 1, From fb28de5772ec1de22934a4dc8f28499999377e57 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 14:44:24 +0100 Subject: [PATCH 35/54] add create game button --- .../views/main_menu/game_history_view.dart | 115 +++++++++++------- 1 file changed, 73 insertions(+), 42 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b5d894d..5689c7b 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; // Add this import +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:provider/provider.dart'; class GameHistoryView extends StatefulWidget { @@ -57,51 +60,79 @@ class _GameHistoryViewState extends State { @override Widget build(BuildContext context) { - return FutureBuilder>( - future: _gameListFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'No Games Available', - ), - ); - } - - final List games = (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .toList(); - - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == games.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + body: Stack( + alignment: Alignment.center, + children: [ + FutureBuilder>( + future: _gameListFuture, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', + ), ); } - return GameHistoryTile(game: games[index]); + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), + ); + } + + final List games = (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .toList(); + + return AppSkeleton( + enabled: isLoading, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, + ); + } + return GameHistoryTile(game: games[index]); // Placeholder + }, + ), + ); }, ), - ); - }, + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: CustomWidthButton( + text: 'Create Game', + sizeRelativeToWidth: 0.90, + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); + }, + ), + ), + ], + ), ); } } \ No newline at end of file From 5ce4964c32b5a1231ab4c2784b938713eef7390a Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 29 Nov 2025 20:04:49 +0100 Subject: [PATCH 36/54] deleted double_row_info_tile --- .../widgets/tiles/double_row_info_tile.dart | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 lib/presentation/widgets/tiles/double_row_info_tile.dart diff --git a/lib/presentation/widgets/tiles/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart deleted file mode 100644 index 57404ff..0000000 --- a/lib/presentation/widgets/tiles/double_row_info_tile.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -Widget doubleRowInfoTile( - String titleOneUpperLeft, - String titleTwoUpperLeft, - String titleUpperRight, - String titleLowerLeft, - String titleLowerRight, -) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: CustomTheme.secondaryColor, - ), - child: Column( - children: [ - Row( - children: [ - Expanded( - flex: 10, - child: Text( - '$titleOneUpperLeft $titleTwoUpperLeft', - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Text( - titleUpperRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - Row( - children: [ - Expanded( - flex: 10, - child: Text( - titleLowerLeft, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 4, - child: Text( - titleLowerRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - ], - ), - ); -} From ec902c6196c283baeb8a18ceaf330c4917688418 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 5 Dec 2025 18:23:58 +0100 Subject: [PATCH 37/54] Removed print --- test/db_tests/player_game_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 6fd87c2..4e2616e 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -148,7 +148,6 @@ void main() { gameId: testGameOnlyPlayers.id, ); - print('existingPlayers: $existingPlayers'); if (existingPlayers == null || existingPlayers.isEmpty) { fail('Existing players should not be null or empty'); } From 3169eebd14c0d29d805f31a369aea82031103ef7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 5 Dec 2025 18:24:06 +0100 Subject: [PATCH 38/54] Import formatting --- .../views/main_menu/game_history_view.dart | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index af18a73..ea5ddd7 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,12 +5,10 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class GameHistoryView extends StatefulWidget { @@ -40,9 +38,7 @@ class _GameHistoryViewState extends State { ], ), winner: Player(name: 'Skeleton Player 1'), - players: [ - Player(name: 'Skeleton Player 6') - ], + players: [Player(name: 'Skeleton Player 6')], ), ); @@ -69,49 +65,53 @@ class _GameHistoryViewState extends State { children: [ FutureBuilder>( future: _gameListFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'No Games Available', - ), - ); - } + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), + ); + } - final List games = (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .toList(); + final List games = + (isLoading ? skeletonData : (snapshot.data ?? []) + ..sort( + (a, b) => b.createdAt.compareTo(a.createdAt), + )) + .toList(); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == games.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, - ); - } - return GameHistoryTile(game: games[index]); // Placeholder - }, - ), - ); - }, + return AppSkeleton( + enabled: isLoading, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, + ); + } + return GameHistoryTile( + game: games[index], + ); // Placeholder + }, + ), + ); + }, ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -137,4 +137,4 @@ class _GameHistoryViewState extends State { ), ); } -} \ No newline at end of file +} From e77896c1d4f39a49b59a33dc7d91de6980149357 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:25:31 +0100 Subject: [PATCH 39/54] removed settings icon leading to game result view --- .../main_menu/custom_navigation_bar.dart | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 4003a5e..7b08017 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -59,26 +56,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute( - builder: (_) => GameResultView( - game: Game( - name: 'Test Game', - players: [ - Player(name: 'Petrus'), - Player(name: 'Peter'), - Player(name: 'Petra'), - ], - group: Group( - name: 'Die Petris', - members: [ - Player(name: 'Petralia'), - Player(name: 'Petrenlia'), - Player(name: 'Petrumlia'), - ], - ), - ), - ), - ), //TODO Replace with Settingsview + MaterialPageRoute(builder: (_) => SettingsView()), ); setState(() { tabKeyCount++; From 9ac6b6e04c6e68dd9728e8faccb90aaa25d41c53 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:25:52 +0100 Subject: [PATCH 40/54] added ontap feature & argument --- .../widgets/tiles/game_history_tile.dart | 193 +++++++++--------- 1 file changed, 91 insertions(+), 102 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 3cdd1ad..83da859 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -6,142 +6,132 @@ import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { final Game game; + final VoidCallback onTap; - const GameHistoryTile({ - super.key, - required this.game, - }); + const GameHistoryTile({super.key, required this.game, required this.onTap}); @override State createState() => _GameHistoryTileState(); } class _GameHistoryTileState extends State { - @override Widget build(BuildContext context) { final group = widget.game.group; final winner = widget.game.winner; final allPlayers = _getAllPlayers(); - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.game.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Text( - _formatDate(widget.game.createdAt), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - - const SizedBox(height: 8), - - if (group != null) ...[ + return GestureDetector( + onTap: widget.onTap, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon( - Icons.group, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 6), Expanded( child: Text( - group.name, + widget.game.name, style: const TextStyle( - fontSize: 14, - color: Colors.grey, + fontSize: 18, + fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), - const SizedBox(height: 12), - ], - if (winner != null) ...[ - Container( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: BoxDecoration( - color: Colors.green.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.green.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Row( + const SizedBox(height: 8), + + if (group != null) ...[ + Row( children: [ - const Icon( - Icons.emoji_events, - size: 20, - color: Colors.amber, - ), - const SizedBox(width: 8), + const Icon(Icons.group, size: 16, color: Colors.grey), + const SizedBox(width: 6), Expanded( child: Text( - 'Winner: ${winner.name}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + group.name, + style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), ), ], ), - ), - const SizedBox(height: 12), - ], + const SizedBox(height: 12), + ], - if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', - style: TextStyle( - fontSize: 13, - color: Colors.grey, - fontWeight: FontWeight.w500, + if (winner != null) ...[ + Container( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - ), - const SizedBox(height: 6), - Wrap( - spacing: 6, - runSpacing: 6, - children: allPlayers.map((player) { - return TextIconTile( - text: player.name, - iconEnabled: false, - ); - }).toList(), - ), + const SizedBox(height: 12), + ], + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + return TextIconTile(text: player.name, iconEnabled: false); + }).toList(), + ), + ], ], - ], + ), ), ); } @@ -187,5 +177,4 @@ class _GameHistoryTileState extends State { return allPlayers; } - -} \ No newline at end of file +} From f7f97fcdcbfabd5ec31875327370078d6504e08f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:26:47 +0100 Subject: [PATCH 41/54] made tapping on game tile redirect to GameResultView --- .../views/main_menu/game_history_view.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index ea5ddd7..713b720 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; @@ -106,6 +107,15 @@ class _GameHistoryViewState extends State { ); } return GameHistoryTile( + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + GameResultView(game: games[index]), + ), + ); + }, game: games[index], ); // Placeholder }, From 8f2c7493d081342bf26e01b07f3c399777258f8a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:35:04 +0100 Subject: [PATCH 42/54] re-set gameListFuture to reload the games after changing the winner --- lib/presentation/views/main_menu/game_history_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 713b720..1356ee4 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -115,9 +115,12 @@ class _GameHistoryViewState extends State { GameResultView(game: games[index]), ), ); + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); }, game: games[index], - ); // Placeholder + ); }, ), ); From d8551b3a27d96886ceeb0ccedb9752d8c10ca5cb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:35:14 +0100 Subject: [PATCH 43/54] add db functionality --- .../views/main_menu/game_result_view.dart | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f86ded1..f74910f 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { final Game game; @@ -17,11 +19,16 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; + late final AppDatabase db; Player? _player; //TODO: Set last winner as selected @override void initState() { + db = Provider.of(context, listen: false); allPlayers = getAllPlayers(widget.game); + if (widget.game.winner != null) { + _player = allPlayers.firstWhere((p) => p.id == widget.game.winner!.id); + } super.initState(); } @@ -112,11 +119,29 @@ class _GameResultViewState extends State { text: 'Save', sizeRelativeToWidth: 0.95, onPressed: _player != null - ? () { - print( - 'Selected Winner: ${_player!.name}', - ); //TODO: Add winner to db - Navigator.pop(context); + ? () async { + print('Selected Winner: ${_player!.name}'); + bool success = await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _player!.id, + ); + if (!context.mounted) return; + if (success) { + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: const Center( + child: Text( + 'Error while setting winner, please try again', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + } + setState(() {}); } : null, ), From dba448b9c1debc43222b2946416c6ca47f3a3f71 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:38:28 +0100 Subject: [PATCH 44/54] added const --- .../views/main_menu/custom_navigation_bar.dart | 2 +- lib/presentation/views/main_menu/game_history_view.dart | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 7b08017..71a072e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -56,7 +56,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute(builder: (_) => SettingsView()), + MaterialPageRoute(builder: (_) => const SettingsView()), ); setState(() { tabKeyCount++; diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 1356ee4..aea958d 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -22,7 +22,6 @@ class GameHistoryView extends StatefulWidget { class _GameHistoryViewState extends State { late Future> _gameListFuture; late final AppDatabase db; - late bool isLoading = true; late final List skeletonData = List.filled( 4, @@ -51,9 +50,6 @@ class _GameHistoryViewState extends State { Future.wait([_gameListFuture]).then((result) async { await Future.delayed(const Duration(milliseconds: 250)); - setState(() { - isLoading = false; - }); }); } @@ -87,14 +83,14 @@ class _GameHistoryViewState extends State { ), ); } - + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; final List games = (isLoading ? skeletonData : (snapshot.data ?? []) ..sort( (a, b) => b.createdAt.compareTo(a.createdAt), )) .toList(); - return AppSkeleton( enabled: isLoading, child: ListView.builder( From e3ac91bf48be1b29b568c74042f7b12ee9b2c2b0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:41:01 +0100 Subject: [PATCH 45/54] remove todo --- lib/presentation/views/main_menu/game_result_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f74910f..7468adb 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -20,7 +20,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; late final AppDatabase db; - Player? _player; //TODO: Set last winner as selected + Player? _player; @override void initState() { From f5842f9c4ab3390bb605caae48acdd61020f1ded Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:44:36 +0100 Subject: [PATCH 46/54] remove print --- lib/presentation/views/main_menu/game_result_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 7468adb..a32ba95 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -120,7 +120,6 @@ class _GameResultViewState extends State { sizeRelativeToWidth: 0.95, onPressed: _player != null ? () async { - print('Selected Winner: ${_player!.name}'); bool success = await db.gameDao.setWinner( gameId: widget.game.id, winnerId: _player!.id, From 7323f52153bfc85502336b79e06b727c1a5d1624 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 20:33:27 +0100 Subject: [PATCH 47/54] add delay and change empty games message --- .../views/main_menu/game_history_view.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index aea958d..e4e135f 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -46,11 +46,10 @@ class _GameHistoryViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = db.gameDao.getAllGames(); - - Future.wait([_gameListFuture]).then((result) async { - await Future.delayed(const Duration(milliseconds: 250)); - }); + _gameListFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.gameDao.getAllGames(), + ); } @override @@ -78,8 +77,8 @@ class _GameHistoryViewState extends State { return const Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Error', - message: 'No Games Available', + title: 'Info', + message: 'No games created yet.', ), ); } From 03035138acb3d9580713c67296cfbc9e0f3696df Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 10:00:59 +0100 Subject: [PATCH 48/54] refactor game result view variable naming and layout --- .../views/main_menu/game_result_view.dart | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index a32ba95..e07b788 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -5,7 +5,6 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { @@ -20,14 +19,16 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; late final AppDatabase db; - Player? _player; + Player? _selectedPlayer; @override void initState() { db = Provider.of(context, listen: false); allPlayers = getAllPlayers(widget.game); if (widget.game.winner != null) { - _player = allPlayers.firstWhere((p) => p.id == widget.game.winner!.id); + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.game.winner!.id, + ); } super.initState(); } @@ -78,36 +79,28 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 2), - Visibility( - visible: allPlayers.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players in this game.', - ), - child: Expanded( - child: RadioGroup( - groupValue: _player, - onChanged: (Player? value) { - setState(() { - _player = value; - }); + const SizedBox(height: 10), + Expanded( + child: RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) { + setState(() { + _selectedPlayer = value; + }); + }, + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) { + setState(() { + _selectedPlayer = value; + }); + }, + ); }, - child: ListView.builder( - itemCount: allPlayers.length, - itemBuilder: (context, index) { - return CustomRadioListTile( - text: allPlayers[index].name, - value: allPlayers[index], - onContainerTap: (value) { - setState(() { - _player = value; - }); - }, - ); - }, - ), ), ), ), @@ -118,11 +111,11 @@ class _GameResultViewState extends State { CustomWidthButton( text: 'Save', sizeRelativeToWidth: 0.95, - onPressed: _player != null + onPressed: _selectedPlayer != null ? () async { bool success = await db.gameDao.setWinner( gameId: widget.game.id, - winnerId: _player!.id, + winnerId: _selectedPlayer!.id, ); if (!context.mounted) return; if (success) { From 306a783d67f8fa2b82c90d4b68893bdabb30bbc7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 11:22:34 +0100 Subject: [PATCH 49/54] removed dots after TopCenteredMessage text --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index e4e135f..3cecd2f 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -78,7 +78,7 @@ class _GameHistoryViewState extends State { child: TopCenteredMessage( icon: Icons.report, title: 'Info', - message: 'No games created yet.', + message: 'No games created yet', ), ); } diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 4601ef9..ce47f90 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -56,7 +56,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.report, title: 'Error', - message: 'Group data couldn\'t\nbe loaded.', + message: 'Group data couldn\'t\nbe loaded', ), ); } @@ -66,7 +66,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: 'No groups created yet.', + message: 'No groups created yet', ), ); } From 697767f0de46ffb39853892bbd9091c38cd7d27e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 13:31:08 +0100 Subject: [PATCH 50/54] made winner deselectable and added auto save on select --- .../views/main_menu/game_result_view.dart | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index e07b788..80a0774 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -3,7 +3,6 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; @@ -94,10 +93,24 @@ class _GameResultViewState extends State { return CustomRadioListTile( text: allPlayers[index].name, value: allPlayers[index], - onContainerTap: (value) { + onContainerTap: (value) async { setState(() { - _selectedPlayer = value; + if (_selectedPlayer == value) { + _selectedPlayer = null; + } else { + _selectedPlayer = value; + } }); + if (_selectedPlayer == null) { + await db.gameDao.removeWinner( + gameId: widget.game.id, + ); + } else { + await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _selectedPlayer!.id, + ); + } }, ); }, @@ -108,36 +121,6 @@ class _GameResultViewState extends State { ), ), ), - CustomWidthButton( - text: 'Save', - sizeRelativeToWidth: 0.95, - onPressed: _selectedPlayer != null - ? () async { - bool success = await db.gameDao.setWinner( - gameId: widget.game.id, - winnerId: _selectedPlayer!.id, - ); - if (!context.mounted) return; - if (success) { - Navigator.pop(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: const Center( - child: Text( - 'Error while setting winner, please try again', - style: TextStyle(color: Colors.white), - ), - ), - ), - ); - } - setState(() {}); - } - : null, - ), - const SizedBox(height: 10), ], ), ), From b1bb8b919f883fdc2ca54a4805f295314ce08d5a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 13:34:11 +0100 Subject: [PATCH 51/54] changed MaterialPageRoute to CupertinoPageRoute for ios animation --- lib/presentation/views/main_menu/game_history_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3cecd2f..31d1b56 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -105,7 +106,8 @@ class _GameHistoryViewState extends State { onTap: () async { await Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( + fullscreenDialog: true, builder: (context) => GameResultView(game: games[index]), ), From 91a727396419933c6c9533793034a432cdb0f385 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:12:34 +0100 Subject: [PATCH 52/54] madio radio list tile toggleable --- lib/presentation/widgets/tiles/custom_radio_list_tile.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 5081bad..11e8b40 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -27,7 +27,11 @@ class CustomRadioListTile extends StatelessWidget { ), child: Row( children: [ - Radio(value: value, activeColor: CustomTheme.primaryColor), + Radio( + value: value, + activeColor: CustomTheme.primaryColor, + toggleable: true, + ), Expanded( child: Text( text, From 1ed6290628104b1170939423093398f773e963ac Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:20:37 +0100 Subject: [PATCH 53/54] Refactor winner selection and persistence logic in GameResultView --- .../views/main_menu/game_result_view.dart | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 80a0774..8b093c1 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -82,10 +82,11 @@ class _GameResultViewState extends State { Expanded( child: RadioGroup( groupValue: _selectedPlayer, - onChanged: (Player? value) { + onChanged: (Player? value) async { setState(() { _selectedPlayer = value; }); + await _handleWinnerSaving(); }, child: ListView.builder( itemCount: allPlayers.length, @@ -95,22 +96,15 @@ class _GameResultViewState extends State { value: allPlayers[index], onContainerTap: (value) async { setState(() { + // Check if the already selected player is the same as the newly tapped player. if (_selectedPlayer == value) { + // If yes deselected the player by setting it to null. _selectedPlayer = null; - } else { - _selectedPlayer = value; - } + } else + // If no assign the newly tapped player to the selected player. + (_selectedPlayer = value); }); - if (_selectedPlayer == null) { - await db.gameDao.removeWinner( - gameId: widget.game.id, - ); - } else { - await db.gameDao.setWinner( - gameId: widget.game.id, - winnerId: _selectedPlayer!.id, - ); - } + await _handleWinnerSaving(); }, ); }, @@ -127,6 +121,17 @@ class _GameResultViewState extends State { ); } + Future _handleWinnerSaving() async { + if (_selectedPlayer == null) { + await db.gameDao.removeWinner(gameId: widget.game.id); + } else { + await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _selectedPlayer!.id, + ); + } + } + List getAllPlayers(Game game) { if (game.group == null && game.players != null) { return [...game.players!]; From dbbe04d4ccf32a28f790f327546a5eb91b994932 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:21:17 +0100 Subject: [PATCH 54/54] add braces to if/else statement in `game_result_view.dart` --- lib/presentation/views/main_menu/game_result_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 8b093c1..f13553b 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -100,9 +100,10 @@ class _GameResultViewState extends State { if (_selectedPlayer == value) { // If yes deselected the player by setting it to null. _selectedPlayer = null; - } else + } else { // If no assign the newly tapped player to the selected player. (_selectedPlayer = value); + } }); await _handleWinnerSaving(); },