From 279afbb707195daa9a95938c3e5fc063b6a83f42 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 12 Jun 2025 13:06:24 +0200 Subject: [PATCH 01/65] Correctet spelling in ModeSelectionView --- lib/views/mode_selection_view.dart | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/mode_selection_view.dart b/lib/views/mode_selection_view.dart index f4edf37..f54f595 100644 --- a/lib/views/mode_selection_view.dart +++ b/lib/views/mode_selection_view.dart @@ -18,7 +18,7 @@ class ModeSelectionMenu extends StatelessWidget { child: CupertinoListTile( title: Text('$pointLimit Punkte', style: CustomTheme.modeTitle), subtitle: Text( - 'Es wird solange gespielt, bis einer Spieler mehr als $pointLimit Punkte erreicht', + 'Es wird so lange gespielt, bis ein:e Spieler:in mehr als $pointLimit Punkte erreicht', style: CustomTheme.modeDescription, maxLines: 3, ), @@ -33,7 +33,7 @@ class ModeSelectionMenu extends StatelessWidget { title: Text('Unbegrenzt', style: CustomTheme.modeTitle), subtitle: const Text( 'Dem Spiel sind keine Grenzen gesetzt. Es wird so lange ' - 'gespielt, bis Ihr keine Lust mehr habt.', + 'gespielt, bis ihr keine Lust mehr habt.', style: CustomTheme.modeDescription, maxLines: 3, ), diff --git a/pubspec.yaml b/pubspec.yaml index aeaa875..b888193 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.2.9+203 +version: 0.2.9+204 environment: sdk: ^3.5.4 From fbc43e489314e02f798bcbbdf8352f1e3893304f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 12 Jun 2025 17:25:23 +0200 Subject: [PATCH 02/65] Updated build no --- ios/Runner.xcodeproj/project.pbxproj | 4 ---- pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 59d8209..21a9acd 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -293,14 +293,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; diff --git a/pubspec.yaml b/pubspec.yaml index 79caa72..5874a43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.0+232 +version: 0.3.0+233 environment: sdk: ^3.5.4 From abd60c6af8f7ebcedc1c115d55c61d778b2588c9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 13 Jun 2025 17:48:46 +0200 Subject: [PATCH 03/65] Hotfix: app appears in english, when anything other than german system language is selected --- lib/main.dart | 8 ++++++++ pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index eb3d022..27c3835 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,6 +56,14 @@ class _AppState extends State with WidgetsBindingObserver { Locale('en'), // English Locale('de'), // German ], + localeResolutionCallback: (locale, supportedLocales) { + for (final supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return supportedLocales.first; + }, theme: CupertinoThemeData( brightness: Brightness.dark, primaryColor: CustomTheme.primaryColor, diff --git a/pubspec.yaml b/pubspec.yaml index 5874a43..e389608 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.0+233 +version: 0.3.0+237 environment: sdk: ^3.5.4 From 6452a0bd776451188df6e2842f6a0ef013895de5 Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:45:59 +0200 Subject: [PATCH 04/65] language enhancements --- lib/l10n/app_en.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cde3b08..c73d5a8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -19,7 +19,7 @@ "about": "About", "empty_text_1": "Pretty empty here...", - "empty_text_2": "Add a new round using the button in the top right corner", + "empty_text_2": "Add a new round using the button in the top right corner.", "delete_game_title": "Delete game?", "delete_game_message": "Are you sure you want to delete the game {gameTitle}? This action cannot be undone.", "@delete_game_message": { @@ -65,9 +65,9 @@ "settings": "Settings", "cabo_penalty": "Cabo Penalty", - "cabo_penalty_subtitle": "... for falsely calling Cabo", + "cabo_penalty_subtitle": "... for falsely calling Cabo.", "point_limit": "Point Limit", - "point_limit_subtitle": "... the game ends here", + "point_limit_subtitle": "... the game ends here.", "reset_to_default": "Reset to Default", "game_data": "Game Data", "import_data": "Import Data", @@ -80,5 +80,5 @@ "app_version": "App Version", "load_version": "Loading version...", "build": "Build", - "about_text": "Hey :) Thanks for being one of the first users of my first app! I’ve put a lot of work into this project, and even though I (hopefully) thought of a lot, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the Testflight app or a message / email. Thank you very much!" -} \ No newline at end of file + "about_text": "Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!" +} From 1579c73d0ecc2b0f1d88d2cd6edd8440f13ae04a Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Sat, 14 Jun 2025 09:35:58 +0200 Subject: [PATCH 05/65] Change styling of about text --- lib/l10n/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c73d5a8..5a7da91 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -80,5 +80,5 @@ "app_version": "App Version", "load_version": "Loading version...", "build": "Build", - "about_text": "Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!" + "about_text": "Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!" } From 53bd0cb43b73661aedd7afc98d3e0e3802d65b70 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 19 Jun 2025 22:08:36 +0200 Subject: [PATCH 06/65] Updated flutter and dart version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b1cd01..e5e187d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # CABO Counter ![Version](https://img.shields.io/badge/Version-0.3.0-orange) -![Flutter](https://img.shields.io/badge/Flutter-3.24.5-blue?logo=flutter) -![Dart](https://img.shields.io/badge/Dart-3.5.4-blue?logo=dart) +![Flutter](https://img.shields.io/badge/Flutter-3.32.1-blue?logo=flutter) +![Dart](https://img.shields.io/badge/Dart-3.8.1-blue?logo=dart) ![iOS](https://img.shields.io/badge/iOS-18.5-white?logo=apple) ![GitHub Issues](https://img.shields.io/github/issues/flixcoo/Cabo-Counter?logo=github) ![GitHub Pull Requests](https://img.shields.io/github/issues-pr/flixcoo/Cabo-Counter?logo=github) From 7710646cd0b559a5a6cd95ff14e53f7ba1828626 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 27 Jun 2025 11:00:37 +0200 Subject: [PATCH 07/65] Added new Buttons and new GraphView --- lib/l10n/app_de.arb | 3 +++ lib/l10n/app_localizations.dart | 12 ++++++++++++ lib/l10n/app_localizations_de.dart | 6 ++++++ lib/l10n/app_localizations_en.dart | 14 ++++++++++---- lib/l10n/untranslated_messages.json | 7 ++++++- lib/views/active_game_view.dart | 25 +++++++++++++++++++++++++ lib/views/graph_view.dart | 22 ++++++++++++++++++++++ pubspec.yaml | 2 +- 8 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 lib/views/graph_view.dart diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index c107f9a..4d2f85b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -14,6 +14,7 @@ "player": "Spieler:in", "players": "Spieler:innen", "name": "Name", + "back": "Zurück", "home": "Home", "about": "Über", @@ -63,6 +64,8 @@ "done": "Fertig", "next_round": "Nächste Runde", + "game_statistics": "Spielstatistiken", + "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", "cabo_penalty_subtitle": "... für falsches Cabo sagen", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ac0b6a9..e6279c2 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -176,6 +176,12 @@ abstract class AppLocalizations { /// **'Name'** String get name; + /// No description provided for @back. + /// + /// In de, this message translates to: + /// **'Zurück'** + String get back; + /// No description provided for @home. /// /// In de, this message translates to: @@ -356,6 +362,12 @@ abstract class AppLocalizations { /// **'Nächste Runde'** String get next_round; + /// No description provided for @game_statistics. + /// + /// In de, this message translates to: + /// **'Spielstatistiken'** + String get game_statistics; + /// No description provided for @settings. /// /// In de, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index a3931c2..f6df27f 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -47,6 +47,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get name => 'Name'; + @override + String get back => 'Zurück'; + @override String get home => 'Home'; @@ -146,6 +149,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get next_round => 'Nächste Runde'; + @override + String get game_statistics => 'Spielstatistiken'; + @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f1958d4..07c3bba 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -47,6 +47,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get name => 'Name'; + @override + String get back => 'Zurück'; + @override String get home => 'Home'; @@ -58,7 +61,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get empty_text_2 => - 'Add a new round using the button in the top right corner'; + 'Add a new round using the button in the top right corner.'; @override String get delete_game_title => 'Delete game?'; @@ -143,6 +146,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get next_round => 'Next Round'; + @override + String get game_statistics => 'Spielstatistiken'; + @override String get settings => 'Settings'; @@ -150,13 +156,13 @@ class AppLocalizationsEn extends AppLocalizations { String get cabo_penalty => 'Cabo Penalty'; @override - String get cabo_penalty_subtitle => '... for falsely calling Cabo'; + String get cabo_penalty_subtitle => '... for falsely calling Cabo.'; @override String get point_limit => 'Point Limit'; @override - String get point_limit_subtitle => '... the game ends here'; + String get point_limit_subtitle => '... the game ends here.'; @override String get reset_to_default => 'Reset to Default'; @@ -196,5 +202,5 @@ class AppLocalizationsEn extends AppLocalizations { @override String get about_text => - 'Hey :) Thanks for being one of the first users of my first app! I’ve put a lot of work into this project, and even though I (hopefully) thought of a lot, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the Testflight app or a message / email. Thank you very much!'; + 'Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!'; } diff --git a/lib/l10n/untranslated_messages.json b/lib/l10n/untranslated_messages.json index 9e26dfe..7c9f0f2 100644 --- a/lib/l10n/untranslated_messages.json +++ b/lib/l10n/untranslated_messages.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "en": [ + "back", + "game_statistics" + ] +} diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 6899caf..c768eb2 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -1,8 +1,10 @@ import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:cabo_counter/views/graph_view.dart'; import 'package:cabo_counter/views/round_view.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; class ActiveGameView extends StatefulWidget { final GameSession gameSession; @@ -107,6 +109,29 @@ class _ActiveGameViewState extends State { )); }, ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).game, + style: CustomTheme.rowTitle, + ), + ), + Column( + children: [ + CupertinoListTile( + title: const Text('Statistiken'), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const GraphView()))), + const CupertinoListTile(title: Text('Spiel löschen')), + const CupertinoListTile( + title: Text( + 'Neues Spiel mit gleichen Einstellungen')), + const CupertinoListTile( + title: Text('Spiel exportieren')), + ], + ) ], ), ), diff --git a/lib/views/graph_view.dart b/lib/views/graph_view.dart new file mode 100644 index 0000000..00dff72 --- /dev/null +++ b/lib/views/graph_view.dart @@ -0,0 +1,22 @@ +import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:flutter/cupertino.dart'; + +class GraphView extends StatefulWidget { + const GraphView({super.key}); + + @override + State createState() => _GraphViewState(); +} + +class _GraphViewState extends State { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(AppLocalizations.of(context).game_statistics), + previousPageTitle: AppLocalizations.of(context).back, + ), + child: const Center(child: Text('GraphView')), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f61258f..ff80cad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.1+237 +version: 0.3.1+239 environment: sdk: ^3.5.4 From 943b0440aa75fe575c827e3fb88e0f20153d60a5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 27 Jun 2025 19:28:22 +0200 Subject: [PATCH 08/65] Lokalisierung erweitert --- lib/l10n/app_de.arb | 7 ++- lib/l10n/app_en.arb | 9 ++++ lib/l10n/app_localizations.dart | 30 +++++++++++-- lib/l10n/app_localizations_de.dart | 14 +++++- lib/l10n/app_localizations_en.dart | 16 ++++++- lib/l10n/untranslated_messages.json | 7 +-- lib/views/active_game_view.dart | 38 +++++++++++++--- lib/views/graph_view.dart | 68 +++++++++++++++++++++++++++-- pubspec.yaml | 3 +- 9 files changed, 168 insertions(+), 24 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4d2f85b..3cb8c5e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -64,7 +64,12 @@ "done": "Fertig", "next_round": "Nächste Runde", - "game_statistics": "Spielstatistiken", + "statistics": "Statistiken", + "delete_game": "Spiel löschen", + "new_game_same_settings": "Neues Spiel mit gleichen Einstellungen", + "export_game": "Spiel exportieren", + + "game_process": "Spielverlauf", "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5a7da91..f76e04a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -14,6 +14,7 @@ "player": "Player", "players": "Players", "name": "Name", + "back": "Back", "home": "Home", "about": "About", @@ -63,6 +64,13 @@ "done": "Done", "next_round": "Next Round", + "statistics": "Statistics", + "delete_game": "Delete Game", + "new_game_same_settings": "New Game with same Settings", + "export_game": "Export Game", + + "game_process": "Spielverlauf", + "settings": "Settings", "cabo_penalty": "Cabo Penalty", "cabo_penalty_subtitle": "... for falsely calling Cabo.", @@ -80,5 +88,6 @@ "app_version": "App Version", "load_version": "Loading version...", "build": "Build", + "about_text": "Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e6279c2..1751ab7 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -362,11 +362,35 @@ abstract class AppLocalizations { /// **'Nächste Runde'** String get next_round; - /// No description provided for @game_statistics. + /// No description provided for @statistics. /// /// In de, this message translates to: - /// **'Spielstatistiken'** - String get game_statistics; + /// **'Statistiken'** + String get statistics; + + /// No description provided for @delete_game. + /// + /// In de, this message translates to: + /// **'Spiel löschen'** + String get delete_game; + + /// No description provided for @new_game_same_settings. + /// + /// In de, this message translates to: + /// **'Neues Spiel mit gleichen Einstellungen'** + String get new_game_same_settings; + + /// No description provided for @export_game. + /// + /// In de, this message translates to: + /// **'Spiel exportieren'** + String get export_game; + + /// No description provided for @game_process. + /// + /// In de, this message translates to: + /// **'Spielverlauf'** + String get game_process; /// No description provided for @settings. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index f6df27f..434b5f1 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -150,7 +150,19 @@ class AppLocalizationsDe extends AppLocalizations { String get next_round => 'Nächste Runde'; @override - String get game_statistics => 'Spielstatistiken'; + String get statistics => 'Statistiken'; + + @override + String get delete_game => 'Spiel löschen'; + + @override + String get new_game_same_settings => 'Neues Spiel mit gleichen Einstellungen'; + + @override + String get export_game => 'Spiel exportieren'; + + @override + String get game_process => 'Spielverlauf'; @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 07c3bba..64cdb77 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -48,7 +48,7 @@ class AppLocalizationsEn extends AppLocalizations { String get name => 'Name'; @override - String get back => 'Zurück'; + String get back => 'Back'; @override String get home => 'Home'; @@ -147,7 +147,19 @@ class AppLocalizationsEn extends AppLocalizations { String get next_round => 'Next Round'; @override - String get game_statistics => 'Spielstatistiken'; + String get statistics => 'Statistics'; + + @override + String get delete_game => 'Delete Game'; + + @override + String get new_game_same_settings => 'New Game with same Settings'; + + @override + String get export_game => 'Export Game'; + + @override + String get game_process => 'Spielverlauf'; @override String get settings => 'Settings'; diff --git a/lib/l10n/untranslated_messages.json b/lib/l10n/untranslated_messages.json index 7c9f0f2..9e26dfe 100644 --- a/lib/l10n/untranslated_messages.json +++ b/lib/l10n/untranslated_messages.json @@ -1,6 +1 @@ -{ - "en": [ - "back", - "game_statistics" - ] -} +{} \ No newline at end of file diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index c768eb2..6266348 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -82,6 +82,8 @@ class _ActiveGameViewState extends State { return Padding( padding: const EdgeInsets.all(1), child: CupertinoListTile( + backgroundColorActivated: + CustomTheme.backgroundColor, title: Text( '${AppLocalizations.of(context).round} ${index + 1}', ), @@ -119,17 +121,39 @@ class _ActiveGameViewState extends State { Column( children: [ CupertinoListTile( - title: const Text('Statistiken'), + backgroundColorActivated: + CustomTheme.backgroundColor, + title: Text( + AppLocalizations.of(context).statistics, + ), onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (_) => const GraphView()))), - const CupertinoListTile(title: Text('Spiel löschen')), - const CupertinoListTile( + builder: (_) => GraphView( + gameSession: widget.gameSession, + )))), + CupertinoListTile( + title: + Text(AppLocalizations.of(context).delete_game, + style: const TextStyle( + color: Colors.white30, + )), + onTap: () {}, + ), + CupertinoListTile( title: Text( - 'Neues Spiel mit gleichen Einstellungen')), - const CupertinoListTile( - title: Text('Spiel exportieren')), + AppLocalizations.of(context) + .new_game_same_settings, + style: const TextStyle( + color: Colors.white30, + ))), + CupertinoListTile( + title: + Text(AppLocalizations.of(context).export_game, + style: const TextStyle( + color: Colors.white30, + )), + ), ], ) ], diff --git a/lib/views/graph_view.dart b/lib/views/graph_view.dart index 00dff72..345c670 100644 --- a/lib/views/graph_view.dart +++ b/lib/views/graph_view.dart @@ -1,22 +1,84 @@ +import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; class GraphView extends StatefulWidget { - const GraphView({super.key}); + final GameSession gameSession; + + const GraphView({super.key, required this.gameSession}); @override State createState() => _GraphViewState(); } class _GraphViewState extends State { + /// List of colors for the graph lines. + List lineColors = [ + Colors.red, + Colors.blue, + Colors.orange.shade400, + Colors.purple, + Colors.green, + ]; + @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text(AppLocalizations.of(context).game_statistics), + middle: Text(AppLocalizations.of(context).game_process), previousPageTitle: AppLocalizations.of(context).back, ), - child: const Center(child: Text('GraphView')), + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), + child: SfCartesianChart( + legend: + const Legend(isVisible: true, position: LegendPosition.bottom), + primaryXAxis: const NumericAxis(), + primaryYAxis: const NumericAxis(), + series: getCumulativeScores(), + ), + ), ); } + + /// Returns a list of LineSeries representing the cumulative scores of each player. + /// Each series contains data points for each round, showing the cumulative score up to that round. + /// The x-axis represents the round number, and the y-axis represents the cumulative score. + List> getCumulativeScores() { + final rounds = widget.gameSession.roundList; + final playerCount = widget.gameSession.players.length; + final playerNames = widget.gameSession.players; + + List> cumulativeScores = List.generate(playerCount, (_) => []); + List runningTotals = List.filled(playerCount, 0); + + for (var round in rounds) { + for (int i = 0; i < playerCount; i++) { + runningTotals[i] += round.scores[i]; + cumulativeScores[i].add(runningTotals[i]); + } + } + + /// Create a list of LineSeries for each player + /// Each series contains data points for each round + return List.generate(playerCount, (i) { + final data = List.generate( + cumulativeScores[i].length, + (j) => (j + 1, cumulativeScores[i][j]), // (round, score) + ); + + /// Create a LineSeries for the player + /// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score. + return LineSeries<(int, int), int>( + name: playerNames[i], + dataSource: data, + xValueMapper: (record, _) => record.$1, // Runde + yValueMapper: (record, _) => record.$2, // Punktestand + markerSettings: const MarkerSettings(isVisible: true), + color: lineColors[i], + ); + }); + } } diff --git a/pubspec.yaml b/pubspec.yaml index ff80cad..b0bf86a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.1+239 +version: 0.3.2+244 environment: sdk: ^3.5.4 @@ -25,6 +25,7 @@ dependencies: flutter_localizations: sdk: flutter intl: any + syncfusion_flutter_charts: ^30.1.37 dev_dependencies: flutter_test: From 9cf4a6947a4c70bbaec106623678c0b943bd4458 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 29 Jun 2025 01:55:45 +0200 Subject: [PATCH 09/65] Updated function and limited name length to 12 --- lib/data/game_session.dart | 7 +++++-- lib/views/create_game_view.dart | 1 + test/data/game_session_test.dart | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index 4896e02..cdbb461 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -73,11 +73,14 @@ class GameSession extends ChangeNotifier { (json['roundList'] as List).map((e) => Round.fromJson(e)).toList(); /// Returns the length of all player names combined. - int getLengthOfPlayerNames() { + int getMaxLengthOfPlayerNames() { int length = 0; for (String player in players) { - length += player.length; + if (player.length >= length) { + length = player.length; + } } + print('Maximale Länge der Spielernamen: $length'); return length; } diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 5ac5026..adcbf86 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -183,6 +183,7 @@ class _CreateGameState extends State { Expanded( child: CupertinoTextField( controller: _playerNameTextControllers[index], + maxLength: 12, placeholder: '${AppLocalizations.of(context).player} ${index + 1}', padding: const EdgeInsets.all(12), diff --git a/test/data/game_session_test.dart b/test/data/game_session_test.dart index 0e9bfa1..0ab65d0 100644 --- a/test/data/game_session_test.dart +++ b/test/data/game_session_test.dart @@ -62,8 +62,7 @@ void main() { group('Helper Functions', () { test('getLengthOfPlayerNames', () { - expect(session.getLengthOfPlayerNames(), - equals(15)); // Alice(5) + Bob(3) + Charlie(7) + expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie(7) }); test('increaseRound', () { From 3da8c886ff923a8e89c1913948c48bf7aba9c390 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 29 Jun 2025 01:56:32 +0200 Subject: [PATCH 10/65] Updated responsive design in segmented control --- devtools_options.yaml | 3 +++ lib/views/round_view.dart | 54 ++++++++++++++++++++++++++++----------- pubspec.yaml | 2 +- 3 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index 056d0a1..b945ec4 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -67,6 +67,7 @@ class _RoundViewState extends State { @override Widget build(BuildContext context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; + final maxLength = widget.gameSession.getMaxLengthOfPlayerNames(); return CupertinoPageScaffold( resizeToAvoidBottomInset: false, @@ -122,15 +123,8 @@ class _RoundViewState extends State { index, Padding( padding: EdgeInsets.symmetric( - horizontal: widget.gameSession - .getLengthOfPlayerNames() > - 20 - ? (widget.gameSession - .getLengthOfPlayerNames() > - 32 - ? 5 - : 10) - : 15, + horizontal: + _getSegmendetControlPadding(maxLength), vertical: 6, ), child: Text( @@ -139,11 +133,9 @@ class _RoundViewState extends State { maxLines: 1, style: TextStyle( fontWeight: FontWeight.bold, - fontSize: widget.gameSession - .getLengthOfPlayerNames() > - 28 - ? 14 - : 18, + fontSize: _getSegmendetControlFontSize( + widget.gameSession + .getMaxLengthOfPlayerNames()), ), ), ), @@ -191,7 +183,13 @@ class _RoundViewState extends State { borderRadius: BorderRadius.circular(12), child: CupertinoListTile( backgroundColor: CupertinoColors.secondaryLabel, - title: Row(children: [Text(name)]), + title: Row(children: [ + Expanded( + child: Text( + name, + overflow: TextOverflow.ellipsis, + )) + ]), subtitle: Text( '${widget.gameSession.playerScores[index]}' ' ${AppLocalizations.of(context).points}'), @@ -395,6 +393,32 @@ class _RoundViewState extends State { } } + double _getSegmendetControlFontSize(int maxLength) { + if (maxLength > 8) { + // 9 - 12 characters + return 9.0; + } else if (maxLength > 4) { + // 5 - 8 characters + return 15.0; + } else { + // 0 - 4 characters + return 18.0; + } + } + + double _getSegmendetControlPadding(int maxLength) { + if (maxLength > 8) { + // 9 - 12 characters + return 0.0; + } else if (maxLength > 4) { + // 5 - 8 characters + return 5.0; + } else { + // 0 - 4 characters + return 8.0; + } + } + @override void dispose() { for (final controller in _scoreControllerList) { diff --git a/pubspec.yaml b/pubspec.yaml index b0bf86a..ffc7346 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+244 +version: 0.3.2+248 environment: sdk: ^3.5.4 From 9fcd919847cd2c1740c00be179896c8de78924a4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 21:29:10 +0200 Subject: [PATCH 11/65] Implemented FittedBox Widget --- lib/views/round_view.dart | 22 ++++++++++++---------- pubspec.yaml | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index b945ec4..b1b98e3 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -123,19 +123,21 @@ class _RoundViewState extends State { index, Padding( padding: EdgeInsets.symmetric( - horizontal: + horizontal: 4 + _getSegmendetControlPadding(maxLength), vertical: 6, ), - child: Text( - name, - textAlign: TextAlign.center, - maxLines: 1, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: _getSegmendetControlFontSize( - widget.gameSession - .getMaxLengthOfPlayerNames()), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + name, + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: _getSegmendetControlFontSize( + maxLength), + ), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index ffc7346..0ba04e5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+248 +version: 0.3.3+255 environment: sdk: ^3.5.4 @@ -26,6 +26,7 @@ dependencies: sdk: flutter intl: any syncfusion_flutter_charts: ^30.1.37 + auto_size_text: ^3.0.0 dev_dependencies: flutter_test: From 722b8357ac0d8b902b105d55ac99a5a203981207 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 21:46:01 +0200 Subject: [PATCH 12/65] Added navigation on the big plus button in home menu --- lib/views/main_menu_view.dart | 22 +++++++++++++++------- pubspec.yaml | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 90c384c..2f5eee9 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -50,7 +50,9 @@ class _MainMenuViewState extends State { CupertinoPageRoute( builder: (context) => const SettingsView(), ), - ); + ).then((_) { + setState(() {}); + }); }, icon: const Icon(CupertinoIcons.settings, size: 30)), middle: const Text('Cabo Counter'), @@ -77,7 +79,12 @@ class _MainMenuViewState extends State { const SizedBox(height: 30), // Abstand von oben Center( child: GestureDetector( - onTap: () => setState(() {}), + onTap: () => Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const CreateGame(), + ), + ), child: Icon( CupertinoIcons.plus, size: 60, @@ -85,15 +92,16 @@ class _MainMenuViewState extends State { ), )), const SizedBox(height: 10), // Abstand von oben - const Padding( - padding: EdgeInsets.symmetric(horizontal: 70), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 70), child: Text( - 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', + '${AppLocalizations.of(context).empty_text_1}\n${AppLocalizations.of(context).empty_text_2}', textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 16), ), ), - ], + ], //Ganz schön leer hier... Füge über den Button oben rechts eine neue Runde hinzu. ) : ListView.builder( itemCount: gameManager.gameList.length, diff --git a/pubspec.yaml b/pubspec.yaml index b0bf86a..89087f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+244 +version: 0.3.2+245 environment: sdk: ^3.5.4 From 4e43ead072888b2f36514eac810105b15dec1e40 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:22:56 +0200 Subject: [PATCH 13/65] Updated import method --- lib/services/local_storage_service.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index e3fddcc..eafcdc4 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -111,7 +111,7 @@ class LocalStorageService { } /// Opens the file picker to import a JSON file and loads the game data from it. - static Future importJsonFile() async { + static Future importJsonFile() async { final result = await FilePicker.platform.pickFiles( dialogTitle: 'Wähle eine Datei mit Spieldaten aus', type: FileType.custom, @@ -121,14 +121,14 @@ class LocalStorageService { if (result == null) { print( '[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen'); - return false; + return 0; } try { final jsonString = await _readFileContent(result.files.single); if (!await validateJsonSchema(jsonString)) { - return false; + return -1; } final jsonData = json.decode(jsonString) as List; gameManager.gameList = jsonData @@ -137,15 +137,16 @@ class LocalStorageService { .toList(); print( '[local_storage_service.dart] Die Datei wurde erfolgreich Importiertn'); - return true; + await saveGameSessions(); + return 1; } on FormatException catch (e) { print( '[local_storage_service.dart] Ungültiges JSON-Format. Exception: $e'); - return false; + return -2; } on Exception catch (e) { print( '[local_storage_service.dart] Fehler beim Dateizugriff. Exception: $e'); - return false; + return -3; } } From dc8efbdd6130db0425dcc62772bf81970be1edf4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:23:12 +0200 Subject: [PATCH 14/65] Implemented feedback dialog --- lib/views/settings_view.dart | 59 +++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 53eb92d..2b185ef 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -1,3 +1,4 @@ +import 'package:analyzer_plugin/utilities/pair.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; @@ -125,27 +126,7 @@ class _SettingsViewState extends State { onPressed: () async { final success = await LocalStorageService.importJsonFile(); - if (!success && context.mounted) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text( - AppLocalizations.of(context) - .error), - content: Text( - AppLocalizations.of(context) - .error_import), - actions: [ - CupertinoDialogAction( - child: Text( - AppLocalizations.of(context) - .ok), - onPressed: () => - Navigator.pop(context), - ), - ], - )); - } + showFeedbackDialog(success); }), const SizedBox( width: 20, @@ -236,4 +217,40 @@ class _SettingsViewState extends State { Future _getPackageInfo() async { return await PackageInfo.fromPlatform(); } + + void showFeedbackDialog(int success) { + if (success == 0) return; + final content = _getDialogContent(success); + + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text(content.first), + content: Text(content.last), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + }); + } + + Pair _getDialogContent(int success) { + if (success == 1) { + return Pair(AppLocalizations.of(context).import_sucess_title, + AppLocalizations.of(context).import_sucess_message); + } else if (success == -1) { + return Pair(AppLocalizations.of(context).import_validation_error_title, + AppLocalizations.of(context).import_validation_error_title); + } else if (success == -2) { + return Pair(AppLocalizations.of(context).import_format_error_title, + AppLocalizations.of(context).import_format_error_title); + } else { + return Pair(AppLocalizations.of(context).import_generic_error_title, + AppLocalizations.of(context).import_generic_error_title); + } + } } From 98bc9bd837ed70fe3c4793755458fbcfaf1f7b6a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:25:29 +0200 Subject: [PATCH 15/65] Added localization --- lib/l10n/app_de.arb | 11 ++++++- lib/l10n/app_en.arb | 11 ++++++- lib/l10n/app_localizations.dart | 48 ++++++++++++++++++++++++++++-- lib/l10n/app_localizations_de.dart | 26 +++++++++++++++- lib/l10n/app_localizations_en.dart | 26 +++++++++++++++- lib/views/settings_view.dart | 2 +- pubspec.yaml | 3 +- 7 files changed, 118 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 3cb8c5e..26d9320 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -81,7 +81,16 @@ "import_data": "Daten importieren", "export_data": "Daten exportieren", "error": "Fehler", - "error_import": "Datei konnte nicht importiert werden", + + "import_sucess_title": "Import erfolgreich", + "import_sucess_message":"Die Spieldaten wurden erfolgreich importiert.", + "import_validation_error_title": "Validierung fehlgeschlagen", + "import_validation_error_message": "Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.", + "import_format_error_title": "Falsches Format", + "import_format_error_message": "Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.", + "import_generic_error_title": "Import fehlgeschlagen", + "import_generic_error_message": "Der Import ist fehlgeschlagen.", + "error_export": "Datei konnte nicht exportiert werden", "error_found": "Fehler gefunden?", "create_issue": "Issue erstellen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f76e04a..120d8a0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -81,7 +81,16 @@ "import_data": "Import Data", "export_data": "Export Data", "error": "Error", - "error_import": "Could not import file", + + "import_sucess_title": "Import successful", + "import_sucess_message":"The game data has been successfully imported.", + "import_validation_error_title": "Validation failed", + "import_validation_error_message": "No Cabo-Counter game data was found. Please make sure that this is a valid Cabo-Counter export file.", + "import_format_error_title": "Wrong format", + "import_format_error_message": "The file is not a valid JSON format or contains invalid data.", + "import_generic_error_title": "Import failed", + "import_generic_error_message": "The import has failed.", + "error_export": "Could not export file", "error_found": "Found a bug?", "create_issue": "Create Issue", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1751ab7..84b9a42 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -452,11 +452,53 @@ abstract class AppLocalizations { /// **'Fehler'** String get error; - /// No description provided for @error_import. + /// No description provided for @import_sucess_title. /// /// In de, this message translates to: - /// **'Datei konnte nicht importiert werden'** - String get error_import; + /// **'Import erfolgreich'** + String get import_sucess_title; + + /// No description provided for @import_sucess_message. + /// + /// In de, this message translates to: + /// **'Die Spieldaten wurden erfolgreich importiert.'** + String get import_sucess_message; + + /// No description provided for @import_validation_error_title. + /// + /// In de, this message translates to: + /// **'Validierung fehlgeschlagen'** + String get import_validation_error_title; + + /// No description provided for @import_validation_error_message. + /// + /// In de, this message translates to: + /// **'Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.'** + String get import_validation_error_message; + + /// No description provided for @import_format_error_title. + /// + /// In de, this message translates to: + /// **'Falsches Format'** + String get import_format_error_title; + + /// No description provided for @import_format_error_message. + /// + /// In de, this message translates to: + /// **'Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.'** + String get import_format_error_message; + + /// No description provided for @import_generic_error_title. + /// + /// In de, this message translates to: + /// **'Import fehlgeschlagen'** + String get import_generic_error_title; + + /// No description provided for @import_generic_error_message. + /// + /// In de, this message translates to: + /// **'Der Import ist fehlgeschlagen.'** + String get import_generic_error_message; /// No description provided for @error_export. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 434b5f1..e7719b1 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -195,7 +195,31 @@ class AppLocalizationsDe extends AppLocalizations { String get error => 'Fehler'; @override - String get error_import => 'Datei konnte nicht importiert werden'; + String get import_sucess_title => 'Import erfolgreich'; + + @override + String get import_sucess_message => + 'Die Spieldaten wurden erfolgreich importiert.'; + + @override + String get import_validation_error_title => 'Validierung fehlgeschlagen'; + + @override + String get import_validation_error_message => + 'Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.'; + + @override + String get import_format_error_title => 'Falsches Format'; + + @override + String get import_format_error_message => + 'Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.'; + + @override + String get import_generic_error_title => 'Import fehlgeschlagen'; + + @override + String get import_generic_error_message => 'Der Import ist fehlgeschlagen.'; @override String get error_export => 'Datei konnte nicht exportiert werden'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 64cdb77..9f9ff13 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -192,7 +192,31 @@ class AppLocalizationsEn extends AppLocalizations { String get error => 'Error'; @override - String get error_import => 'Could not import file'; + String get import_sucess_title => 'Import successful'; + + @override + String get import_sucess_message => + 'The game data has been successfully imported.'; + + @override + String get import_validation_error_title => 'Validation failed'; + + @override + String get import_validation_error_message => + 'No Cabo-Counter game data was found. Please make sure that this is a valid Cabo-Counter export file.'; + + @override + String get import_format_error_title => 'Wrong format'; + + @override + String get import_format_error_message => + 'The file is not a valid JSON format or contains invalid data.'; + + @override + String get import_generic_error_title => 'Import failed'; + + @override + String get import_generic_error_message => 'The import has failed.'; @override String get error_export => 'Could not export file'; diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 2b185ef..0f41d21 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -230,7 +230,7 @@ class _SettingsViewState extends State { content: Text(content.last), actions: [ CupertinoDialogAction( - child: const Text('OK'), + child: Text(AppLocalizations.of(context).ok), onPressed: () => Navigator.pop(context), ), ], diff --git a/pubspec.yaml b/pubspec.yaml index 89087f1..e464e51 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+245 +version: 0.3.2+252 environment: sdk: ^3.5.4 @@ -26,6 +26,7 @@ dependencies: sdk: flutter intl: any syncfusion_flutter_charts: ^30.1.37 + analyzer_plugin: ^0.13.4 dev_dependencies: flutter_test: From 59c710a43fde69752c8b8a51ebebea3fbf6369c7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:29:23 +0200 Subject: [PATCH 16/65] Removed print --- lib/data/game_session.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index cdbb461..80f1152 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -80,7 +80,6 @@ class GameSession extends ChangeNotifier { length = player.length; } } - print('Maximale Länge der Spielernamen: $length'); return length; } From 3e0482e963f2005c580e49a23ddd971d939651ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:29:31 +0200 Subject: [PATCH 17/65] Refactored method names --- lib/views/round_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index b1b98e3..c40db1b 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -124,7 +124,7 @@ class _RoundViewState extends State { Padding( padding: EdgeInsets.symmetric( horizontal: 4 + - _getSegmendetControlPadding(maxLength), + _getSegmentedControlPadding(maxLength), vertical: 6, ), child: FittedBox( @@ -135,7 +135,7 @@ class _RoundViewState extends State { maxLines: 1, style: TextStyle( fontWeight: FontWeight.bold, - fontSize: _getSegmendetControlFontSize( + fontSize: _getSegmentedControlFontSize( maxLength), ), ), @@ -395,7 +395,7 @@ class _RoundViewState extends State { } } - double _getSegmendetControlFontSize(int maxLength) { + double _getSegmentedControlFontSize(int maxLength) { if (maxLength > 8) { // 9 - 12 characters return 9.0; @@ -408,7 +408,7 @@ class _RoundViewState extends State { } } - double _getSegmendetControlPadding(int maxLength) { + double _getSegmentedControlPadding(int maxLength) { if (maxLength > 8) { // 9 - 12 characters return 0.0; From f7ea02603724c1386c11621e8e606c72e3dff90a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:32:33 +0200 Subject: [PATCH 18/65] Corrected typos --- lib/l10n/app_de.arb | 4 ++-- lib/l10n/app_en.arb | 4 ++-- lib/l10n/app_localizations.dart | 8 ++++---- lib/l10n/app_localizations_de.dart | 4 ++-- lib/l10n/app_localizations_en.dart | 4 ++-- lib/views/settings_view.dart | 10 +++++----- pubspec.yaml | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 26d9320..51147d0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -82,8 +82,8 @@ "export_data": "Daten exportieren", "error": "Fehler", - "import_sucess_title": "Import erfolgreich", - "import_sucess_message":"Die Spieldaten wurden erfolgreich importiert.", + "import_success_title": "Import erfolgreich", + "import_success_message":"Die Spieldaten wurden erfolgreich importiert.", "import_validation_error_title": "Validierung fehlgeschlagen", "import_validation_error_message": "Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.", "import_format_error_title": "Falsches Format", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 120d8a0..a6b1a8f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -82,8 +82,8 @@ "export_data": "Export Data", "error": "Error", - "import_sucess_title": "Import successful", - "import_sucess_message":"The game data has been successfully imported.", + "import_success_title": "Import successful", + "import_success_message":"The game data has been successfully imported.", "import_validation_error_title": "Validation failed", "import_validation_error_message": "No Cabo-Counter game data was found. Please make sure that this is a valid Cabo-Counter export file.", "import_format_error_title": "Wrong format", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 84b9a42..a4e1edb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -452,17 +452,17 @@ abstract class AppLocalizations { /// **'Fehler'** String get error; - /// No description provided for @import_sucess_title. + /// No description provided for @import_success_title. /// /// In de, this message translates to: /// **'Import erfolgreich'** - String get import_sucess_title; + String get import_success_title; - /// No description provided for @import_sucess_message. + /// No description provided for @import_success_message. /// /// In de, this message translates to: /// **'Die Spieldaten wurden erfolgreich importiert.'** - String get import_sucess_message; + String get import_success_message; /// No description provided for @import_validation_error_title. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index e7719b1..b06d454 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -195,10 +195,10 @@ class AppLocalizationsDe extends AppLocalizations { String get error => 'Fehler'; @override - String get import_sucess_title => 'Import erfolgreich'; + String get import_success_title => 'Import erfolgreich'; @override - String get import_sucess_message => + String get import_success_message => 'Die Spieldaten wurden erfolgreich importiert.'; @override diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9f9ff13..2dfc72d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -192,10 +192,10 @@ class AppLocalizationsEn extends AppLocalizations { String get error => 'Error'; @override - String get import_sucess_title => 'Import successful'; + String get import_success_title => 'Import successful'; @override - String get import_sucess_message => + String get import_success_message => 'The game data has been successfully imported.'; @override diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 0f41d21..7172969 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -240,17 +240,17 @@ class _SettingsViewState extends State { Pair _getDialogContent(int success) { if (success == 1) { - return Pair(AppLocalizations.of(context).import_sucess_title, - AppLocalizations.of(context).import_sucess_message); + return Pair(AppLocalizations.of(context).import_success_title, + AppLocalizations.of(context).import_success_message); } else if (success == -1) { return Pair(AppLocalizations.of(context).import_validation_error_title, - AppLocalizations.of(context).import_validation_error_title); + AppLocalizations.of(context).import_validation_error_message); } else if (success == -2) { return Pair(AppLocalizations.of(context).import_format_error_title, - AppLocalizations.of(context).import_format_error_title); + AppLocalizations.of(context).import_format_error_message); } else { return Pair(AppLocalizations.of(context).import_generic_error_title, - AppLocalizations.of(context).import_generic_error_title); + AppLocalizations.of(context).import_generic_error_message); } } } diff --git a/pubspec.yaml b/pubspec.yaml index e464e51..ecff07a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+252 +version: 0.3.2+253 environment: sdk: ^3.5.4 From 3c09d1f7f2e238915e8d229fe734afde9a973a64 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:34:42 +0200 Subject: [PATCH 19/65] Corrected tests --- test/data/game_session_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/game_session_test.dart b/test/data/game_session_test.dart index 0ab65d0..de4e284 100644 --- a/test/data/game_session_test.dart +++ b/test/data/game_session_test.dart @@ -61,8 +61,8 @@ void main() { }); group('Helper Functions', () { - test('getLengthOfPlayerNames', () { - expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie(7) + test('getMaxLengthOfPlayerNames', () { + expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie (7) }); test('increaseRound', () { From 9d3d6b3ca3688643408ef920596487be087a0ba6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:34:49 +0200 Subject: [PATCH 20/65] Corrected docs --- lib/data/game_session.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index 80f1152..a741ae8 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -72,7 +72,7 @@ class GameSession extends ChangeNotifier { roundList = (json['roundList'] as List).map((e) => Round.fromJson(e)).toList(); - /// Returns the length of all player names combined. + /// Returns the length of the longest player name. int getMaxLengthOfPlayerNames() { int length = 0; for (String player in players) { From aca46fec0f730dd4f83553c68a26fa4c3c5ace07 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:35:00 +0200 Subject: [PATCH 21/65] Removed unused package --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f0a8fd2..7dc76bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,6 @@ dependencies: sdk: flutter intl: any syncfusion_flutter_charts: ^30.1.37 - auto_size_text: ^3.0.0 dev_dependencies: flutter_test: From 854afe1e7f6041d064f9119203e8d638a016f434 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:39:15 +0200 Subject: [PATCH 22/65] Replaced pair with tuple --- lib/views/settings_view.dart | 31 +++++++++++++++++++------------ pubspec.yaml | 3 +-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 7172969..4876cc7 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -1,4 +1,3 @@ -import 'package:analyzer_plugin/utilities/pair.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; @@ -226,8 +225,8 @@ class _SettingsViewState extends State { context: context, builder: (context) { return CupertinoAlertDialog( - title: Text(content.first), - content: Text(content.last), + title: Text(content.$1), + content: Text(content.$2), actions: [ CupertinoDialogAction( child: Text(AppLocalizations.of(context).ok), @@ -238,19 +237,27 @@ class _SettingsViewState extends State { }); } - Pair _getDialogContent(int success) { + (String, String) _getDialogContent(int success) { if (success == 1) { - return Pair(AppLocalizations.of(context).import_success_title, - AppLocalizations.of(context).import_success_message); + return ( + AppLocalizations.of(context).import_success_title, + AppLocalizations.of(context).import_success_message + ); } else if (success == -1) { - return Pair(AppLocalizations.of(context).import_validation_error_title, - AppLocalizations.of(context).import_validation_error_message); + return ( + AppLocalizations.of(context).import_validation_error_title, + AppLocalizations.of(context).import_validation_error_message + ); } else if (success == -2) { - return Pair(AppLocalizations.of(context).import_format_error_title, - AppLocalizations.of(context).import_format_error_message); + return ( + AppLocalizations.of(context).import_format_error_title, + AppLocalizations.of(context).import_format_error_message + ); } else { - return Pair(AppLocalizations.of(context).import_generic_error_title, - AppLocalizations.of(context).import_generic_error_message); + return ( + AppLocalizations.of(context).import_generic_error_title, + AppLocalizations.of(context).import_generic_error_message + ); } } } diff --git a/pubspec.yaml b/pubspec.yaml index ecff07a..c55a475 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.2+253 +version: 0.3.2+254 environment: sdk: ^3.5.4 @@ -26,7 +26,6 @@ dependencies: sdk: flutter intl: any syncfusion_flutter_charts: ^30.1.37 - analyzer_plugin: ^0.13.4 dev_dependencies: flutter_test: From c72db61997d4a6f3b471ebaceff177a42826562a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:45:17 +0200 Subject: [PATCH 23/65] Destructed record --- lib/views/settings_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 4876cc7..068138a 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -219,14 +219,14 @@ class _SettingsViewState extends State { void showFeedbackDialog(int success) { if (success == 0) return; - final content = _getDialogContent(success); + final (title, message) = _getDialogContent(success); showCupertinoDialog( context: context, builder: (context) { return CupertinoAlertDialog( - title: Text(content.$1), - content: Text(content.$2), + title: Text(title), + content: Text(message), actions: [ CupertinoDialogAction( child: Text(AppLocalizations.of(context).ok), From ac2b071a7ec5b2d3d0b9dd7f0bc76042592b34fb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:56:59 +0200 Subject: [PATCH 24/65] Implemented enum for import status --- lib/services/local_storage_service.dart | 20 +++++++---- lib/views/settings_view.dart | 48 +++++++++++++------------ 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index eafcdc4..6039a7b 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -9,6 +9,14 @@ import 'package:flutter/services.dart'; import 'package:json_schema/json_schema.dart'; import 'package:path_provider/path_provider.dart'; +enum ImportStatus { + success, + canceled, + validationError, + formatError, + genericError +} + class LocalStorageService { static const String _fileName = 'game_data.json'; @@ -111,7 +119,7 @@ class LocalStorageService { } /// Opens the file picker to import a JSON file and loads the game data from it. - static Future importJsonFile() async { + static Future importJsonFile() async { final result = await FilePicker.platform.pickFiles( dialogTitle: 'Wähle eine Datei mit Spieldaten aus', type: FileType.custom, @@ -121,14 +129,14 @@ class LocalStorageService { if (result == null) { print( '[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen'); - return 0; + return ImportStatus.canceled; } try { final jsonString = await _readFileContent(result.files.single); if (!await validateJsonSchema(jsonString)) { - return -1; + return ImportStatus.validationError; } final jsonData = json.decode(jsonString) as List; gameManager.gameList = jsonData @@ -138,15 +146,15 @@ class LocalStorageService { print( '[local_storage_service.dart] Die Datei wurde erfolgreich Importiertn'); await saveGameSessions(); - return 1; + return ImportStatus.success; } on FormatException catch (e) { print( '[local_storage_service.dart] Ungültiges JSON-Format. Exception: $e'); - return -2; + return ImportStatus.formatError; } on Exception catch (e) { print( '[local_storage_service.dart] Fehler beim Dateizugriff. Exception: $e'); - return -3; + return ImportStatus.genericError; } } diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 068138a..270118f 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -217,7 +217,7 @@ class _SettingsViewState extends State { return await PackageInfo.fromPlatform(); } - void showFeedbackDialog(int success) { + void showFeedbackDialog(ImportStatus success) { if (success == 0) return; final (title, message) = _getDialogContent(success); @@ -237,27 +237,31 @@ class _SettingsViewState extends State { }); } - (String, String) _getDialogContent(int success) { - if (success == 1) { - return ( - AppLocalizations.of(context).import_success_title, - AppLocalizations.of(context).import_success_message - ); - } else if (success == -1) { - return ( - AppLocalizations.of(context).import_validation_error_title, - AppLocalizations.of(context).import_validation_error_message - ); - } else if (success == -2) { - return ( - AppLocalizations.of(context).import_format_error_title, - AppLocalizations.of(context).import_format_error_message - ); - } else { - return ( - AppLocalizations.of(context).import_generic_error_title, - AppLocalizations.of(context).import_generic_error_message - ); + (String, String) _getDialogContent(ImportStatus success) { + switch (success) { + case ImportStatus.success: + return ( + AppLocalizations.of(context).import_success_title, + AppLocalizations.of(context).import_success_message + ); + case ImportStatus.validationError: + return ( + AppLocalizations.of(context).import_validation_error_title, + AppLocalizations.of(context).import_validation_error_message + ); + + case ImportStatus.formatError: + return ( + AppLocalizations.of(context).import_format_error_title, + AppLocalizations.of(context).import_format_error_message + ); + case ImportStatus.genericError: + return ( + AppLocalizations.of(context).import_generic_error_title, + AppLocalizations.of(context).import_generic_error_message + ); + case ImportStatus.canceled: + return ('', ''); } } } From dc0482e9b7482ae1989e57aee00cff4d3dc7e5cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 22:59:54 +0200 Subject: [PATCH 25/65] Renamed input var --- lib/views/settings_view.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 270118f..b36347d 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -217,9 +217,9 @@ class _SettingsViewState extends State { return await PackageInfo.fromPlatform(); } - void showFeedbackDialog(ImportStatus success) { - if (success == 0) return; - final (title, message) = _getDialogContent(success); + void showFeedbackDialog(ImportStatus status) { + if (status == ImportStatus.canceled) return; + final (title, message) = _getDialogContent(status); showCupertinoDialog( context: context, @@ -237,8 +237,8 @@ class _SettingsViewState extends State { }); } - (String, String) _getDialogContent(ImportStatus success) { - switch (success) { + (String, String) _getDialogContent(ImportStatus status) { + switch (status) { case ImportStatus.success: return ( AppLocalizations.of(context).import_success_title, From 711e23a18a70f4fc410afedaf43d808d3af2602c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 1 Jul 2025 23:05:36 +0200 Subject: [PATCH 26/65] Changed swipe to delete direction --- lib/views/main_menu_view.dart | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 2f5eee9..2c1dba2 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -114,15 +114,15 @@ class _MainMenuViewState extends State { key: Key(session.gameTitle), background: Container( color: CupertinoColors.destructiveRed, - alignment: Alignment.centerLeft, + alignment: Alignment.centerRight, padding: - const EdgeInsets.only(left: 20.0), + const EdgeInsets.only(right: 20.0), child: const Icon( CupertinoIcons.delete, color: CupertinoColors.white, ), ), - direction: DismissDirection.startToEnd, + direction: DismissDirection.endToStart, confirmDismiss: (direction) async { final String gameTitle = gameManager .gameList[index].gameTitle; diff --git a/pubspec.yaml b/pubspec.yaml index 39afb1b..af1107f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.3+265 +version: 0.3.4+267 environment: sdk: ^3.5.4 From a0206e613f934e6e39413137aac43d2da5c762f7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 2 Jul 2025 15:51:26 +0200 Subject: [PATCH 27/65] Removed forgotten string comment --- lib/views/main_menu_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 2c1dba2..ab52d4e 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -101,7 +101,7 @@ class _MainMenuViewState extends State { style: const TextStyle(fontSize: 16), ), ), - ], //Ganz schön leer hier... Füge über den Button oben rechts eine neue Runde hinzu. + ], ) : ListView.builder( itemCount: gameManager.gameList.length, From d6413c096d4ec2132ff59efe7a01db4e9983ad58 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 2 Jul 2025 16:30:29 +0200 Subject: [PATCH 28/65] Implemented new game same settings features --- lib/views/active_game_view.dart | 25 +++++++++++++++++------ lib/views/create_game_view.dart | 36 ++++++++++++++++++++++++++------- lib/views/main_menu_view.dart | 5 +++-- pubspec.yaml | 2 +- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 6266348..d4ff7bd 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -1,6 +1,7 @@ import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:cabo_counter/views/create_game_view.dart'; import 'package:cabo_counter/views/graph_view.dart'; import 'package:cabo_counter/views/round_view.dart'; import 'package:flutter/cupertino.dart'; @@ -141,12 +142,24 @@ class _ActiveGameViewState extends State { onTap: () {}, ), CupertinoListTile( - title: Text( - AppLocalizations.of(context) - .new_game_same_settings, - style: const TextStyle( - color: Colors.white30, - ))), + title: Text( + AppLocalizations.of(context) + .new_game_same_settings, + ), + onTap: () { + Navigator.pushReplacement( + context, + CupertinoPageRoute( + builder: (_) => CreateGameView( + gameTitle: + widget.gameSession.gameTitle, + isPointsLimitEnabled: widget + .gameSession + .isPointsLimitEnabled, + players: widget.gameSession.players, + ))); + }, + ), CupertinoListTile( title: Text(AppLocalizations.of(context).export_game, diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index adcbf86..70c65b4 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -7,18 +7,25 @@ import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/mode_selection_view.dart'; import 'package:flutter/cupertino.dart'; -class CreateGame extends StatefulWidget { - const CreateGame({super.key}); +class CreateGameView extends StatefulWidget { + final String? gameTitle; + final bool? isPointsLimitEnabled; + final List? players; + + const CreateGameView({ + super.key, + this.gameTitle, + this.isPointsLimitEnabled, + this.players, + }); @override // ignore: library_private_types_in_public_api - _CreateGameState createState() => _CreateGameState(); + _CreateGameViewState createState() => _CreateGameViewState(); } -class _CreateGameState extends State { - final List _playerNameTextControllers = [ - TextEditingController() - ]; +class _CreateGameViewState extends State { + late List _playerNameTextControllers = []; final TextEditingController _gameTitleTextController = TextEditingController(); @@ -28,6 +35,21 @@ class _CreateGameState extends State { /// Variable to store the selected game mode. bool? selectedMode; + @override + void initState() { + selectedMode = widget.isPointsLimitEnabled; + _gameTitleTextController.text = widget.gameTitle ?? ''; + if (widget.players != null) { + _playerNameTextControllers = []; + for (var player in widget.players!) { + _playerNameTextControllers.add(TextEditingController(text: player)); + } + } else { + _playerNameTextControllers = [TextEditingController()]; + } + super.initState(); + } + @override Widget build(BuildContext context) { return CupertinoPageScaffold( diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index ab52d4e..f41a0ef 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -61,7 +61,7 @@ class _MainMenuViewState extends State { Navigator.push( context, CupertinoPageRoute( - builder: (context) => const CreateGame(), + builder: (context) => const CreateGameView(), ), ) }, @@ -82,7 +82,8 @@ class _MainMenuViewState extends State { onTap: () => Navigator.push( context, CupertinoPageRoute( - builder: (context) => const CreateGame(), + builder: (context) => + const CreateGameView(), ), ), child: Icon( diff --git a/pubspec.yaml b/pubspec.yaml index af1107f..a7c91ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.4+267 +version: 0.3.5+269 environment: sdk: ^3.5.4 From 8d411fcdb23c76f2c6690fcc412b3f9ab15556ce Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 2 Jul 2025 17:15:23 +0200 Subject: [PATCH 29/65] Refactoring & dispose updated --- lib/views/create_game_view.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 70c65b4..609e9b0 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -33,11 +33,11 @@ class _CreateGameViewState extends State { final int maxPlayers = 5; /// Variable to store the selected game mode. - bool? selectedMode; + bool? _isPointsLimitEnabled; @override void initState() { - selectedMode = widget.isPointsLimitEnabled; + _isPointsLimitEnabled = widget.isPointsLimitEnabled; _gameTitleTextController.text = widget.gameTitle ?? ''; if (widget.players != null) { _playerNameTextControllers = []; @@ -91,9 +91,9 @@ class _CreateGameViewState extends State { suffix: Row( children: [ Text( - selectedMode == null + _isPointsLimitEnabled == null ? AppLocalizations.of(context).select_mode - : (selectedMode! + : (_isPointsLimitEnabled! ? '${Globals.pointLimit} ${AppLocalizations.of(context).points}' : AppLocalizations.of(context).unlimited), ), @@ -102,7 +102,7 @@ class _CreateGameViewState extends State { ], ), onTap: () async { - final selected = await Navigator.push( + final selectedMode = await Navigator.push( context, CupertinoPageRoute( builder: (context) => ModeSelectionMenu( @@ -111,9 +111,9 @@ class _CreateGameViewState extends State { ), ); - if (selected != null) { + if (selectedMode != null) { setState(() { - selectedMode = selected; + _isPointsLimitEnabled = selectedMode; }); } }, @@ -252,7 +252,7 @@ class _CreateGameViewState extends State { ); return; } - if (selectedMode == null) { + if (_isPointsLimitEnabled == null) { showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( @@ -315,7 +315,7 @@ class _CreateGameViewState extends State { players: players, pointLimit: Globals.pointLimit, caboPenalty: Globals.caboPenalty, - isPointsLimitEnabled: selectedMode!, + isPointsLimitEnabled: _isPointsLimitEnabled!, ); final index = await gameManager.addGameSession(gameSession); if (context.mounted) { @@ -343,6 +343,7 @@ class _CreateGameViewState extends State { @override void dispose() { + _gameTitleTextController.dispose(); for (var controller in _playerNameTextControllers) { controller.dispose(); } From 694fa3394bbca66f3d6a79811632b2da07afa00d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 2 Jul 2025 17:20:02 +0200 Subject: [PATCH 30/65] Removed unnessecary code --- lib/views/create_game_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 609e9b0..7377178 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -25,7 +25,9 @@ class CreateGameView extends StatefulWidget { } class _CreateGameViewState extends State { - late List _playerNameTextControllers = []; + final List _playerNameTextControllers = [ + TextEditingController() + ]; final TextEditingController _gameTitleTextController = TextEditingController(); @@ -39,13 +41,12 @@ class _CreateGameViewState extends State { void initState() { _isPointsLimitEnabled = widget.isPointsLimitEnabled; _gameTitleTextController.text = widget.gameTitle ?? ''; + if (widget.players != null) { - _playerNameTextControllers = []; + _playerNameTextControllers.clear(); for (var player in widget.players!) { _playerNameTextControllers.add(TextEditingController(text: player)); } - } else { - _playerNameTextControllers = [TextEditingController()]; } super.initState(); } From de423aaec3483fa284b003b9481cc27db46a9df5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 2 Jul 2025 17:41:05 +0200 Subject: [PATCH 31/65] Small changes --- lib/views/create_game_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 7377178..6c52890 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -34,11 +34,13 @@ class _CreateGameViewState extends State { /// Maximum number of players allowed in the game. final int maxPlayers = 5; - /// Variable to store the selected game mode. + /// Variable to store whether the points limit feature is enabled. bool? _isPointsLimitEnabled; @override void initState() { + super.initState(); + _isPointsLimitEnabled = widget.isPointsLimitEnabled; _gameTitleTextController.text = widget.gameTitle ?? ''; @@ -48,7 +50,6 @@ class _CreateGameViewState extends State { _playerNameTextControllers.add(TextEditingController(text: player)); } } - super.initState(); } @override @@ -348,6 +349,7 @@ class _CreateGameViewState extends State { for (var controller in _playerNameTextControllers) { controller.dispose(); } + super.dispose(); } } From 38e43f54d884d1c1dd227034aaaea9c0281b8e21 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 11:52:40 +0200 Subject: [PATCH 32/65] Added uuid to gameSession class & json scheme --- assets/schema.json | 3 +++ lib/data/game_session.dart | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 17d7faa..a9a07e5 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -5,6 +5,9 @@ "items": { "type": "object", "properties": { + "id": { + "type": "string" + }, "createdAt": { "type": "string" }, diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index a741ae8..ada34dc 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -1,5 +1,6 @@ import 'package:cabo_counter/data/round.dart'; import 'package:flutter/cupertino.dart'; +import 'package:uuid/uuid.dart'; /// This class represents a game session for Cabo game. /// [createdAt] is the timestamp of when the game session was created. @@ -12,6 +13,7 @@ import 'package:flutter/cupertino.dart'; /// [isGameFinished] is a boolean indicating if the game has ended yet. /// [winner] is the name of the player who won the game. class GameSession extends ChangeNotifier { + late String id; final DateTime createdAt; final String gameTitle; final List players; @@ -33,17 +35,21 @@ class GameSession extends ChangeNotifier { required this.isPointsLimitEnabled, }) { playerScores = List.filled(players.length, 0); + var uuid = const Uuid(); + id = uuid.v1(); + print('GameSession created with ID: $id'); } @override toString() { - return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, ' + return ('GameSession: [id: $id, createdAt: $createdAt, gameTitle: $gameTitle, ' 'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,' ' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]'); } /// Converts the GameSession object to a JSON map. Map toJson() => { + 'id': id, 'createdAt': createdAt.toIso8601String(), 'gameTitle': gameTitle, 'players': players, @@ -59,7 +65,8 @@ class GameSession extends ChangeNotifier { /// Creates a GameSession object from a JSON map. GameSession.fromJson(Map json) - : createdAt = DateTime.parse(json['createdAt']), + : id = json['id'] ?? const Uuid().v1(), + createdAt = DateTime.parse(json['createdAt']), gameTitle = json['gameTitle'], players = List.from(json['players']), pointLimit = json['pointLimit'], From 9eb3cac0981d9253d2dbceeda201996138a7d72e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 11:52:50 +0200 Subject: [PATCH 33/65] Addewd temp print --- lib/services/local_storage_service.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 6039a7b..71dd332 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -86,6 +86,11 @@ class LocalStorageService { GameSession.fromJson(jsonItem as Map)) .toList(); + for (GameSession session in gameManager.gameList) { + print( + '[local_storage_service.dart] Geladene Session: ${session.gameTitle} - ${session.id}'); + } + print( '[local_storage_service.dart] Die Spieldaten wurden erfolgreich geladen und verarbeitet'); return true; From a756c493970d62c1563c50c9cd31c2657894b34f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 11:53:30 +0200 Subject: [PATCH 34/65] Added function getGameSessionIndexById() and renamed removeGameSessionByIndex() --- lib/data/game_manager.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index 94b6287..f00d73c 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -30,12 +30,19 @@ class GameManager extends ChangeNotifier { /// Takes a [index] as input. It then removes the session at the specified index from the `gameList`, /// sorts the list in descending order based on the creation date, and notifies listeners of the change. /// It also saves the updated game sessions to local storage. - void removeGameSession(int index) { + void removeGameSessionByIndex(int index) { gameList[index].removeListener(notifyListeners); gameList.removeAt(index); notifyListeners(); LocalStorageService.saveGameSessions(); } + + /// Removes a game session by its ID. + /// Takes a String [id] as input. It finds the index of the game session with the matching ID + /// in the `gameList`, and then calls `removeGameSessionByIndex` with that index. + int getGameSessionIndexById(String id) { + return gameList.indexWhere((session) => session.id.toString() == id); + } } final gameManager = GameManager(); From f7842404113dc1d535acd343777550512834e765 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 11:53:46 +0200 Subject: [PATCH 35/65] Refactoring --- lib/views/main_menu_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index f41a0ef..6597a68 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -131,7 +131,8 @@ class _MainMenuViewState extends State { gameTitle); }, onDismissed: (direction) { - gameManager.removeGameSession(index); + gameManager + .removeGameSessionByIndex(index); }, dismissThresholds: const { DismissDirection.startToEnd: 0.6 From f9fac719b0da671ffeefb3b6a24a10d20aad4be9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 11:58:25 +0200 Subject: [PATCH 36/65] Changed function and altered return type --- lib/data/game_manager.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index f00d73c..f29270c 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -40,8 +40,12 @@ class GameManager extends ChangeNotifier { /// Removes a game session by its ID. /// Takes a String [id] as input. It finds the index of the game session with the matching ID /// in the `gameList`, and then calls `removeGameSessionByIndex` with that index. - int getGameSessionIndexById(String id) { - return gameList.indexWhere((session) => session.id.toString() == id); + bool removeGameSessionById(String id) { + final int index = + gameList.indexWhere((session) => session.id.toString() == id); + if (index == -1) return false; + removeGameSessionByIndex(index); + return true; } } From 2c1d65c4034761f8710d38d23c99dd5179790064 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:05:28 +0200 Subject: [PATCH 37/65] Changed state handling --- lib/views/main_menu_view.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 6597a68..3a9441f 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -169,18 +169,19 @@ class _MainMenuViewState extends State { CupertinoIcons.person_2_fill), ], ), - onTap: () async { - //ignore: unused_local_variable - final val = await Navigator.push( + onTap: () { + final session = + gameManager.gameList[index]; + Navigator.push( context, CupertinoPageRoute( builder: (context) => ActiveGameView( - gameSession: gameManager - .gameList[index]), + gameSession: session), ), - ); - setState(() {}); + ).then((_) { + setState(() {}); + }); }, ), ), From d34c6fff53c480bd5f402804a6083d9271fd155a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:06:12 +0200 Subject: [PATCH 38/65] Added function gameExistsInGameList --- lib/data/game_manager.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index f29270c..1ccc9dc 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -15,14 +15,9 @@ class GameManager extends ChangeNotifier { notifyListeners(); // Propagate session changes }); gameList.add(session); - print( - '[game_manager.dart] Added game session: ${session.gameTitle} at ${session.createdAt}'); gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); - print( - '[game_manager.dart] Sorted game sessions by creation date. Total sessions: ${gameList.length}'); notifyListeners(); await LocalStorageService.saveGameSessions(); - print('[game_manager.dart] Saved game sessions to local storage.'); return gameList.indexOf(session); } @@ -40,12 +35,17 @@ class GameManager extends ChangeNotifier { /// Removes a game session by its ID. /// Takes a String [id] as input. It finds the index of the game session with the matching ID /// in the `gameList`, and then calls `removeGameSessionByIndex` with that index. - bool removeGameSessionById(String id) { + void removeGameSessionById(String id) { final int index = gameList.indexWhere((session) => session.id.toString() == id); - if (index == -1) return false; + if (index == -1) return; removeGameSessionByIndex(index); - return true; + } + + bool gameExistsInGameList(String id) { + return gameList.any((session) => session.id.toString() == id) == -1 + ? false + : true; } } From e45fc8abc886644229019cbca319d7f52edbb971 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:06:40 +0200 Subject: [PATCH 39/65] Input changed to copy instead of reference --- lib/views/create_game_view.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 6c52890..fd59529 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -320,12 +320,13 @@ class _CreateGameViewState extends State { isPointsLimitEnabled: _isPointsLimitEnabled!, ); final index = await gameManager.addGameSession(gameSession); + final session = gameManager.gameList[index]; if (context.mounted) { Navigator.pushReplacement( context, CupertinoPageRoute( - builder: (context) => ActiveGameView( - gameSession: gameManager.gameList[index]))); + builder: (context) => + ActiveGameView(gameSession: session))); } }, ), From 5389ce1d442fda7107fa6690948dc0a2a27fbd81 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:07:01 +0200 Subject: [PATCH 40/65] Implemented deletion button --- lib/main.dart | 1 + lib/views/active_game_view.dart | 123 ++++++++++++++++++++++++-------- pubspec.yaml | 3 +- 3 files changed, 96 insertions(+), 31 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 27c3835..2a2a91e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -65,6 +65,7 @@ class _AppState extends State with WidgetsBindingObserver { return supportedLocales.first; }, theme: CupertinoThemeData( + applyThemeToAll: true, brightness: Brightness.dark, primaryColor: CustomTheme.primaryColor, scaffoldBackgroundColor: CustomTheme.backgroundColor, diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index d4ff7bd..b5eafc3 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; @@ -17,15 +18,24 @@ class ActiveGameView extends StatefulWidget { } class _ActiveGameViewState extends State { + late final GameSession gameSession; + bool _deleted = false; + + @override + void initState() { + super.initState(); + gameSession = widget.gameSession; + } + @override Widget build(BuildContext context) { return ListenableBuilder( - listenable: widget.gameSession, + listenable: gameSession, builder: (context, _) { List sortedPlayerIndices = _getSortedPlayerIndices(); return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text(widget.gameSession.gameTitle), + middle: Text(gameSession.gameTitle), ), child: SafeArea( child: SingleChildScrollView( @@ -42,7 +52,7 @@ class _ActiveGameViewState extends State { ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: widget.gameSession.players.length, + itemCount: gameSession.players.length, itemBuilder: (BuildContext context, int index) { int playerIndex = sortedPlayerIndices[index]; return CupertinoListTile( @@ -51,7 +61,7 @@ class _ActiveGameViewState extends State { _getPlacementPrefix(index), const SizedBox(width: 5), Text( - widget.gameSession.players[playerIndex], + gameSession.players[playerIndex], style: const TextStyle( fontWeight: FontWeight.bold), ), @@ -60,8 +70,7 @@ class _ActiveGameViewState extends State { trailing: Row( children: [ const SizedBox(width: 5), - Text( - '${widget.gameSession.playerScores[playerIndex]} ' + Text('${gameSession.playerScores[playerIndex]} ' '${AppLocalizations.of(context).points}') ], ), @@ -78,7 +87,7 @@ class _ActiveGameViewState extends State { ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: widget.gameSession.roundNumber, + itemCount: gameSession.roundNumber, itemBuilder: (BuildContext context, int index) { return Padding( padding: const EdgeInsets.all(1), @@ -88,14 +97,13 @@ class _ActiveGameViewState extends State { title: Text( '${AppLocalizations.of(context).round} ${index + 1}', ), - trailing: index + 1 != - widget.gameSession.roundNumber || - widget.gameSession.isGameFinished == - true - ? (const Text('\u{2705}', - style: TextStyle(fontSize: 22))) - : const Text('\u{23F3}', - style: TextStyle(fontSize: 22)), + trailing: + index + 1 != gameSession.roundNumber || + gameSession.isGameFinished == true + ? (const Text('\u{2705}', + style: TextStyle(fontSize: 22))) + : const Text('\u{23F3}', + style: TextStyle(fontSize: 22)), onTap: () async { // ignore: unused_local_variable final val = await Navigator.of(context, @@ -104,7 +112,7 @@ class _ActiveGameViewState extends State { CupertinoPageRoute( fullscreenDialog: true, builder: (context) => RoundView( - gameSession: widget.gameSession, + gameSession: gameSession, roundNumber: index + 1), ), ); @@ -129,17 +137,21 @@ class _ActiveGameViewState extends State { ), onTap: () => Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( builder: (_) => GraphView( - gameSession: widget.gameSession, + gameSession: gameSession, )))), CupertinoListTile( - title: - Text(AppLocalizations.of(context).delete_game, - style: const TextStyle( - color: Colors.white30, - )), - onTap: () {}, + title: Text( + AppLocalizations.of(context).delete_game, + ), + onTap: () { + _showDeleteGameDialog().then((value) { + if (value) { + _removeGameSession(gameSession); + } + }); + }, ), CupertinoListTile( title: Text( @@ -151,12 +163,11 @@ class _ActiveGameViewState extends State { context, CupertinoPageRoute( builder: (_) => CreateGameView( - gameTitle: - widget.gameSession.gameTitle, + gameTitle: gameSession.gameTitle, isPointsLimitEnabled: widget .gameSession .isPointsLimitEnabled, - players: widget.gameSession.players, + players: gameSession.players, ))); }, ), @@ -180,11 +191,11 @@ class _ActiveGameViewState extends State { /// ascending order. List _getSortedPlayerIndices() { List playerIndices = - List.generate(widget.gameSession.players.length, (index) => index); + List.generate(gameSession.players.length, (index) => index); // Sort the indices based on the summed points playerIndices.sort((a, b) { - int scoreA = widget.gameSession.playerScores[a]; - int scoreB = widget.gameSession.playerScores[b]; + int scoreA = gameSession.playerScores[a]; + int scoreB = gameSession.playerScores[b]; return scoreA.compareTo(scoreB); }); return playerIndices; @@ -217,4 +228,56 @@ class _ActiveGameViewState extends State { ); } } + + Future _showDeleteGameDialog() async { + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).delete_game), + content: Text( + 'Möchtes du das Spiel "${gameSession.gameTitle}" wirklich löschen?'), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel), + onPressed: () => Navigator.pop(context, false), + ), + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + Navigator.pop(context, true); + }, + ), + ], + ); + }, + ) ?? + false; + } + + Future _removeGameSession(GameSession gameSession) async { + if (gameManager.gameExistsInGameList(gameSession.id)) { + Navigator.pop(context); + + WidgetsBinding.instance.addPostFrameCallback((_) { + gameManager.removeGameSessionById(gameSession.id); + }); + } else { + showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: const Text('ID Fehler'), + content: const Text( + 'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).ok), + onPressed: () => Navigator.pop(context), + ), + ], + ); + }); + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index a7c91ae..a52beee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.5+269 +version: 0.3.5+312 environment: sdk: ^3.5.4 @@ -26,6 +26,7 @@ dependencies: sdk: flutter intl: any syncfusion_flutter_charts: ^30.1.37 + uuid: ^4.5.1 dev_dependencies: flutter_test: From 6f0cd817149882377e18bc048a16ab291cb0b9a8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:08:56 +0200 Subject: [PATCH 41/65] Removed variable --- lib/views/active_game_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index b5eafc3..c44ef26 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -19,7 +19,6 @@ class ActiveGameView extends StatefulWidget { class _ActiveGameViewState extends State { late final GameSession gameSession; - bool _deleted = false; @override void initState() { From 3aab4721979e9edb4f5e9fe7dd914ff4e5b12a50 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 13:09:09 +0200 Subject: [PATCH 42/65] Shortened return --- lib/data/game_manager.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index 1ccc9dc..24c55da 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -43,9 +43,7 @@ class GameManager extends ChangeNotifier { } bool gameExistsInGameList(String id) { - return gameList.any((session) => session.id.toString() == id) == -1 - ? false - : true; + return gameList.any((session) => session.id.toString() == id); } } From 9e9693ae42e5b8869170f71b3486cff8b994c196 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:26:27 +0200 Subject: [PATCH 43/65] Updated localizations --- lib/l10n/app_de.arb | 9 ++++++--- lib/l10n/app_en.arb | 6 ++++-- lib/l10n/app_localizations.dart | 30 +++++++++++++++++++++--------- lib/l10n/app_localizations_de.dart | 17 ++++++++++++----- lib/l10n/app_localizations_en.dart | 15 +++++++++++---- pubspec.yaml | 2 +- 6 files changed, 55 insertions(+), 24 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 51147d0..c10a52a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -22,7 +22,7 @@ "empty_text_1": "Ganz schön leer hier...", "empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu", "delete_game_title": "Spiel löschen?", - "delete_game_message": "Bist du sicher, dass du die Runde {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_game_message": "Bist du sicher, dass du das Spiel {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "@delete_game_message": { "placeholders": { "gameTitle": { @@ -68,6 +68,9 @@ "delete_game": "Spiel löschen", "new_game_same_settings": "Neues Spiel mit gleichen Einstellungen", "export_game": "Spiel exportieren", + "id_error_title": "ID Fehler", + "id_error_message": "Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.", + "game_process": "Spielverlauf", @@ -80,7 +83,6 @@ "game_data": "Spieldaten", "import_data": "Daten importieren", "export_data": "Daten exportieren", - "error": "Fehler", "import_success_title": "Import erfolgreich", "import_success_message":"Die Spieldaten wurden erfolgreich importiert.", @@ -91,7 +93,8 @@ "import_generic_error_title": "Import fehlgeschlagen", "import_generic_error_message": "Der Import ist fehlgeschlagen.", - "error_export": "Datei konnte nicht exportiert werden", + "export_error_title": "Fehler", + "export_error_message": "Datei konnte nicht exportiert werden", "error_found": "Fehler gefunden?", "create_issue": "Issue erstellen", "app_version": "App-Version", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a6b1a8f..73d1c96 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -80,7 +80,8 @@ "game_data": "Game Data", "import_data": "Import Data", "export_data": "Export Data", - "error": "Error", + "id_error_title": "ID Error", + "id_error_message": "The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.", "import_success_title": "Import successful", "import_success_message":"The game data has been successfully imported.", @@ -91,7 +92,8 @@ "import_generic_error_title": "Import failed", "import_generic_error_message": "The import has failed.", - "error_export": "Could not export file", + "export_error_title": "Fehler", + "export_error_message": "Could not export file", "error_found": "Found a bug?", "create_issue": "Create Issue", "app_version": "App Version", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a4e1edb..2659d08 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -215,7 +215,7 @@ abstract class AppLocalizations { /// No description provided for @delete_game_message. /// /// In de, this message translates to: - /// **'Bist du sicher, dass du die Runde {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** + /// **'Bist du sicher, dass du das Spiel {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** String delete_game_message(String gameTitle); /// No description provided for @overview. @@ -386,6 +386,18 @@ abstract class AppLocalizations { /// **'Spiel exportieren'** String get export_game; + /// No description provided for @id_error_title. + /// + /// In de, this message translates to: + /// **'ID Fehler'** + String get id_error_title; + + /// No description provided for @id_error_message. + /// + /// In de, this message translates to: + /// **'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'** + String get id_error_message; + /// No description provided for @game_process. /// /// In de, this message translates to: @@ -446,12 +458,6 @@ abstract class AppLocalizations { /// **'Daten exportieren'** String get export_data; - /// No description provided for @error. - /// - /// In de, this message translates to: - /// **'Fehler'** - String get error; - /// No description provided for @import_success_title. /// /// In de, this message translates to: @@ -500,11 +506,17 @@ abstract class AppLocalizations { /// **'Der Import ist fehlgeschlagen.'** String get import_generic_error_message; - /// No description provided for @error_export. + /// No description provided for @export_error_title. + /// + /// In de, this message translates to: + /// **'Fehler'** + String get export_error_title; + + /// No description provided for @export_error_message. /// /// In de, this message translates to: /// **'Datei konnte nicht exportiert werden'** - String get error_export; + String get export_error_message; /// No description provided for @error_found. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index b06d454..6d5f1a0 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -68,7 +68,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String delete_game_message(String gameTitle) { - return 'Bist du sicher, dass du die Runde $gameTitle löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + return 'Bist du sicher, dass du das Spiel $gameTitle löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; } @override @@ -161,6 +161,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get export_game => 'Spiel exportieren'; + @override + String get id_error_title => 'ID Fehler'; + + @override + String get id_error_message => + 'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'; + @override String get game_process => 'Spielverlauf'; @@ -191,9 +198,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get export_data => 'Daten exportieren'; - @override - String get error => 'Fehler'; - @override String get import_success_title => 'Import erfolgreich'; @@ -222,7 +226,10 @@ class AppLocalizationsDe extends AppLocalizations { String get import_generic_error_message => 'Der Import ist fehlgeschlagen.'; @override - String get error_export => 'Datei konnte nicht exportiert werden'; + String get export_error_title => 'Fehler'; + + @override + String get export_error_message => 'Datei konnte nicht exportiert werden'; @override String get error_found => 'Fehler gefunden?'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2dfc72d..63553ff 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -158,6 +158,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_game => 'Export Game'; + @override + String get id_error_title => 'ID Error'; + + @override + String get id_error_message => + 'The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.'; + @override String get game_process => 'Spielverlauf'; @@ -188,9 +195,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_data => 'Export Data'; - @override - String get error => 'Error'; - @override String get import_success_title => 'Import successful'; @@ -219,7 +223,10 @@ class AppLocalizationsEn extends AppLocalizations { String get import_generic_error_message => 'The import has failed.'; @override - String get error_export => 'Could not export file'; + String get export_error_title => 'Fehler'; + + @override + String get export_error_message => 'Could not export file'; @override String get error_found => 'Found a bug?'; diff --git a/pubspec.yaml b/pubspec.yaml index a52beee..88e2991 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.5+312 +version: 0.3.5+317 environment: sdk: ^3.5.4 From 5c1ed94d446da1570c22063b586c6c5ed5a93251 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:26:39 +0200 Subject: [PATCH 44/65] Implemented localizations --- lib/views/active_game_view.dart | 11 ++++++----- lib/views/settings_view.dart | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index c44ef26..3f9c613 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -233,9 +233,11 @@ class _ActiveGameViewState extends State { context: context, builder: (BuildContext context) { return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).delete_game), + title: Text(AppLocalizations.of(context).delete_game_title), content: Text( - 'Möchtes du das Spiel "${gameSession.gameTitle}" wirklich löschen?'), + AppLocalizations.of(context) + .delete_game_message(gameSession.gameTitle), + ), actions: [ CupertinoDialogAction( child: Text(AppLocalizations.of(context).cancel), @@ -266,9 +268,8 @@ class _ActiveGameViewState extends State { context: context, builder: (BuildContext context) { return CupertinoAlertDialog( - title: const Text('ID Fehler'), - content: const Text( - 'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'), + title: Text(AppLocalizations.of(context).id_error_title), + content: Text(AppLocalizations.of(context).id_error_message), actions: [ CupertinoDialogAction( child: Text(AppLocalizations.of(context).ok), diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index b36347d..b9ae02c 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -145,10 +145,10 @@ class _SettingsViewState extends State { showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( - title: - Text(AppLocalizations.of(context).error), + title: Text(AppLocalizations.of(context) + .export_error_title), content: Text(AppLocalizations.of(context) - .error_export), + .export_error_message), actions: [ CupertinoDialogAction( child: From 7d6c3e5f14b5593eb91696a264147e0d003e0af0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:32:52 +0200 Subject: [PATCH 45/65] Added quotation marks to String --- lib/l10n/app_de.arb | 2 +- lib/l10n/app_en.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_de.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index c10a52a..7fa3710 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -22,7 +22,7 @@ "empty_text_1": "Ganz schön leer hier...", "empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu", "delete_game_title": "Spiel löschen?", - "delete_game_message": "Bist du sicher, dass du das Spiel {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_game_message": "Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "@delete_game_message": { "placeholders": { "gameTitle": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 73d1c96..ce99202 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -22,7 +22,7 @@ "empty_text_1": "Pretty empty here...", "empty_text_2": "Add a new round using the button in the top right corner.", "delete_game_title": "Delete game?", - "delete_game_message": "Are you sure you want to delete the game {gameTitle}? This action cannot be undone.", + "delete_game_message": "Are you sure you want to delete the game \"{gameTitle}\"? This action cannot be undone.", "@delete_game_message": { "placeholders": { "gameTitle": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2659d08..eb858f5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -215,7 +215,7 @@ abstract class AppLocalizations { /// No description provided for @delete_game_message. /// /// In de, this message translates to: - /// **'Bist du sicher, dass du das Spiel {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** + /// **'Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** String delete_game_message(String gameTitle); /// No description provided for @overview. diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6d5f1a0..f72adbb 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -68,7 +68,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String delete_game_message(String gameTitle) { - return 'Bist du sicher, dass du das Spiel $gameTitle löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; } @override From 4f2f2c8d3c5d2cfc45b815cc3af03f00e2cae06f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:33:19 +0200 Subject: [PATCH 46/65] Updated delete popup style --- lib/views/active_game_view.dart | 6 +++++- lib/views/main_menu_view.dart | 6 +++++- pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 3f9c613..1b5e546 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -244,7 +244,11 @@ class _ActiveGameViewState extends State { onPressed: () => Navigator.pop(context, false), ), CupertinoDialogAction( - child: Text(AppLocalizations.of(context).delete), + child: Text( + AppLocalizations.of(context).delete, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.red), + ), onPressed: () { Navigator.pop(context, true); }, diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 3a9441f..3281c6f 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -226,7 +226,11 @@ class _MainMenuViewState extends State { onPressed: () { Navigator.pop(context, true); }, - child: Text(AppLocalizations.of(context).delete), + child: Text( + AppLocalizations.of(context).delete, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.red), + ), ), ], ); diff --git a/pubspec.yaml b/pubspec.yaml index 88e2991..56b89bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.5+317 +version: 0.3.6+318 environment: sdk: ^3.5.4 From 1c2803eb85dcd93d2dad90eaf11dcef12e3e2b79 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:34:12 +0200 Subject: [PATCH 47/65] Deleted print --- lib/data/game_session.dart | 1 - lib/l10n/app_localizations_en.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index ada34dc..36c4c4e 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -37,7 +37,6 @@ class GameSession extends ChangeNotifier { playerScores = List.filled(players.length, 0); var uuid = const Uuid(); id = uuid.v1(); - print('GameSession created with ID: $id'); } @override diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 63553ff..27d675b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -68,7 +68,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String delete_game_message(String gameTitle) { - return 'Are you sure you want to delete the game $gameTitle? This action cannot be undone.'; + return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.'; } @override From 69b213ffe3f2cc436faa6b8a123bb26e93849bd2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 3 Jul 2025 17:38:05 +0200 Subject: [PATCH 48/65] Updated translation --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_localizations_en.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ce99202..f8dbb2f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -92,7 +92,7 @@ "import_generic_error_title": "Import failed", "import_generic_error_message": "The import has failed.", - "export_error_title": "Fehler", + "export_error_title": "Export failed", "export_error_message": "Could not export file", "error_found": "Found a bug?", "create_issue": "Create Issue", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 27d675b..657f2ce 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -223,7 +223,7 @@ class AppLocalizationsEn extends AppLocalizations { String get import_generic_error_message => 'The import has failed.'; @override - String get export_error_title => 'Fehler'; + String get export_error_title => 'Export failed'; @override String get export_error_message => 'Could not export file'; From 181a135c07bbd7b44970981b0eef6e4f83fe96b4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:32:25 +0200 Subject: [PATCH 49/65] Implemented delete game button --- lib/data/game_manager.dart | 18 +++++++++ lib/l10n/app_de.arb | 4 +- lib/l10n/app_en.arb | 3 ++ lib/l10n/app_localizations.dart | 18 +++++++++ lib/l10n/app_localizations_de.dart | 10 +++++ lib/l10n/app_localizations_en.dart | 10 +++++ lib/views/active_game_view.dart | 62 +++++++++++++++++++++++++++++- pubspec.yaml | 2 +- 8 files changed, 123 insertions(+), 4 deletions(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index 24c55da..ea18be7 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -42,9 +42,27 @@ class GameManager extends ChangeNotifier { removeGameSessionByIndex(index); } + /// Retrieves a game session by its ID. + /// Takes a String [id] as input. It finds the game session with the matching id bool gameExistsInGameList(String id) { return gameList.any((session) => session.id.toString() == id); } + + /// Ends a game session if its in unlimited mode. + /// Takes a String [id] as input. It finds the index of the game + /// session with the matching ID marks it as finished, + void endGame(String id) { + final int index = + gameList.indexWhere((session) => session.id.toString() == id); + + // Game session not found or not in unlimited mode + if (index == -1 || gameList[index].isPointsLimitEnabled == false) return; + + gameList[index].roundNumber--; + gameList[index].isGameFinished = true; + notifyListeners(); + LocalStorageService.saveGameSessions(); + } } final gameManager = GameManager(); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 7fa3710..d300ab0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -65,12 +65,14 @@ "next_round": "Nächste Runde", "statistics": "Statistiken", + "end_game": "Spiel beenden", "delete_game": "Spiel löschen", "new_game_same_settings": "Neues Spiel mit gleichen Einstellungen", "export_game": "Spiel exportieren", "id_error_title": "ID Fehler", "id_error_message": "Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.", - + "end_game_title": "Spiel beenden?", + "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.", "game_process": "Spielverlauf", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f8dbb2f..8b328ba 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -65,6 +65,7 @@ "next_round": "Next Round", "statistics": "Statistics", + "end_game": "End Game", "delete_game": "Delete Game", "new_game_same_settings": "New Game with same Settings", "export_game": "Export Game", @@ -82,6 +83,8 @@ "export_data": "Export Data", "id_error_title": "ID Error", "id_error_message": "The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.", + "end_game_title": "End the game?", + "end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.", "import_success_title": "Import successful", "import_success_message":"The game data has been successfully imported.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index eb858f5..02e1d3c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -368,6 +368,12 @@ abstract class AppLocalizations { /// **'Statistiken'** String get statistics; + /// No description provided for @end_game. + /// + /// In de, this message translates to: + /// **'Spiel beenden'** + String get end_game; + /// No description provided for @delete_game. /// /// In de, this message translates to: @@ -398,6 +404,18 @@ abstract class AppLocalizations { /// **'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'** String get id_error_message; + /// No description provided for @end_game_title. + /// + /// In de, this message translates to: + /// **'Spiel beenden?'** + String get end_game_title; + + /// No description provided for @end_game_message. + /// + /// In de, this message translates to: + /// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.'** + String get end_game_message; + /// No description provided for @game_process. /// /// In de, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index f72adbb..1b91ed4 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -152,6 +152,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get statistics => 'Statistiken'; + @override + String get end_game => 'Spiel beenden'; + @override String get delete_game => 'Spiel löschen'; @@ -168,6 +171,13 @@ class AppLocalizationsDe extends AppLocalizations { String get id_error_message => 'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'; + @override + String get end_game_title => 'Spiel beenden?'; + + @override + String get end_game_message => + 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.'; + @override String get game_process => 'Spielverlauf'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 657f2ce..c98dddd 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -149,6 +149,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get statistics => 'Statistics'; + @override + String get end_game => 'End Game'; + @override String get delete_game => 'Delete Game'; @@ -165,6 +168,13 @@ class AppLocalizationsEn extends AppLocalizations { String get id_error_message => 'The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.'; + @override + String get end_game_title => 'End the game?'; + + @override + String get end_game_message => + 'Do you want to end the game? The game gets marked as finished and cannot be continued.'; + @override String get game_process => 'Spielverlauf'; diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 1b5e546..dc0e078 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -129,21 +129,40 @@ class _ActiveGameViewState extends State { Column( children: [ CupertinoListTile( - backgroundColorActivated: - CustomTheme.backgroundColor, title: Text( AppLocalizations.of(context).statistics, ), + backgroundColorActivated: + CustomTheme.backgroundColor, onTap: () => Navigator.push( context, CupertinoPageRoute( builder: (_) => GraphView( gameSession: gameSession, )))), + if (!gameSession.isPointsLimitEnabled) + CupertinoListTile( + title: Text( + AppLocalizations.of(context).end_game, + style: gameSession.roundNumber > 1 && + !gameSession.isGameFinished + ? const TextStyle(color: Colors.white) + : const TextStyle(color: Colors.white30), + ), + backgroundColorActivated: + CustomTheme.backgroundColor, + onTap: () => { + gameSession.roundNumber > 1 && + !gameSession.isGameFinished + ? _showEndGameDialog() + : null + }), CupertinoListTile( title: Text( AppLocalizations.of(context).delete_game, ), + backgroundColorActivated: + CustomTheme.backgroundColor, onTap: () { _showDeleteGameDialog().then((value) { if (value) { @@ -157,6 +176,8 @@ class _ActiveGameViewState extends State { AppLocalizations.of(context) .new_game_same_settings, ), + backgroundColorActivated: + CustomTheme.backgroundColor, onTap: () { Navigator.pushReplacement( context, @@ -176,6 +197,8 @@ class _ActiveGameViewState extends State { style: const TextStyle( color: Colors.white30, )), + backgroundColorActivated: + CustomTheme.backgroundColor, ), ], ) @@ -186,6 +209,41 @@ class _ActiveGameViewState extends State { }); } + /// Shows a dialog to confirm ending the game. + /// If the user confirms, it calls the `endGame` method on the game manager + void _showEndGameDialog() { + showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).end_game_title), + content: Text(AppLocalizations.of(context).end_game_message), + actions: [ + CupertinoDialogAction( + child: Text( + AppLocalizations.of(context).end_game, + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.red), + ), + onPressed: () { + setState(() { + gameSession.isGameFinished = true; + gameSession.roundNumber--; + gameManager.endGame(gameSession.id); + }); + Navigator.pop(context); + }, + ), + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel), + onPressed: () => Navigator.pop(context), + ), + ], + ); + }, + ); + } + /// Returns a list of player indices sorted by their scores in /// ascending order. List _getSortedPlayerIndices() { diff --git a/pubspec.yaml b/pubspec.yaml index 56b89bc..e3b4a67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.6+318 +version: 0.3.6+322 environment: sdk: ^3.5.4 From aa338451130f25ce99dce315f9e3b921b16bae4a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:35:17 +0200 Subject: [PATCH 50/65] Updated version number --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e3b4a67..e9ef18c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.6+322 +version: 0.3.7+322 environment: sdk: ^3.5.4 From 003e80c6ff2af03070311764109b96989b66ab25 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:39:55 +0200 Subject: [PATCH 51/65] Updated onTap Handler --- lib/views/active_game_view.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index dc0e078..aaea85d 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -151,12 +151,12 @@ class _ActiveGameViewState extends State { ), backgroundColorActivated: CustomTheme.backgroundColor, - onTap: () => { - gameSession.roundNumber > 1 && - !gameSession.isGameFinished - ? _showEndGameDialog() - : null - }), + onTap: () { + if (gameSession.roundNumber > 1 && + !gameSession.isGameFinished) { + _showEndGameDialog(); + } + }), CupertinoListTile( title: Text( AppLocalizations.of(context).delete_game, From 554f6e243c2cd70b8f8e6328ea339bc61f2420c4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:41:26 +0200 Subject: [PATCH 52/65] Updated typo --- lib/l10n/app_de.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d300ab0..9281ac1 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -72,7 +72,7 @@ "id_error_title": "ID Fehler", "id_error_message": "Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.", "end_game_title": "Spiel beenden?", - "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.", + "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", "game_process": "Spielverlauf", From 32aa2e607062f52007c8cbcd86faa3021a29be68 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:43:32 +0200 Subject: [PATCH 53/65] Updated endGame method --- lib/data/game_manager.dart | 2 +- lib/views/active_game_view.dart | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/data/game_manager.dart b/lib/data/game_manager.dart index ea18be7..b3a1933 100644 --- a/lib/data/game_manager.dart +++ b/lib/data/game_manager.dart @@ -56,7 +56,7 @@ class GameManager extends ChangeNotifier { gameList.indexWhere((session) => session.id.toString() == id); // Game session not found or not in unlimited mode - if (index == -1 || gameList[index].isPointsLimitEnabled == false) return; + if (index == -1 || gameList[index].isPointsLimitEnabled == true) return; gameList[index].roundNumber--; gameList[index].isGameFinished = true; diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index aaea85d..586eab3 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -227,8 +227,6 @@ class _ActiveGameViewState extends State { ), onPressed: () { setState(() { - gameSession.isGameFinished = true; - gameSession.roundNumber--; gameManager.endGame(gameSession.id); }); Navigator.pop(context); From 6f340a0d3917dd94ff5fa5f1b43e48ebf072a7e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:43:42 +0200 Subject: [PATCH 54/65] Updated localization --- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_de.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 02e1d3c..c836194 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -413,7 +413,7 @@ abstract class AppLocalizations { /// No description provided for @end_game_message. /// /// In de, this message translates to: - /// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.'** + /// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'** String get end_game_message; /// No description provided for @game_process. diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 1b91ed4..5b9d841 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -176,7 +176,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get end_game_message => - 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht forgeführt werden.'; + 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'; @override String get game_process => 'Spielverlauf'; diff --git a/pubspec.yaml b/pubspec.yaml index e9ef18c..160097c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.7+322 +version: 0.3.7+323 environment: sdk: ^3.5.4 From 7a3c8b2e806a57040586c08d645a5a9271e88a48 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 22:40:24 +0200 Subject: [PATCH 55/65] Implementing single game export --- lib/services/local_storage_service.dart | 25 +++++++++++++++++++++++-- lib/views/active_game_view.dart | 18 ++++++++++-------- lib/views/settings_view.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 71dd332..074caf6 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -103,12 +103,12 @@ class LocalStorageService { } /// Opens the file picker to save a JSON file with the current game data. - static Future exportJsonFile() async { + static Future exportGameData() async { final jsonString = getJsonFile(); try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); final result = await FileSaver.instance.saveAs( - name: 'cabo_counter_data', + name: 'cabo_counter-game_data', bytes: bytes, ext: 'json', mimeType: MimeType.json, @@ -123,6 +123,27 @@ class LocalStorageService { } } + /// Opens the file picker to save a single game session as a JSON file. + static Future exportSingleGameSession(GameSession session) async { + final jsonString = json.encode(session.toJson()); + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + final result = await FileSaver.instance.saveAs( + name: 'cabo_counter-game_${session.id.substring(0, 7)}', + bytes: bytes, + ext: 'json', + mimeType: MimeType.json, + ); + print( + '[local_storage_service.dart] Die Spieldaten der Session wurden exportiert. Dateipfad: $result'); + return true; + } catch (e) { + print( + '[local_storage_service.dart] Fehler beim Exportieren der Spieldaten der Session. Exception: $e'); + return false; + } + } + /// Opens the file picker to import a JSON file and loads the game data from it. static Future importJsonFile() async { final result = await FilePicker.platform.pickFiles( diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 586eab3..2ae9665 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -1,6 +1,7 @@ import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/views/create_game_view.dart'; import 'package:cabo_counter/views/graph_view.dart'; @@ -192,14 +193,15 @@ class _ActiveGameViewState extends State { }, ), CupertinoListTile( - title: - Text(AppLocalizations.of(context).export_game, - style: const TextStyle( - color: Colors.white30, - )), - backgroundColorActivated: - CustomTheme.backgroundColor, - ), + title: Text( + AppLocalizations.of(context).export_game, + ), + backgroundColorActivated: + CustomTheme.backgroundColor, + onTap: () { + LocalStorageService.exportSingleGameSession( + widget.gameSession); + }), ], ) ], diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index b9ae02c..f98c542 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -140,7 +140,7 @@ class _SettingsViewState extends State { ), onPressed: () async { final success = - await LocalStorageService.exportJsonFile(); + await LocalStorageService.exportGameData(); if (!success && context.mounted) { showCupertinoDialog( context: context, diff --git a/pubspec.yaml b/pubspec.yaml index 160097c..b786c31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.7+323 +version: 0.3.7+325 environment: sdk: ^3.5.4 From e8210babe66a9cc1b010aeb989c3335401ba6002 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:00:53 +0200 Subject: [PATCH 56/65] Removed doubled code and merged methods --- lib/services/local_storage_service.dart | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 074caf6..338a0c3 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -20,8 +20,8 @@ enum ImportStatus { class LocalStorageService { static const String _fileName = 'game_data.json'; - /// Writes the game session list to a JSON file and returns it as string. - static String getJsonFile() { + /// Writes the game session list to a JSON file and returns it as string. + static String getGameDataAsJsonFile() { final jsonFile = gameManager.gameList.map((session) => session.toJson()).toList(); return json.encode(jsonFile); @@ -39,7 +39,7 @@ class LocalStorageService { print('[local_storage_service.dart] Versuche, Daten zu speichern...'); try { final file = await _getFilePath(); - final jsonFile = getJsonFile(); + final jsonFile = getGameDataAsJsonFile(); await file.writeAsString(jsonFile); print( '[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.'); @@ -102,19 +102,27 @@ class LocalStorageService { } } - /// Opens the file picker to save a JSON file with the current game data. - static Future exportGameData() async { - final jsonString = getJsonFile(); + /// Opens the file picker to export game data as a JSON file. + /// This method will export the given [jsonString] as a JSON file. It opens + /// the file picker with the choosen [fileName]. + static Future exportJsonData( + String jsonString, + String fileName, + ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); - final result = await FileSaver.instance.saveAs( - name: 'cabo_counter-game_data', + final path = await FileSaver.instance.saveAs( + name: fileName, bytes: bytes, ext: 'json', mimeType: MimeType.json, ); - print( - '[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $result'); + if (path == null) { + print('[local_storage_service.dart]: Export abgebrochen'); + } else { + print( + '[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $path'); + } return true; } catch (e) { print( @@ -123,43 +131,36 @@ class LocalStorageService { } } + /// Opens the file picker to export all game sessions as a JSON file. + static Future exportGameData() async { + String jsonString = getGameDataAsJsonFile(); + String fileName = 'cabo_counter-game_data'; + return exportJsonData(jsonString, fileName); + } + /// Opens the file picker to save a single game session as a JSON file. static Future exportSingleGameSession(GameSession session) async { - final jsonString = json.encode(session.toJson()); - try { - final bytes = Uint8List.fromList(utf8.encode(jsonString)); - final result = await FileSaver.instance.saveAs( - name: 'cabo_counter-game_${session.id.substring(0, 7)}', - bytes: bytes, - ext: 'json', - mimeType: MimeType.json, - ); - print( - '[local_storage_service.dart] Die Spieldaten der Session wurden exportiert. Dateipfad: $result'); - return true; - } catch (e) { - print( - '[local_storage_service.dart] Fehler beim Exportieren der Spieldaten der Session. Exception: $e'); - return false; - } + String jsonString = json.encode(session.toJson()); + String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}'; + return exportJsonData(jsonString, fileName); } /// Opens the file picker to import a JSON file and loads the game data from it. static Future importJsonFile() async { - final result = await FilePicker.platform.pickFiles( + final path = await FilePicker.platform.pickFiles( dialogTitle: 'Wähle eine Datei mit Spieldaten aus', type: FileType.custom, allowedExtensions: ['json'], ); - if (result == null) { + if (path == null) { print( '[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen'); return ImportStatus.canceled; } try { - final jsonString = await _readFileContent(result.files.single); + final jsonString = await _readFileContent(path.files.single); if (!await validateJsonSchema(jsonString)) { return ImportStatus.validationError; From c8d0c56ddf8c4109e833186c538f1536113f8ea0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:01:16 +0200 Subject: [PATCH 57/65] Added user feedback for exporting --- lib/views/active_game_view.dart | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 2ae9665..68dfeeb 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -198,9 +198,29 @@ class _ActiveGameViewState extends State { ), backgroundColorActivated: CustomTheme.backgroundColor, - onTap: () { - LocalStorageService.exportSingleGameSession( - widget.gameSession); + onTap: () async { + final success = await LocalStorageService + .exportSingleGameSession( + widget.gameSession); + if (!success && context.mounted) { + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: Text(AppLocalizations.of(context) + .export_error_title), + content: Text(AppLocalizations.of(context) + .export_error_message), + actions: [ + CupertinoDialogAction( + child: Text( + AppLocalizations.of(context).ok), + onPressed: () => + Navigator.pop(context), + ), + ], + ), + ); + } }), ], ) From ace157dc4730d0896df26155838583d2466f672b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:14:11 +0200 Subject: [PATCH 58/65] Moved config values to ConfigService --- lib/main.dart | 5 ++--- lib/services/config_service.dart | 13 ++++++++----- lib/utility/globals.dart | 2 -- lib/views/create_game_view.dart | 10 +++++----- lib/views/main_menu_view.dart | 4 ++-- lib/views/settings_view.dart | 8 ++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2a2a91e..cd3d05f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/utility/globals.dart'; import 'package:cabo_counter/views/tab_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -12,8 +11,8 @@ Future main() async { await SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); await ConfigService.initConfig(); - Globals.pointLimit = await ConfigService.getPointLimit(); - Globals.caboPenalty = await ConfigService.getCaboPenalty(); + ConfigService.pointLimit = await ConfigService.getPointLimit(); + ConfigService.caboPenalty = await ConfigService.getCaboPenalty(); runApp(const App()); } diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 1c8275a..70f6133 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -1,4 +1,3 @@ -import 'package:cabo_counter/utility/globals.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// This class handles the configuration settings for the app. @@ -7,8 +6,12 @@ import 'package:shared_preferences/shared_preferences.dart'; class ConfigService { static const String _keyPointLimit = 'pointLimit'; static const String _keyCaboPenalty = 'caboPenalty'; - static const int _defaultPointLimit = 100; // Default Value - static const int _defaultCaboPenalty = 5; // Default Value + // Actual values used in the app + static int pointLimit = 100; + static int caboPenalty = 5; + // Default values + static const int _defaultPointLimit = 100; + static const int _defaultCaboPenalty = 5; static Future initConfig() async { final prefs = await SharedPreferences.getInstance(); @@ -48,8 +51,8 @@ class ConfigService { /// Resets the configuration to default values. static Future resetConfig() async { - Globals.pointLimit = _defaultPointLimit; - Globals.caboPenalty = _defaultCaboPenalty; + ConfigService.pointLimit = _defaultPointLimit; + ConfigService.caboPenalty = _defaultCaboPenalty; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, _defaultPointLimit); await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty); diff --git a/lib/utility/globals.dart b/lib/utility/globals.dart index 54cf2d0..e11a118 100644 --- a/lib/utility/globals.dart +++ b/lib/utility/globals.dart @@ -1,5 +1,3 @@ class Globals { - static int pointLimit = 100; - static int caboPenalty = 5; static String appDevPhase = 'Beta'; } diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index fd59529..21e8d54 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -1,8 +1,8 @@ import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/utility/globals.dart'; import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/mode_selection_view.dart'; import 'package:flutter/cupertino.dart'; @@ -96,7 +96,7 @@ class _CreateGameViewState extends State { _isPointsLimitEnabled == null ? AppLocalizations.of(context).select_mode : (_isPointsLimitEnabled! - ? '${Globals.pointLimit} ${AppLocalizations.of(context).points}' + ? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}' : AppLocalizations.of(context).unlimited), ), const SizedBox(width: 3), @@ -108,7 +108,7 @@ class _CreateGameViewState extends State { context, CupertinoPageRoute( builder: (context) => ModeSelectionMenu( - pointLimit: Globals.pointLimit, + pointLimit: ConfigService.pointLimit, ), ), ); @@ -315,8 +315,8 @@ class _CreateGameViewState extends State { createdAt: DateTime.now(), gameTitle: _gameTitleTextController.text, players: players, - pointLimit: Globals.pointLimit, - caboPenalty: Globals.caboPenalty, + pointLimit: ConfigService.pointLimit, + caboPenalty: ConfigService.caboPenalty, isPointsLimitEnabled: _isPointsLimitEnabled!, ); final index = await gameManager.addGameSession(gameSession); diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 3281c6f..e087cfc 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -1,8 +1,8 @@ import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/utility/globals.dart'; import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/create_game_view.dart'; import 'package:cabo_counter/views/settings_view.dart'; @@ -199,7 +199,7 @@ class _MainMenuViewState extends State { /// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'. String _translateGameMode(bool pointLimit) { if (pointLimit) { - return '${Globals.pointLimit} ${AppLocalizations.of(context).points}'; + return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}'; } return AppLocalizations.of(context).unlimited; } diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index f98c542..2e86122 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -52,14 +52,14 @@ class _SettingsViewState extends State { AppLocalizations.of(context).cabo_penalty_subtitle), trailing: Stepper( key: _stepperKey1, - initialValue: Globals.caboPenalty, + initialValue: ConfigService.caboPenalty, minValue: 0, maxValue: 50, step: 1, onChanged: (newCaboPenalty) { setState(() { ConfigService.setCaboPenalty(newCaboPenalty); - Globals.caboPenalty = newCaboPenalty; + ConfigService.caboPenalty = newCaboPenalty; }); }, ), @@ -73,14 +73,14 @@ class _SettingsViewState extends State { Text(AppLocalizations.of(context).point_limit_subtitle), trailing: Stepper( key: _stepperKey2, - initialValue: Globals.pointLimit, + initialValue: ConfigService.pointLimit, minValue: 30, maxValue: 1000, step: 10, onChanged: (newPointLimit) { setState(() { ConfigService.setPointLimit(newPointLimit); - Globals.pointLimit = newPointLimit; + ConfigService.pointLimit = newPointLimit; }); }, ), From 751f490c44df32065c5cf8f57acf348b08028df9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:18:15 +0200 Subject: [PATCH 59/65] Renamed current json scheme --- assets/{schema.json => game_list-schema.json} | 0 lib/services/local_storage_service.dart | 3 ++- pubspec.yaml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename assets/{schema.json => game_list-schema.json} (100%) diff --git a/assets/schema.json b/assets/game_list-schema.json similarity index 100% rename from assets/schema.json rename to assets/game_list-schema.json diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 338a0c3..e145571 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -196,7 +196,8 @@ class LocalStorageService { /// Validates the JSON data against the schema. static Future validateJsonSchema(String jsonString) async { try { - final schemaString = await rootBundle.loadString('assets/schema.json'); + final schemaString = + await rootBundle.loadString('assets/game_list-schema.json'); final schema = JsonSchema.create(json.decode(schemaString)); final jsonData = json.decode(jsonString); final result = schema.validate(jsonData); diff --git a/pubspec.yaml b/pubspec.yaml index b786c31..144adf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.7+325 +version: 0.3.7+326 environment: sdk: ^3.5.4 @@ -39,4 +39,4 @@ flutter: uses-material-design: false assets: - assets/cabo_counter-logo_rounded.png - - assets/schema.json + - assets/game_list-schema.json From 5bfac732af215514a976b64d8d051a35adaa56ff Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:19:30 +0200 Subject: [PATCH 60/65] Added new schema file --- assets/game-schema.json | 291 +++++++++++++++++++++++++++++++++++ assets/game_list-schema.json | 2 +- 2 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 assets/game-schema.json diff --git a/assets/game-schema.json b/assets/game-schema.json new file mode 100644 index 0000000..9991c74 --- /dev/null +++ b/assets/game-schema.json @@ -0,0 +1,291 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Generated schema for a single cabo counter game", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "gameTitle": { + "type": "string" + }, + "players": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + }, + "pointLimit": { + "type": "integer" + }, + "caboPenalty": { + "type": "integer" + }, + "isPointsLimitEnabled": { + "type": "boolean" + }, + "isGameFinished": { + "type": "boolean" + }, + "winner": { + "type": "string" + }, + "roundNumber": { + "type": "integer" + }, + "playerScores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "roundList": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "roundNum": { + "type": "integer" + }, + "caboPlayerIndex": { + "type": "integer" + }, + "kamikazePlayerIndex": { + "type": "null" + }, + "scores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "scoreUpdates": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + }, + "required": [ + "roundNum", + "caboPlayerIndex", + "kamikazePlayerIndex", + "scores", + "scoreUpdates" + ] + }, + { + "type": "object", + "properties": { + "roundNum": { + "type": "integer" + }, + "caboPlayerIndex": { + "type": "integer" + }, + "kamikazePlayerIndex": { + "type": "null" + }, + "scores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "scoreUpdates": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + }, + "required": [ + "roundNum", + "caboPlayerIndex", + "kamikazePlayerIndex", + "scores", + "scoreUpdates" + ] + }, + { + "type": "object", + "properties": { + "roundNum": { + "type": "integer" + }, + "caboPlayerIndex": { + "type": "integer" + }, + "kamikazePlayerIndex": { + "type": "null" + }, + "scores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "scoreUpdates": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + }, + "required": [ + "roundNum", + "caboPlayerIndex", + "kamikazePlayerIndex", + "scores", + "scoreUpdates" + ] + }, + { + "type": "object", + "properties": { + "roundNum": { + "type": "integer" + }, + "caboPlayerIndex": { + "type": "integer" + }, + "kamikazePlayerIndex": { + "type": "null" + }, + "scores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "scoreUpdates": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + }, + "required": [ + "roundNum", + "caboPlayerIndex", + "kamikazePlayerIndex", + "scores", + "scoreUpdates" + ] + }, + { + "type": "object", + "properties": { + "roundNum": { + "type": "integer" + }, + "caboPlayerIndex": { + "type": "integer" + }, + "kamikazePlayerIndex": { + "type": "null" + }, + "scores": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + }, + "scoreUpdates": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + }, + "required": [ + "roundNum", + "caboPlayerIndex", + "kamikazePlayerIndex", + "scores", + "scoreUpdates" + ] + } + ] + } + }, + "required": [ + "id", + "createdAt", + "gameTitle", + "players", + "pointLimit", + "caboPenalty", + "isPointsLimitEnabled", + "isGameFinished", + "winner", + "roundNumber", + "playerScores", + "roundList" + ] +} + diff --git a/assets/game_list-schema.json b/assets/game_list-schema.json index a9a07e5..26e4278 100644 --- a/assets/game_list-schema.json +++ b/assets/game_list-schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Generated schema for cabo game data", + "title": "Generated schema for the cabo counter game data", "type": "array", "items": { "type": "object", From 4a6f69ab85d9ff8fd3eb62a8363dc15f335d6ffd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:31:04 +0200 Subject: [PATCH 61/65] Implemented single game detection --- lib/services/local_storage_service.dart | 54 ++++++++++++++++++++----- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index e145571..68097da 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -70,7 +70,7 @@ class LocalStorageService { return false; } - if (!await validateJsonSchema(jsonString)) { + if (!await validateJsonSchema(jsonString, true)) { print( '[local_storage_service.dart] Die Datei konnte nicht validiert werden'); gameManager.gameList = []; @@ -161,17 +161,29 @@ class LocalStorageService { try { final jsonString = await _readFileContent(path.files.single); + final jsonData = json.decode(jsonString) as List; - if (!await validateJsonSchema(jsonString)) { + if (await validateJsonSchema(jsonString, true)) { + print( + '[local_storage_service.dart] Die Datei wurde erfolgreich als Liste validiert'); + List tempList = jsonData + .map((jsonItem) => + GameSession.fromJson(jsonItem as Map)) + .toList(); + + for (GameSession s in tempList) { + importSession(s); + } + } else if (await validateJsonSchema(jsonString, false)) { + print( + '[local_storage_service.dart] Die Datei wurde erfolgreich als einzelnes Spiel validiert'); + importSession(GameSession.fromJson(jsonData as Map)); + } else { return ImportStatus.validationError; } - final jsonData = json.decode(jsonString) as List; - gameManager.gameList = jsonData - .map((jsonItem) => - GameSession.fromJson(jsonItem as Map)) - .toList(); + print( - '[local_storage_service.dart] Die Datei wurde erfolgreich Importiertn'); + '[local_storage_service.dart] Die Datei wurde erfolgreich Importiert'); await saveGameSessions(); return ImportStatus.success; } on FormatException catch (e) { @@ -185,6 +197,18 @@ class LocalStorageService { } } + /// Imports a single game session into the gameList. + static Future importSession(GameSession session) async { + if (gameManager.gameList.any((s) => s.id == session.id)) { + print( + '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird aktualisiert.'); + gameManager.gameList.removeWhere((s) => s.id == session.id); + } + gameManager.gameList.add(session); + print( + '[local_storage_service.dart] Die Session mit der ID ${session.id} wurde erfolgreich importiert.'); + } + /// Helper method to read file content from either bytes or path static Future _readFileContent(PlatformFile file) async { if (file.bytes != null) return utf8.decode(file.bytes!); @@ -194,10 +218,18 @@ class LocalStorageService { } /// Validates the JSON data against the schema. - static Future validateJsonSchema(String jsonString) async { - try { - final schemaString = + static Future validateJsonSchema( + String jsonString, bool isGameList) async { + final String schemaString; + + if (isGameList) { + schemaString = await rootBundle.loadString('assets/game_list-schema.json'); + } else { + schemaString = await rootBundle.loadString('assets/game-schema.json'); + } + + try { final schema = JsonSchema.create(json.decode(schemaString)); final jsonData = json.decode(jsonString); final result = schema.validate(jsonData); From c279acfdebcb225156c9ea45ae4d23b82ea95df4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 7 Jul 2025 23:47:55 +0200 Subject: [PATCH 62/65] Organized LocalStorageService --- lib/services/local_storage_service.dart | 30 ++++++++++++++----------- pubspec.yaml | 3 ++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 68097da..12130a1 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -161,23 +161,23 @@ class LocalStorageService { try { final jsonString = await _readFileContent(path.files.single); - final jsonData = json.decode(jsonString) as List; if (await validateJsonSchema(jsonString, true)) { - print( - '[local_storage_service.dart] Die Datei wurde erfolgreich als Liste validiert'); - List tempList = jsonData + // Checks if the JSON String is in the gameList format + + final jsonData = json.decode(jsonString) as List; + List importedList = jsonData .map((jsonItem) => GameSession.fromJson(jsonItem as Map)) .toList(); - for (GameSession s in tempList) { + for (GameSession s in importedList) { importSession(s); } } else if (await validateJsonSchema(jsonString, false)) { - print( - '[local_storage_service.dart] Die Datei wurde erfolgreich als einzelnes Spiel validiert'); - importSession(GameSession.fromJson(jsonData as Map)); + // Checks if the JSON String is in the single game format + final jsonData = json.decode(jsonString) as Map; + importSession(GameSession.fromJson(jsonData)); } else { return ImportStatus.validationError; } @@ -199,12 +199,12 @@ class LocalStorageService { /// Imports a single game session into the gameList. static Future importSession(GameSession session) async { - if (gameManager.gameList.any((s) => s.id == session.id)) { + if (gameManager.gameExistsInGameList(session.id)) { print( - '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird aktualisiert.'); - gameManager.gameList.removeWhere((s) => s.id == session.id); + '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.'); + gameManager.removeGameSessionById(session.id); } - gameManager.gameList.add(session); + gameManager.addGameSession(session); print( '[local_storage_service.dart] Die Session mit der ID ${session.id} wurde erfolgreich importiert.'); } @@ -218,6 +218,9 @@ class LocalStorageService { } /// Validates the JSON data against the schema. + /// This method checks if the provided [jsonString] is valid against the + /// JSON schema. It takes a boolean [isGameList] to determine + /// which schema to use (game list or single game). static Future validateJsonSchema( String jsonString, bool isGameList) async { final String schemaString; @@ -235,7 +238,8 @@ class LocalStorageService { final result = schema.validate(jsonData); if (result.isValid) { - print('[local_storage_service.dart] JSON ist erfolgreich validiert.'); + print( + '[local_storage_service.dart] JSON ist erfolgreich validiert. Typ: ${isGameList ? 'Game List' : 'Single Game'}'); return true; } print( diff --git a/pubspec.yaml b/pubspec.yaml index 144adf4..7ac98ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.7+326 +version: 0.3.8+327 environment: sdk: ^3.5.4 @@ -40,3 +40,4 @@ flutter: assets: - assets/cabo_counter-logo_rounded.png - assets/game_list-schema.json + - assets/game-schema.json From c2f46ef6f4f08e568cffdece9ca88efbb57cb949 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 8 Jul 2025 00:11:45 +0200 Subject: [PATCH 63/65] Updated popups and refactored --- lib/views/create_game_view.dart | 141 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 74 deletions(-) diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 21e8d54..f6099f4 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -7,6 +7,14 @@ import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/mode_selection_view.dart'; import 'package:flutter/cupertino.dart'; +enum CreateStatus { + noGameTitle, + noModeSelected, + minPlayers, + maxPlayers, + noPlayerName, +} + class CreateGameView extends StatefulWidget { final String? gameTitle; final bool? isPointsLimitEnabled; @@ -163,22 +171,7 @@ class _CreateGameViewState extends State { .add(TextEditingController()); }); } else { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text(AppLocalizations.of(context) - .max_players_title), - content: Text(AppLocalizations.of(context) - .max_players_message), - actions: [ - CupertinoDialogAction( - child: - Text(AppLocalizations.of(context).ok), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); + showFeedbackDialog(CreateStatus.maxPlayers); } }, ), @@ -237,73 +230,19 @@ class _CreateGameViewState extends State { ), onPressed: () async { if (_gameTitleTextController.text == '') { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text( - AppLocalizations.of(context).no_gameTitle_title), - content: Text( - AppLocalizations.of(context).no_gameTitle_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).ok), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); + showFeedbackDialog(CreateStatus.noGameTitle); return; } if (_isPointsLimitEnabled == null) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).no_mode_title), - content: - Text(AppLocalizations.of(context).no_mode_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).ok), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); + showFeedbackDialog(CreateStatus.noModeSelected); return; } if (_playerNameTextControllers.length < 2) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text( - AppLocalizations.of(context).min_players_title), - content: Text( - AppLocalizations.of(context).min_players_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).ok), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); + showFeedbackDialog(CreateStatus.minPlayers); return; } if (!everyPlayerHasAName()) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).no_name_title), - content: - Text(AppLocalizations.of(context).no_name_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).ok), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); + showFeedbackDialog(CreateStatus.noPlayerName); return; } @@ -335,6 +274,60 @@ class _CreateGameViewState extends State { )))); } + /// Displays a feedback dialog based on the [CreateStatus]. + void showFeedbackDialog(CreateStatus status) { + final (title, message) = _getDialogContent(status); + + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text(title), + content: Text(message), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).ok), + onPressed: () => Navigator.pop(context), + ), + ], + ); + }); + } + + /// Returns the title and message for the dialog based on the [CreateStatus]. + (String, String) _getDialogContent(CreateStatus status) { + switch (status) { + case CreateStatus.noGameTitle: + return ( + AppLocalizations.of(context).no_gameTitle_title, + AppLocalizations.of(context).no_gameTitle_message + ); + case CreateStatus.noModeSelected: + return ( + AppLocalizations.of(context).no_mode_title, + AppLocalizations.of(context).no_mode_message + ); + + case CreateStatus.minPlayers: + return ( + AppLocalizations.of(context).min_players_title, + AppLocalizations.of(context).min_players_message + ); + case CreateStatus.maxPlayers: + return ( + AppLocalizations.of(context).max_players_title, + AppLocalizations.of(context).max_players_message + ); + case CreateStatus.noPlayerName: + return ( + AppLocalizations.of(context).no_name_title, + AppLocalizations.of(context).no_name_message + ); + } + } + + /// Checks if every player has a name. + /// Returns true if all players have a name, false otherwise. bool everyPlayerHasAName() { for (var controller in _playerNameTextControllers) { if (controller.text == '') { From 1683a8464b2cff6403ba388fa8300efbeda1624e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 8 Jul 2025 21:41:38 +0200 Subject: [PATCH 64/65] Implemented openRoundView method --- lib/views/active_game_view.dart | 33 ++++++++++++++++++++++----------- lib/views/round_view.dart | 15 ++++----------- pubspec.yaml | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 68dfeeb..5945a3e 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -105,17 +105,7 @@ class _ActiveGameViewState extends State { : const Text('\u{23F3}', style: TextStyle(fontSize: 22)), onTap: () async { - // ignore: unused_local_variable - final val = await Navigator.of(context, - rootNavigator: true) - .push( - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => RoundView( - gameSession: gameSession, - roundNumber: index + 1), - ), - ); + _openRoundView(index + 1); }, )); }, @@ -362,4 +352,25 @@ class _ActiveGameViewState extends State { }); } } + + /// Recursively opens the RoundView for the specified round number. + /// It starts with the given [roundNumber] and continues to open the next round + /// until the user navigates back or the round number is invalid. + void _openRoundView(int roundNumber) async { + final val = await Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => RoundView( + gameSession: gameSession, + roundNumber: roundNumber, + ), + ), + ); + if (val != null && val >= 0) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.delayed(const Duration(milliseconds: 600)); + _openRoundView(val); + }); + } + } } diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index c40db1b..beb6577 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -290,7 +290,7 @@ class _RoundViewState extends State { ? () { _finishRound(); LocalStorageService.saveGameSessions(); - Navigator.pop(context, widget.gameSession); + Navigator.pop(context, -1); } : null, child: Text(AppLocalizations.of(context).done), @@ -301,17 +301,10 @@ class _RoundViewState extends State { _finishRound(); LocalStorageService.saveGameSessions(); if (widget.gameSession.isGameFinished == true) { - Navigator.pop(context, widget.gameSession); + Navigator.pop(context, -1); } else { - Navigator.of(context, rootNavigator: true) - .pushReplacement( - CupertinoPageRoute( - builder: (context) => RoundView( - gameSession: widget.gameSession, - roundNumber: widget.roundNumber + 1, - ), - ), - ); + Navigator.pop( + context, widget.roundNumber + 1); } } : null, diff --git a/pubspec.yaml b/pubspec.yaml index 7ac98ec..006a846 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.8+327 +version: 0.3.9+330 environment: sdk: ^3.5.4 From 75e5512f35c292c187ba523da8a7575028255756 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 8 Jul 2025 21:52:51 +0200 Subject: [PATCH 65/65] Updated pop return type in roundView --- lib/views/round_view.dart | 10 ++++------ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index beb6577..4e114fe 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -76,10 +76,8 @@ class _RoundViewState extends State { middle: Text(AppLocalizations.of(context).results), leading: CupertinoButton( padding: EdgeInsets.zero, - onPressed: () => { - LocalStorageService.saveGameSessions(), - Navigator.pop(context, widget.gameSession) - }, + onPressed: () => + {LocalStorageService.saveGameSessions(), Navigator.pop(context)}, child: Text(AppLocalizations.of(context).cancel), ), ), @@ -290,7 +288,7 @@ class _RoundViewState extends State { ? () { _finishRound(); LocalStorageService.saveGameSessions(); - Navigator.pop(context, -1); + Navigator.pop(context); } : null, child: Text(AppLocalizations.of(context).done), @@ -301,7 +299,7 @@ class _RoundViewState extends State { _finishRound(); LocalStorageService.saveGameSessions(); if (widget.gameSession.isGameFinished == true) { - Navigator.pop(context, -1); + Navigator.pop(context); } else { Navigator.pop( context, widget.roundNumber + 1); diff --git a/pubspec.yaml b/pubspec.yaml index 006a846..562b189 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.3.9+330 +version: 0.3.9+331 environment: sdk: ^3.5.4