From 181a135c07bbd7b44970981b0eef6e4f83fe96b4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 6 Jul 2025 00:32:25 +0200 Subject: [PATCH 001/182] 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 002/182] 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 003/182] 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 004/182] 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 005/182] 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 006/182] 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 007/182] 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 008/182] 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 009/182] 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 010/182] 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 011/182] 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 012/182] 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 013/182] 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 014/182] 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 015/182] 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 016/182] 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 017/182] 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 From 0a316035c065ac7c9d249f7b537fa227b8aae347 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 8 Jul 2025 22:28:46 +0200 Subject: [PATCH 018/182] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5e187d..20b0cae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CABO Counter -![Version](https://img.shields.io/badge/Version-0.3.0-orange) +![Version](https://img.shields.io/badge/Version-0.3.8-orange) ![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) From a3003047ae4ebe81eaa06bbae54e917f258ec664 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 8 Jul 2025 22:28:59 +0200 Subject: [PATCH 019/182] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5e187d..20b0cae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CABO Counter -![Version](https://img.shields.io/badge/Version-0.3.0-orange) +![Version](https://img.shields.io/badge/Version-0.3.8-orange) ![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) From 34207997f62db9560a9a1570b2278601f836edc8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 14:15:36 +0200 Subject: [PATCH 020/182] Tried new design for im- and export-button --- lib/views/settings_view.dart | 39 ++++++++++++++++++++++++++++++++++-- pubspec.yaml | 2 +- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 2e86122..8185d8a 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -107,7 +107,7 @@ class _SettingsViewState extends State { style: CustomTheme.rowTitle, ), ), - Padding( + /*Padding( padding: const EdgeInsets.only(top: 30), child: Center( heightFactor: 1, @@ -163,7 +163,42 @@ class _SettingsViewState extends State { ), ], )), - ) + ),*/ + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 0), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CupertinoFormRow( + prefix: Row( + children: [ + Icon( + CupertinoIcons.square_arrow_up, + color: CustomTheme.primaryColor, + ), + const SizedBox(width: 10), + const Text('Spieldaten exportieren'), + ], + ), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 15), + child: const CupertinoListTileChevron()), + CupertinoFormRow( + prefix: Row( + children: [ + Icon( + CupertinoIcons.square_arrow_down, + color: CustomTheme.primaryColor, + ), + const SizedBox(width: 10), + const Text('Spieldaten importieren'), + ], + ), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 15), + child: const CupertinoListTileChevron()) + ])), ], ), Positioned( diff --git a/pubspec.yaml b/pubspec.yaml index 562b189..9b8aaa4 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+331 +version: 0.3.9+333 environment: sdk: ^3.5.4 From 1bae1be93b7d6e5957f4c6502210537ea596baea Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 14:17:54 +0200 Subject: [PATCH 021/182] Moved views to presentation folder --- lib/main.dart | 2 +- lib/{ => presentation}/views/active_game_view.dart | 6 +++--- lib/{ => presentation}/views/create_game_view.dart | 4 ++-- lib/{ => presentation}/views/graph_view.dart | 0 lib/{ => presentation}/views/information_view.dart | 0 lib/{ => presentation}/views/main_menu_view.dart | 6 +++--- lib/{ => presentation}/views/mode_selection_view.dart | 0 lib/{ => presentation}/views/round_view.dart | 0 lib/{ => presentation}/views/settings_view.dart | 0 lib/{ => presentation}/views/tab_view.dart | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) rename lib/{ => presentation}/views/active_game_view.dart (98%) rename lib/{ => presentation}/views/create_game_view.dart (98%) rename lib/{ => presentation}/views/graph_view.dart (100%) rename lib/{ => presentation}/views/information_view.dart (100%) rename lib/{ => presentation}/views/main_menu_view.dart (98%) rename lib/{ => presentation}/views/mode_selection_view.dart (100%) rename lib/{ => presentation}/views/round_view.dart (100%) rename lib/{ => presentation}/views/settings_view.dart (100%) rename lib/{ => presentation}/views/tab_view.dart (90%) diff --git a/lib/main.dart b/lib/main.dart index cd3d05f..06e2d2b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/tab_view.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/views/tab_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; diff --git a/lib/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart similarity index 98% rename from lib/views/active_game_view.dart rename to lib/presentation/views/active_game_view.dart index 5945a3e..ddc0299 100644 --- a/lib/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -1,11 +1,11 @@ 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/presentation/views/create_game_view.dart'; +import 'package:cabo_counter/presentation/views/graph_view.dart'; +import 'package:cabo_counter/presentation/views/round_view.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'; -import 'package:cabo_counter/views/round_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart similarity index 98% rename from lib/views/create_game_view.dart rename to lib/presentation/views/create_game_view.dart index f6099f4..2c04e74 100644 --- a/lib/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -1,10 +1,10 @@ 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/presentation/views/active_game_view.dart'; +import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -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 { diff --git a/lib/views/graph_view.dart b/lib/presentation/views/graph_view.dart similarity index 100% rename from lib/views/graph_view.dart rename to lib/presentation/views/graph_view.dart diff --git a/lib/views/information_view.dart b/lib/presentation/views/information_view.dart similarity index 100% rename from lib/views/information_view.dart rename to lib/presentation/views/information_view.dart diff --git a/lib/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart similarity index 98% rename from lib/views/main_menu_view.dart rename to lib/presentation/views/main_menu_view.dart index e087cfc..230c4de 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,11 +1,11 @@ import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/active_game_view.dart'; +import 'package:cabo_counter/presentation/views/create_game_view.dart'; +import 'package:cabo_counter/presentation/views/settings_view.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/views/active_game_view.dart'; -import 'package:cabo_counter/views/create_game_view.dart'; -import 'package:cabo_counter/views/settings_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart similarity index 100% rename from lib/views/mode_selection_view.dart rename to lib/presentation/views/mode_selection_view.dart diff --git a/lib/views/round_view.dart b/lib/presentation/views/round_view.dart similarity index 100% rename from lib/views/round_view.dart rename to lib/presentation/views/round_view.dart diff --git a/lib/views/settings_view.dart b/lib/presentation/views/settings_view.dart similarity index 100% rename from lib/views/settings_view.dart rename to lib/presentation/views/settings_view.dart diff --git a/lib/views/tab_view.dart b/lib/presentation/views/tab_view.dart similarity index 90% rename from lib/views/tab_view.dart rename to lib/presentation/views/tab_view.dart index 4abd411..efa4311 100644 --- a/lib/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,7 +1,7 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/information_view.dart'; +import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/views/information_view.dart'; -import 'package:cabo_counter/views/main_menu_view.dart'; import 'package:flutter/cupertino.dart'; class TabView extends StatefulWidget { From ed8aced8a870426bdb50d5528c38912f33f450bb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 14:18:28 +0200 Subject: [PATCH 022/182] Moved widgets to presentation folder --- lib/presentation/views/settings_view.dart | 2 +- lib/{ => presentation}/widgets/stepper.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{ => presentation}/widgets/stepper.dart (100%) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 8185d8a..7875890 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,9 +1,9 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/presentation/widgets/stepper.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/widgets/stepper.dart'; import 'package:flutter/cupertino.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/widgets/stepper.dart b/lib/presentation/widgets/stepper.dart similarity index 100% rename from lib/widgets/stepper.dart rename to lib/presentation/widgets/stepper.dart From 0c774366594d944e2250fd35ae3585fd1a04ec29 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 15:20:05 +0200 Subject: [PATCH 023/182] Implemented CustomRowForm Widget --- lib/presentation/widgets/custom_form_row.dart | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/presentation/widgets/custom_form_row.dart diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart new file mode 100644 index 0000000..61d0fdb --- /dev/null +++ b/lib/presentation/widgets/custom_form_row.dart @@ -0,0 +1,49 @@ +import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:flutter/cupertino.dart'; + +class CustomFormRow extends StatefulWidget { + final String prefixText; + final IconData prefixIcon; + final Widget? suffixWidget; + final void Function()? onTap; + const CustomFormRow({ + super.key, + required this.prefixText, + required this.prefixIcon, + required this.onTap, + this.suffixWidget, + }); + + @override + State createState() => _CustomFormRowState(); +} + +class _CustomFormRowState extends State { + late Widget suffixWidget; + @override + void initState() { + super.initState(); + suffixWidget = widget.suffixWidget ?? const SizedBox.shrink(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: CupertinoFormRow( + prefix: Row( + children: [ + Icon( + widget.prefixIcon, + color: CustomTheme.primaryColor, + ), + const SizedBox(width: 10), + Text(widget.prefixText), + ], + ), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + child: suffixWidget, + ), + ); + } +} From 7fa95f4bcaf7c27438955dd413b37cc366de4c09 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 15:20:28 +0200 Subject: [PATCH 024/182] Used new custom form row --- lib/presentation/views/settings_view.dart | 106 +++++----------------- pubspec.yaml | 2 +- 2 files changed, 22 insertions(+), 86 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 7875890..4c44e1b 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,10 +1,12 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/stepper.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:flutter/cupertino.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -107,97 +109,31 @@ class _SettingsViewState extends State { style: CustomTheme.rowTitle, ), ), - /*Padding( - padding: const EdgeInsets.only(top: 30), - child: Center( - heightFactor: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CupertinoButton( - color: CustomTheme.primaryColor, - sizeStyle: CupertinoButtonSize.medium, - child: Text( - AppLocalizations.of(context).import_data, - style: - TextStyle(color: CustomTheme.backgroundColor), - ), - onPressed: () async { - final success = - await LocalStorageService.importJsonFile(); - showFeedbackDialog(success); - }), - const SizedBox( - width: 20, - ), - CupertinoButton( - color: CustomTheme.primaryColor, - sizeStyle: CupertinoButtonSize.medium, - child: Text( - AppLocalizations.of(context).export_data, - style: - TextStyle(color: CustomTheme.backgroundColor), - ), - onPressed: () async { - final success = - await LocalStorageService.exportGameData(); - 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), - ), - ], - ), - ); - } - }, - ), - ], - )), - ),*/ Padding( padding: const EdgeInsets.fromLTRB(10, 15, 10, 0), child: CupertinoFormSection.insetGrouped( backgroundColor: CustomTheme.backgroundColor, margin: EdgeInsets.zero, children: [ - CupertinoFormRow( - prefix: Row( - children: [ - Icon( - CupertinoIcons.square_arrow_up, - color: CustomTheme.primaryColor, - ), - const SizedBox(width: 10), - const Text('Spieldaten exportieren'), - ], - ), - padding: const EdgeInsets.symmetric( - vertical: 10, horizontal: 15), - child: const CupertinoListTileChevron()), - CupertinoFormRow( - prefix: Row( - children: [ - Icon( - CupertinoIcons.square_arrow_down, - color: CustomTheme.primaryColor, - ), - const SizedBox(width: 10), - const Text('Spieldaten importieren'), - ], - ), - padding: const EdgeInsets.symmetric( - vertical: 10, horizontal: 15), - child: const CupertinoListTileChevron()) + CustomFormRow( + prefixText: 'Spieldaten importieren', + prefixIcon: CupertinoIcons.square_arrow_down, + onTap: () {}, + suffixWidget: CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: 'Spieldaten exportieren', + prefixIcon: CupertinoIcons.square_arrow_up, + onTap: () {}, + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).create_issue, + prefixIcon: FontAwesomeIcons.github, + onTap: () => launchUrl(Uri.parse( + 'https://github.com/flixcoo/Cabo-Counter/issues')), + suffixWidget: const CupertinoListTileChevron(), + ), ])), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 9b8aaa4..2bae78c 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+333 +version: 0.3.9+337 environment: sdk: ^3.5.4 From 2d34ebf35b71d52b9f1702e70668da5cc6aba869 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 15:21:46 +0200 Subject: [PATCH 025/182] Removed double information --- lib/presentation/views/settings_view.dart | 60 +++++++++-------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 4c44e1b..baaf24e 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -141,44 +141,28 @@ class _SettingsViewState extends State { bottom: 30, left: 0, right: 0, - child: Column( - children: [ - Center( - child: Text(AppLocalizations.of(context).error_found), - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 30), - child: Center( - child: CupertinoButton( - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/issues')), - child: Text(AppLocalizations.of(context).create_issue), - ), - ), - ), - FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - '${Globals.appDevPhase} ${snapshot.data!.version} ' - '(${AppLocalizations.of(context).build} ${snapshot.data!.buildNumber})', - textAlign: TextAlign.center, - ); - } else if (snapshot.hasError) { - return Text( - '${AppLocalizations.of(context).app_version} -.-.- (${AppLocalizations.of(context).build} -)', - textAlign: TextAlign.center, - ); - } - return Text( - AppLocalizations.of(context).load_version, - textAlign: TextAlign.center, - ); - }, - ) - ], - )), + child: Center( + child: FutureBuilder( + future: _getPackageInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + '${Globals.appDevPhase} ${snapshot.data!.version} ' + '(${AppLocalizations.of(context).build} ${snapshot.data!.buildNumber})', + textAlign: TextAlign.center, + ); + } else if (snapshot.hasError) { + return Text( + '${AppLocalizations.of(context).app_version} -.-.- (${AppLocalizations.of(context).build} -)', + textAlign: TextAlign.center, + ); + } + return Text( + AppLocalizations.of(context).load_version, + textAlign: TextAlign.center, + ); + }, + ))), ], )), ); From ee5ec4d0d99cdc3f5649c9273e3a153de560d459 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 16:19:16 +0200 Subject: [PATCH 026/182] Refactored methods to private --- lib/services/local_storage_service.dart | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 12130a1..29ac58b 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -21,7 +21,7 @@ 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 getGameDataAsJsonFile() { + 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 = getGameDataAsJsonFile(); + final jsonFile = _getGameDataAsJsonFile(); await file.writeAsString(jsonFile); print( '[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.'); @@ -70,7 +70,7 @@ class LocalStorageService { return false; } - if (!await validateJsonSchema(jsonString, true)) { + if (!await _validateJsonSchema(jsonString, true)) { print( '[local_storage_service.dart] Die Datei konnte nicht validiert werden'); gameManager.gameList = []; @@ -105,7 +105,7 @@ class LocalStorageService { /// 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( + static Future _exportJsonData( String jsonString, String fileName, ) async { @@ -133,16 +133,16 @@ class LocalStorageService { /// Opens the file picker to export all game sessions as a JSON file. static Future exportGameData() async { - String jsonString = getGameDataAsJsonFile(); + String jsonString = _getGameDataAsJsonFile(); String fileName = 'cabo_counter-game_data'; - return exportJsonData(jsonString, fileName); + return _exportJsonData(jsonString, fileName); } /// Opens the file picker to save a single game session as a JSON file. static Future exportSingleGameSession(GameSession session) async { String jsonString = json.encode(session.toJson()); String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}'; - return exportJsonData(jsonString, fileName); + return _exportJsonData(jsonString, fileName); } /// Opens the file picker to import a JSON file and loads the game data from it. @@ -162,7 +162,7 @@ class LocalStorageService { try { final jsonString = await _readFileContent(path.files.single); - if (await validateJsonSchema(jsonString, true)) { + if (await _validateJsonSchema(jsonString, true)) { // Checks if the JSON String is in the gameList format final jsonData = json.decode(jsonString) as List; @@ -172,12 +172,12 @@ class LocalStorageService { .toList(); for (GameSession s in importedList) { - importSession(s); + _importSession(s); } - } else if (await validateJsonSchema(jsonString, false)) { + } else if (await _validateJsonSchema(jsonString, false)) { // Checks if the JSON String is in the single game format final jsonData = json.decode(jsonString) as Map; - importSession(GameSession.fromJson(jsonData)); + _importSession(GameSession.fromJson(jsonData)); } else { return ImportStatus.validationError; } @@ -198,7 +198,7 @@ class LocalStorageService { } /// Imports a single game session into the gameList. - static Future importSession(GameSession session) async { + static Future _importSession(GameSession session) async { if (gameManager.gameExistsInGameList(session.id)) { print( '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.'); @@ -221,7 +221,7 @@ class LocalStorageService { /// 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( + static Future _validateJsonSchema( String jsonString, bool isGameList) async { final String schemaString; From 249609276411d0399e57ca44a5c9c2e2ae1dde9f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 16:21:14 +0200 Subject: [PATCH 027/182] Changed label --- lib/l10n/app_de.arb | 6 +++--- lib/l10n/app_en.arb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9281ac1..632988c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -82,9 +82,9 @@ "point_limit": "Punkte-Limit", "point_limit_subtitle": "... hier ist Schluss", "reset_to_default": "Auf Standard zurücksetzen", - "game_data": "Spieldaten", - "import_data": "Daten importieren", - "export_data": "Daten exportieren", + "app": "App", + "import_data": "Spieldaten importieren", + "export_data": "Spieldaten exportieren", "import_success_title": "Import erfolgreich", "import_success_message":"Die Spieldaten wurden erfolgreich importiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8b328ba..1948270 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -78,7 +78,7 @@ "point_limit": "Point Limit", "point_limit_subtitle": "... the game ends here.", "reset_to_default": "Reset to Default", - "game_data": "Game Data", + "app": "App", "import_data": "Import Data", "export_data": "Export Data", "id_error_title": "ID Error", From 847e3dcd19b834e86d25aa6378fa064fed0a6bb2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 16:21:32 +0200 Subject: [PATCH 028/182] Modified paddings and text color --- lib/presentation/widgets/stepper.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/presentation/widgets/stepper.dart b/lib/presentation/widgets/stepper.dart index 879235e..8ca2635 100644 --- a/lib/presentation/widgets/stepper.dart +++ b/lib/presentation/widgets/stepper.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; // Für iOS-Style class Stepper extends StatefulWidget { @@ -34,18 +35,20 @@ class _StepperState extends State { Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, children: [ CupertinoButton( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.zero, onPressed: _decrement, child: const Icon(CupertinoIcons.minus), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Text('$_value', style: const TextStyle(fontSize: 18)), + child: Text('$_value', + style: TextStyle(fontSize: 18, color: CustomTheme.white)), ), CupertinoButton( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.zero, onPressed: _increment, child: const Icon(CupertinoIcons.add), ), From 21920d2ac4807e6f40ce4842bb024e74e0741b5f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:14:20 +0200 Subject: [PATCH 029/182] Changed string --- lib/l10n/app_de.arb | 5 +++-- lib/l10n/app_en.arb | 13 +++++++------ lib/l10n/app_localizations.dart | 22 ++++++++++++++-------- lib/l10n/app_localizations_de.dart | 11 +++++++---- lib/l10n/app_localizations_en.dart | 7 +++++-- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 632988c..b83c646 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -82,9 +82,10 @@ "point_limit": "Punkte-Limit", "point_limit_subtitle": "... hier ist Schluss", "reset_to_default": "Auf Standard zurücksetzen", - "app": "App", + "data": "Daten", "import_data": "Spieldaten importieren", "export_data": "Spieldaten exportieren", + "app": "App", "import_success_title": "Import erfolgreich", "import_success_message":"Die Spieldaten wurden erfolgreich importiert.", @@ -101,7 +102,7 @@ "create_issue": "Issue erstellen", "app_version": "App-Version", "build": "Build", - "load_version": "Lade Version...", + "loading": "Lädt...", "about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! " } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1948270..1c111e2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -71,6 +71,10 @@ "export_game": "Export Game", "game_process": "Spielverlauf", + "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.", "settings": "Settings", "cabo_penalty": "Cabo Penalty", @@ -78,13 +82,10 @@ "point_limit": "Point Limit", "point_limit_subtitle": "... the game ends here.", "reset_to_default": "Reset to Default", - "app": "App", + "data": "Data", "import_data": "Import Data", "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.", + "app": "App", "import_success_title": "Import successful", "import_success_message":"The game data has been successfully imported.", @@ -100,7 +101,7 @@ "error_found": "Found a bug?", "create_issue": "Create Issue", "app_version": "App Version", - "load_version": "Loading version...", + "loading": "Loading...", "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 c836194..84295df 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -458,24 +458,30 @@ abstract class AppLocalizations { /// **'Auf Standard zurücksetzen'** String get reset_to_default; - /// No description provided for @game_data. + /// No description provided for @data. /// /// In de, this message translates to: - /// **'Spieldaten'** - String get game_data; + /// **'Daten'** + String get data; /// No description provided for @import_data. /// /// In de, this message translates to: - /// **'Daten importieren'** + /// **'Spieldaten importieren'** String get import_data; /// No description provided for @export_data. /// /// In de, this message translates to: - /// **'Daten exportieren'** + /// **'Spieldaten exportieren'** String get export_data; + /// No description provided for @app. + /// + /// In de, this message translates to: + /// **'App'** + String get app; + /// No description provided for @import_success_title. /// /// In de, this message translates to: @@ -560,11 +566,11 @@ abstract class AppLocalizations { /// **'Build'** String get build; - /// No description provided for @load_version. + /// No description provided for @loading. /// /// In de, this message translates to: - /// **'Lade Version...'** - String get load_version; + /// **'Lädt...'** + String get loading; /// No description provided for @about_text. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 5b9d841..f4a2bbd 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -200,13 +200,16 @@ class AppLocalizationsDe extends AppLocalizations { String get reset_to_default => 'Auf Standard zurücksetzen'; @override - String get game_data => 'Spieldaten'; + String get data => 'Daten'; @override - String get import_data => 'Daten importieren'; + String get import_data => 'Spieldaten importieren'; @override - String get export_data => 'Daten exportieren'; + String get export_data => 'Spieldaten exportieren'; + + @override + String get app => 'App'; @override String get import_success_title => 'Import erfolgreich'; @@ -254,7 +257,7 @@ class AppLocalizationsDe extends AppLocalizations { String get build => 'Build'; @override - String get load_version => 'Lade Version...'; + String get loading => 'Lädt...'; @override String get about_text => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c98dddd..aac55f2 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -197,7 +197,7 @@ class AppLocalizationsEn extends AppLocalizations { String get reset_to_default => 'Reset to Default'; @override - String get game_data => 'Game Data'; + String get data => 'Data'; @override String get import_data => 'Import Data'; @@ -205,6 +205,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_data => 'Export Data'; + @override + String get app => 'App'; + @override String get import_success_title => 'Import successful'; @@ -251,7 +254,7 @@ class AppLocalizationsEn extends AppLocalizations { String get build => 'Build'; @override - String get load_version => 'Loading version...'; + String get loading => 'Loading...'; @override String get about_text => From 9bc80a8cd94e827576bc726d506b9319b5ba1220 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:15:00 +0200 Subject: [PATCH 030/182] Updated CustomFormRow padding and pressed handler --- lib/presentation/widgets/custom_form_row.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart index 61d0fdb..f03b453 100644 --- a/lib/presentation/widgets/custom_form_row.dart +++ b/lib/presentation/widgets/custom_form_row.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; @@ -5,12 +6,12 @@ class CustomFormRow extends StatefulWidget { final String prefixText; final IconData prefixIcon; final Widget? suffixWidget; - final void Function()? onTap; + final void Function()? onPressed; const CustomFormRow({ super.key, required this.prefixText, required this.prefixIcon, - required this.onTap, + this.onPressed, this.suffixWidget, }); @@ -28,8 +29,9 @@ class _CustomFormRowState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: widget.onTap, + return CupertinoButton( + padding: EdgeInsets.zero, + onPressed: widget.onPressed, child: CupertinoFormRow( prefix: Row( children: [ @@ -41,7 +43,9 @@ class _CustomFormRowState extends State { Text(widget.prefixText), ], ), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + padding: suffixWidget is Stepper + ? const EdgeInsets.fromLTRB(15, 0, 0, 0) + : const EdgeInsets.symmetric(vertical: 10, horizontal: 15), child: suffixWidget, ), ); From 696ade5b9b1c60f52cda8aed76d21c708d0c6842 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:15:30 +0200 Subject: [PATCH 031/182] Implemented various new forms of CustomFormRow into SettingsView --- lib/presentation/views/settings_view.dart | 234 +++++++++++++--------- pubspec.yaml | 2 +- 2 files changed, 139 insertions(+), 97 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index baaf24e..34540a4 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -46,66 +46,89 @@ class _SettingsViewState extends State { ), ), Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoListTile( - padding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context).cabo_penalty), - subtitle: Text( - AppLocalizations.of(context).cabo_penalty_subtitle), - trailing: Stepper( - key: _stepperKey1, - initialValue: ConfigService.caboPenalty, - minValue: 0, - maxValue: 50, - step: 1, - onChanged: (newCaboPenalty) { - setState(() { - ConfigService.setCaboPenalty(newCaboPenalty); - ConfigService.caboPenalty = newCaboPenalty; - }); - }, - ), - )), - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoListTile( - padding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context).point_limit), - subtitle: - Text(AppLocalizations.of(context).point_limit_subtitle), - trailing: Stepper( - key: _stepperKey2, - initialValue: ConfigService.pointLimit, - minValue: 30, - maxValue: 1000, - step: 10, - onChanged: (newPointLimit) { - setState(() { - ConfigService.setPointLimit(newPointLimit); - ConfigService.pointLimit = newPointLimit; - }); - }, - ), - )), - Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), - child: Center( - heightFactor: 0.9, - child: CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () => setState(() { - ConfigService.resetConfig(); - _stepperKey1 = UniqueKey(); - _stepperKey2 = UniqueKey(); - }), - child: - Text(AppLocalizations.of(context).reset_to_default), - ), - )), + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + key: _stepperKey1, + prefixText: 'Cabo-Strafe', + prefixIcon: CupertinoIcons.minus_square, + suffixWidget: Stepper( + initialValue: ConfigService.caboPenalty, + minValue: 0, + maxValue: 50, + step: 1, + onChanged: (newCaboPenalty) { + setState(() { + ConfigService.setCaboPenalty(newCaboPenalty); + ConfigService.caboPenalty = newCaboPenalty; + }); + }, + ), + ), + CustomFormRow( + key: _stepperKey2, + prefixText: 'Punkte-Limit', + prefixIcon: FontAwesomeIcons.bullseye, + suffixWidget: Stepper( + initialValue: ConfigService.pointLimit, + minValue: 30, + maxValue: 1000, + step: 10, + onChanged: (newPointLimit) { + setState(() { + ConfigService.setPointLimit(newPointLimit); + ConfigService.pointLimit = newPointLimit; + }); + }, + ), + ), + CustomFormRow( + prefixText: + AppLocalizations.of(context).reset_to_default, + prefixIcon: CupertinoIcons.arrow_counterclockwise, + onPressed: () { + ConfigService.resetConfig(); + setState(() { + _stepperKey1 = UniqueKey(); + _stepperKey2 = UniqueKey(); + print('Config reset to default'); + }); + }, + ) + ])), Padding( padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( - AppLocalizations.of(context).game_data, + AppLocalizations.of(context).data, + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).import_data, + prefixIcon: CupertinoIcons.square_arrow_down, + onPressed: () => LocalStorageService.importJsonFile(), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).export_data, + prefixIcon: CupertinoIcons.square_arrow_up, + onPressed: () => LocalStorageService.importJsonFile(), + suffixWidget: const CupertinoListTileChevron(), + ), + ])), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).app, style: CustomTheme.rowTitle, ), ), @@ -115,54 +138,73 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.backgroundColor, margin: EdgeInsets.zero, children: [ - CustomFormRow( - prefixText: 'Spieldaten importieren', - prefixIcon: CupertinoIcons.square_arrow_down, - onTap: () {}, - suffixWidget: CupertinoListTileChevron(), - ), - CustomFormRow( - prefixText: 'Spieldaten exportieren', - prefixIcon: CupertinoIcons.square_arrow_up, - onTap: () {}, - suffixWidget: const CupertinoListTileChevron(), - ), CustomFormRow( prefixText: AppLocalizations.of(context).create_issue, prefixIcon: FontAwesomeIcons.github, - onTap: () => launchUrl(Uri.parse( + onPressed: () => launchUrl(Uri.parse( 'https://github.com/flixcoo/Cabo-Counter/issues')), suffixWidget: const CupertinoListTileChevron(), ), + CustomFormRow( + prefixText: 'App-Version', + prefixIcon: CupertinoIcons.info, + onPressed: null, + suffixWidget: FutureBuilder( + future: _getPackageInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + '${Globals.appDevPhase} ${snapshot.data!.version} ', + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ); + } else if (snapshot.hasError) { + return Text( + '${AppLocalizations.of(context).app_version} -.-.-', + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ); + } + return Text( + AppLocalizations.of(context).loading, + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ); + }, + )), + CustomFormRow( + prefixText: 'Build-Nr.', + prefixIcon: CupertinoIcons.info, + onPressed: null, + suffixWidget: FutureBuilder( + future: _getPackageInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + snapshot.data!.buildNumber, + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ); + } else if (snapshot.hasError) { + return Text( + '-', + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ); + } + return Text( + AppLocalizations.of(context).loading, + ); + }, + )), ])), ], ), - Positioned( - bottom: 30, - left: 0, - right: 0, - child: Center( - child: FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - '${Globals.appDevPhase} ${snapshot.data!.version} ' - '(${AppLocalizations.of(context).build} ${snapshot.data!.buildNumber})', - textAlign: TextAlign.center, - ); - } else if (snapshot.hasError) { - return Text( - '${AppLocalizations.of(context).app_version} -.-.- (${AppLocalizations.of(context).build} -)', - textAlign: TextAlign.center, - ); - } - return Text( - AppLocalizations.of(context).load_version, - textAlign: TextAlign.center, - ); - }, - ))), ], )), ); diff --git a/pubspec.yaml b/pubspec.yaml index 2bae78c..fdf9916 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+337 +version: 0.4.0+371 environment: sdk: ^3.5.4 From aba4bf9c0bd8b09a67e0f104145fa81e08d86e1a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:30:28 +0200 Subject: [PATCH 032/182] Implemented VersionService --- lib/main.dart | 2 + lib/presentation/views/settings_view.dart | 64 ++++------------------- lib/services/version_service.dart | 32 ++++++++++++ pubspec.yaml | 2 +- 4 files changed, 44 insertions(+), 56 deletions(-) create mode 100644 lib/services/version_service.dart diff --git a/lib/main.dart b/lib/main.dart index 06e2d2b..b826072 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/views/tab_view.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; +import 'package:cabo_counter/services/version_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -13,6 +14,7 @@ Future main() async { await ConfigService.initConfig(); ConfigService.pointLimit = await ConfigService.getPointLimit(); ConfigService.caboPenalty = await ConfigService.getCaboPenalty(); + await VersionService.init(); runApp(const App()); } diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 34540a4..2ed422f 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -3,11 +3,10 @@ import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; +import 'package:cabo_counter/services/version_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/utility/globals.dart'; import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; class SettingsView extends StatefulWidget { @@ -149,59 +148,18 @@ class _SettingsViewState extends State { prefixText: 'App-Version', prefixIcon: CupertinoIcons.info, onPressed: null, - suffixWidget: FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - '${Globals.appDevPhase} ${snapshot.data!.version} ', - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ); - } else if (snapshot.hasError) { - return Text( - '${AppLocalizations.of(context).app_version} -.-.-', - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ); - } - return Text( - AppLocalizations.of(context).loading, - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ); - }, - )), + suffixWidget: Text(VersionService.getVersion(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), CustomFormRow( prefixText: 'Build-Nr.', prefixIcon: CupertinoIcons.info, onPressed: null, - suffixWidget: FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - snapshot.data!.buildNumber, - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ); - } else if (snapshot.hasError) { - return Text( - '-', - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ); - } - return Text( - AppLocalizations.of(context).loading, - ); - }, - )), + suffixWidget: Text(VersionService.getBuildNumber(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), ])), ], ), @@ -210,10 +168,6 @@ class _SettingsViewState extends State { ); } - Future _getPackageInfo() async { - return await PackageInfo.fromPlatform(); - } - void showFeedbackDialog(ImportStatus status) { if (status == ImportStatus.canceled) return; final (title, message) = _getDialogContent(status); diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart new file mode 100644 index 0000000..c23d187 --- /dev/null +++ b/lib/services/version_service.dart @@ -0,0 +1,32 @@ +import 'package:cabo_counter/utility/globals.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class VersionService { + static String _version = '-.-.-'; + static String _buildNumber = '-'; + + static Future init() async { + var packageInfo = await PackageInfo.fromPlatform(); + _version = packageInfo.version; + _buildNumber = packageInfo.buildNumber; + } + + static String getVersionNumber() { + return _version; + } + + static String getVersion() { + if (_version == '-.-.-') { + return getVersionNumber(); + } + return '${Globals.appDevPhase} $_version'; + } + + static String getBuildNumber() { + return _buildNumber; + } + + static String getVersionWithBuild() { + return '$_version ($_buildNumber)'; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index fdf9916..414389d 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.4.0+371 +version: 0.4.0+376 environment: sdk: ^3.5.4 From 55f3da8052c067b86650aa91d40e787f129c1a6a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:41:54 +0200 Subject: [PATCH 033/182] Updated strings, added wiki button --- lib/l10n/app_de.arb | 6 ++++-- lib/l10n/app_en.arb | 5 +++-- lib/l10n/app_localizations.dart | 14 ++++++++++---- lib/l10n/app_localizations_de.dart | 7 +++++-- lib/l10n/app_localizations_en.dart | 7 +++++-- lib/presentation/views/settings_view.dart | 18 +++++++++++++----- pubspec.yaml | 4 ++-- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b83c646..9c88d6a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -82,7 +82,7 @@ "point_limit": "Punkte-Limit", "point_limit_subtitle": "... hier ist Schluss", "reset_to_default": "Auf Standard zurücksetzen", - "data": "Daten", + "game_data": "Spieldaten", "import_data": "Spieldaten importieren", "export_data": "Spieldaten exportieren", "app": "App", @@ -98,10 +98,12 @@ "export_error_title": "Fehler", "export_error_message": "Datei konnte nicht exportiert werden", + "error_found": "Fehler gefunden?", "create_issue": "Issue erstellen", + "wiki": "Wiki", "app_version": "App-Version", - "build": "Build", + "build": "Build-Nr.", "loading": "Lädt...", "about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! " diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1c111e2..60fc593 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -82,7 +82,7 @@ "point_limit": "Point Limit", "point_limit_subtitle": "... the game ends here.", "reset_to_default": "Reset to Default", - "data": "Data", + "game_data": "Game Data", "import_data": "Import Data", "export_data": "Export Data", "app": "App", @@ -100,9 +100,10 @@ "export_error_message": "Could not export file", "error_found": "Found a bug?", "create_issue": "Create Issue", + "wiki": "Wiki", "app_version": "App Version", "loading": "Loading...", - "build": "Build", + "build": "Build No.", "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 84295df..9f85aab 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -458,11 +458,11 @@ abstract class AppLocalizations { /// **'Auf Standard zurücksetzen'** String get reset_to_default; - /// No description provided for @data. + /// No description provided for @game_data. /// /// In de, this message translates to: - /// **'Daten'** - String get data; + /// **'Spieldaten'** + String get game_data; /// No description provided for @import_data. /// @@ -554,6 +554,12 @@ abstract class AppLocalizations { /// **'Issue erstellen'** String get create_issue; + /// No description provided for @wiki. + /// + /// In de, this message translates to: + /// **'Wiki'** + String get wiki; + /// No description provided for @app_version. /// /// In de, this message translates to: @@ -563,7 +569,7 @@ abstract class AppLocalizations { /// No description provided for @build. /// /// In de, this message translates to: - /// **'Build'** + /// **'Build-Nr.'** String get build; /// No description provided for @loading. diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index f4a2bbd..23b41ac 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -200,7 +200,7 @@ class AppLocalizationsDe extends AppLocalizations { String get reset_to_default => 'Auf Standard zurücksetzen'; @override - String get data => 'Daten'; + String get game_data => 'Spieldaten'; @override String get import_data => 'Spieldaten importieren'; @@ -250,11 +250,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_issue => 'Issue erstellen'; + @override + String get wiki => 'Wiki'; + @override String get app_version => 'App-Version'; @override - String get build => 'Build'; + String get build => 'Build-Nr.'; @override String get loading => 'Lädt...'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index aac55f2..eea3896 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -197,7 +197,7 @@ class AppLocalizationsEn extends AppLocalizations { String get reset_to_default => 'Reset to Default'; @override - String get data => 'Data'; + String get game_data => 'Game Data'; @override String get import_data => 'Import Data'; @@ -247,11 +247,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_issue => 'Create Issue'; + @override + String get wiki => 'Wiki'; + @override String get app_version => 'App Version'; @override - String get build => 'Build'; + String get build => 'Build No.'; @override String get loading => 'Loading...'; diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 2ed422f..0d32971 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -101,7 +101,7 @@ class _SettingsViewState extends State { Padding( padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( - AppLocalizations.of(context).data, + AppLocalizations.of(context).game_data, style: CustomTheme.rowTitle, ), ), @@ -145,16 +145,24 @@ class _SettingsViewState extends State { suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( - prefixText: 'App-Version', - prefixIcon: CupertinoIcons.info, + prefixText: AppLocalizations.of(context).wiki, + prefixIcon: CupertinoIcons.book, + onPressed: () => launchUrl(Uri.parse( + 'https://github.com/flixcoo/Cabo-Counter/wiki')), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: + AppLocalizations.of(context).app_version, + prefixIcon: CupertinoIcons.number, onPressed: null, suffixWidget: Text(VersionService.getVersion(), style: TextStyle( color: CustomTheme.primaryColor, ))), CustomFormRow( - prefixText: 'Build-Nr.', - prefixIcon: CupertinoIcons.info, + prefixText: AppLocalizations.of(context).build, + prefixIcon: CupertinoIcons.number, onPressed: null, suffixWidget: Text(VersionService.getBuildNumber(), style: TextStyle( diff --git a/pubspec.yaml b/pubspec.yaml index 414389d..a692f21 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: cabo_counter -description: "Mobile app for the card game Cabo" +description: "Mobile game_data for the card game Cabo" publish_to: 'none' -version: 0.4.0+376 +version: 0.4.0+378 environment: sdk: ^3.5.4 From 17072fb5844c263048f87f849a6eb308b1cb4b8e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 17:42:18 +0200 Subject: [PATCH 034/182] Corrected replaced string --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a692f21..ce04a55 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: cabo_counter -description: "Mobile game_data for the card game Cabo" +description: "Mobile app for the card game Cabo" publish_to: 'none' version: 0.4.0+378 From fd8efb2a56464d8e818bee4891cecb93b63c6a48 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 18:46:58 +0200 Subject: [PATCH 035/182] Added import dialog feedback (got lost in refactoring) --- lib/presentation/views/settings_view.dart | 6 +++++- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 0d32971..9b4f91a 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -114,7 +114,11 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: AppLocalizations.of(context).import_data, prefixIcon: CupertinoIcons.square_arrow_down, - onPressed: () => LocalStorageService.importJsonFile(), + onPressed: () async { + final status = + await LocalStorageService.importJsonFile(); + showFeedbackDialog(status); + }, suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( diff --git a/pubspec.yaml b/pubspec.yaml index ce04a55..e393fb9 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.4.0+378 +version: 0.4.0+380 environment: sdk: ^3.5.4 From 42e51a092b7436d4737b9ad45f193395820d1ba0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 18:49:13 +0200 Subject: [PATCH 036/182] Corrected function duplication --- lib/l10n/app_de.arb | 2 +- lib/l10n/app_en.arb | 1 + lib/presentation/views/settings_view.dart | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9c88d6a..0a76696 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -93,7 +93,7 @@ "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_title": "Import fehlgeschlagen", "import_generic_error_message": "Der Import ist fehlgeschlagen.", "export_error_title": "Fehler", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 60fc593..edca935 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -98,6 +98,7 @@ "export_error_title": "Export failed", "export_error_message": "Could not export file", + "error_found": "Found a bug?", "create_issue": "Create Issue", "wiki": "Wiki", diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 9b4f91a..7c1df2e 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -124,7 +124,7 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: AppLocalizations.of(context).export_data, prefixIcon: CupertinoIcons.square_arrow_up, - onPressed: () => LocalStorageService.importJsonFile(), + onPressed: () => LocalStorageService.exportGameData(), suffixWidget: const CupertinoListTileChevron(), ), ])), From 74a27aea24b6fda2979bb72db6c55e9aa4d425c9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 18:51:34 +0200 Subject: [PATCH 037/182] changed suffixWidget assignment and moved stepperKeys --- lib/presentation/views/settings_view.dart | 4 ++-- lib/presentation/widgets/custom_form_row.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 7c1df2e..72365ea 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -51,10 +51,10 @@ class _SettingsViewState extends State { margin: EdgeInsets.zero, children: [ CustomFormRow( - key: _stepperKey1, prefixText: 'Cabo-Strafe', prefixIcon: CupertinoIcons.minus_square, suffixWidget: Stepper( + key: _stepperKey1, initialValue: ConfigService.caboPenalty, minValue: 0, maxValue: 50, @@ -68,10 +68,10 @@ class _SettingsViewState extends State { ), ), CustomFormRow( - key: _stepperKey2, prefixText: 'Punkte-Limit', prefixIcon: FontAwesomeIcons.bullseye, suffixWidget: Stepper( + key: _stepperKey2, initialValue: ConfigService.pointLimit, minValue: 30, maxValue: 1000, diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart index f03b453..7a266ae 100644 --- a/lib/presentation/widgets/custom_form_row.dart +++ b/lib/presentation/widgets/custom_form_row.dart @@ -24,11 +24,11 @@ class _CustomFormRowState extends State { @override void initState() { super.initState(); - suffixWidget = widget.suffixWidget ?? const SizedBox.shrink(); } @override Widget build(BuildContext context) { + suffixWidget = widget.suffixWidget ?? const SizedBox.shrink(); return CupertinoButton( padding: EdgeInsets.zero, onPressed: widget.onPressed, diff --git a/pubspec.yaml b/pubspec.yaml index e393fb9..7b8621b 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.4.0+380 +version: 0.4.0+381 environment: sdk: ^3.5.4 From d1ad5511f4682709d901b06a5792c0cac4a6b5f7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 19:01:12 +0200 Subject: [PATCH 038/182] Changed icons --- lib/presentation/views/settings_view.dart | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 72365ea..84916c2 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -52,7 +52,7 @@ class _SettingsViewState extends State { children: [ CustomFormRow( prefixText: 'Cabo-Strafe', - prefixIcon: CupertinoIcons.minus_square, + prefixIcon: CupertinoIcons.bolt_fill, suffixWidget: Stepper( key: _stepperKey1, initialValue: ConfigService.caboPenalty, @@ -158,7 +158,7 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: AppLocalizations.of(context).app_version, - prefixIcon: CupertinoIcons.number, + prefixIcon: CupertinoIcons.tag, onPressed: null, suffixWidget: Text(VersionService.getVersion(), style: TextStyle( diff --git a/pubspec.yaml b/pubspec.yaml index 7b8621b..5edeba0 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.4.0+381 +version: 0.4.0+382 environment: sdk: ^3.5.4 From 68dbed1e560123882c05c4d725c6d4b7c555830c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 21:04:47 +0200 Subject: [PATCH 039/182] Added rate_my_app package --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 5edeba0..175eebe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: intl: any syncfusion_flutter_charts: ^30.1.37 uuid: ^4.5.1 + rate_my_app: ^2.3.2 dev_dependencies: flutter_test: From 61d844c289bd253c448bf21835c91ef4c265945e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 21:06:55 +0200 Subject: [PATCH 040/182] Renamed folder --- lib/{utility => core}/custom_theme.dart | 0 lib/{utility => core}/globals.dart | 0 lib/main.dart | 2 +- lib/presentation/views/active_game_view.dart | 2 +- lib/presentation/views/create_game_view.dart | 2 +- lib/presentation/views/main_menu_view.dart | 2 +- lib/presentation/views/mode_selection_view.dart | 2 +- lib/presentation/views/round_view.dart | 2 +- lib/presentation/views/settings_view.dart | 2 +- lib/presentation/views/tab_view.dart | 2 +- lib/presentation/widgets/custom_form_row.dart | 2 +- lib/presentation/widgets/stepper.dart | 2 +- lib/services/version_service.dart | 2 +- 13 files changed, 11 insertions(+), 11 deletions(-) rename lib/{utility => core}/custom_theme.dart (100%) rename lib/{utility => core}/globals.dart (100%) diff --git a/lib/utility/custom_theme.dart b/lib/core/custom_theme.dart similarity index 100% rename from lib/utility/custom_theme.dart rename to lib/core/custom_theme.dart diff --git a/lib/utility/globals.dart b/lib/core/globals.dart similarity index 100% rename from lib/utility/globals.dart rename to lib/core/globals.dart diff --git a/lib/main.dart b/lib/main.dart index b826072..2b6a163 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,9 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/views/tab_view.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/version_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index ddc0299..37fd652 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; @@ -5,7 +6,6 @@ import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 2c04e74..37123ca 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -1,10 +1,10 @@ +import 'package:cabo_counter/core/custom_theme.dart'; 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/presentation/views/active_game_view.dart'; import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/services/config_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; enum CreateStatus { diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 230c4de..425cf46 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart'; @@ -5,7 +6,6 @@ import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/settings_view.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:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/presentation/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart index 93cdc7a..cc2ac52 100644 --- a/lib/presentation/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -1,5 +1,5 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; class ModeSelectionMenu extends StatelessWidget { diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 4e114fe..4dfb45c 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -1,7 +1,7 @@ +import 'package:cabo_counter/core/custom_theme.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:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 84916c2..20c97ec 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,10 +1,10 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/version_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index efa4311..a3d06de 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,7 +1,7 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/views/information_view.dart'; import 'package:cabo_counter/presentation/views/main_menu_view.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; class TabView extends StatefulWidget { diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart index 7a266ae..96067c3 100644 --- a/lib/presentation/widgets/custom_form_row.dart +++ b/lib/presentation/widgets/custom_form_row.dart @@ -1,5 +1,5 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/presentation/widgets/stepper.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; class CustomFormRow extends StatefulWidget { diff --git a/lib/presentation/widgets/stepper.dart b/lib/presentation/widgets/stepper.dart index 8ca2635..5d0bce8 100644 --- a/lib/presentation/widgets/stepper.dart +++ b/lib/presentation/widgets/stepper.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:flutter/cupertino.dart'; // Für iOS-Style class Stepper extends StatefulWidget { diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart index c23d187..b116d9b 100644 --- a/lib/services/version_service.dart +++ b/lib/services/version_service.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/utility/globals.dart'; +import 'package:cabo_counter/core/globals.dart'; import 'package:package_info_plus/package_info_plus.dart'; class VersionService { From ceb0be123e239a2b88e25463d200e409fce20d52 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 22:04:54 +0200 Subject: [PATCH 041/182] Implement native rating dialog --- lib/core/globals.dart | 8 ++++++++ lib/presentation/views/main_menu_view.dart | 8 ++++++++ pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/core/globals.dart b/lib/core/globals.dart index e11a118..6bfd077 100644 --- a/lib/core/globals.dart +++ b/lib/core/globals.dart @@ -1,3 +1,11 @@ +import 'package:rate_my_app/rate_my_app.dart'; + class Globals { static String appDevPhase = 'Beta'; + static RateMyApp rateMyApp = RateMyApp( + appStoreIdentifier: '6747105718', + minDays: 15, + remindDays: 45, + minLaunches: 15, + remindLaunches: 40); } diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 425cf46..05665ce 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,4 +1,5 @@ import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/core/globals.dart'; import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart'; @@ -29,6 +30,13 @@ class _MainMenuViewState extends State { }); }); gameManager.addListener(_updateView); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Globals.rateMyApp.init(); + if (mounted && Globals.rateMyApp.shouldOpenDialog) { + Globals.rateMyApp.showStarRateDialog(context); + } + }); } void _updateView() { diff --git a/pubspec.yaml b/pubspec.yaml index 175eebe..92ed456 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.4.0+382 +version: 0.4.0+411 environment: sdk: ^3.5.4 From f66b725f791c18fad7588880c06d5ff03480c90c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 23:11:06 +0200 Subject: [PATCH 042/182] Implemented logic for pre rating and refactored rating dialog --- analysis_options.yaml | 3 +- lib/presentation/views/main_menu_view.dart | 68 +++++++++++++++++++++- pubspec.yaml | 2 +- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2ce6b52..f9adae0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,5 @@ linter: prefer_const_literals_to_create_immutables: true unnecessary_const: true lines_longer_than_80_chars: false + constant_identifier_names: false -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 05665ce..ea330e5 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -9,6 +9,7 @@ import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart' as url; class MainMenuView extends StatefulWidget { const MainMenuView({super.key}); @@ -20,6 +21,9 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; + static const int RATING_DIALOG_YES = 1; + static const int RATING_DIALOG_NO = 0; + static const int RATING_DIALOG_CANCEL = -1; @override initState() { @@ -33,8 +37,11 @@ class _MainMenuViewState extends State { WidgetsBinding.instance.addPostFrameCallback((_) async { await Globals.rateMyApp.init(); - if (mounted && Globals.rateMyApp.shouldOpenDialog) { - Globals.rateMyApp.showStarRateDialog(context); + + if (Globals.rateMyApp.shouldOpenDialog) { + await Future.delayed(const Duration(milliseconds: 600)); + if (!mounted) return; + _handleFeedbackDialog(context); } }); } @@ -248,6 +255,63 @@ class _MainMenuViewState extends State { return shouldDelete; } + Future _showPreRatingDialog(BuildContext context) async { + int? answer = await showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: const Text('Gefällt dir die App?'), + content: const Text('Dein Feedback hilft uns weiter.'), + actions: [ + CupertinoDialogAction( + child: const Text('Ja'), + onPressed: () { + Navigator.of(context).pop(RATING_DIALOG_YES); + }, + ), + CupertinoDialogAction( + child: const Text('Nein'), + onPressed: () { + Navigator.of(context).pop(RATING_DIALOG_NO); + }, + ), + CupertinoDialogAction( + isDestructiveAction: true, + onPressed: () { + Navigator.of(context).pop(RATING_DIALOG_CANCEL); + }, + child: Text(AppLocalizations.of(context).cancel), + ), + ], + ); + }, + ); + return answer ?? RATING_DIALOG_CANCEL; + } + + /// Handles the feedback dialog when the conditions for rating are met. + /// It shows a dialog asking the user if they like the app, + /// and based on their response, it either opens the rating dialog or an email client for feedback. + Future _handleFeedbackDialog(BuildContext context) async { + int decision = await _showPreRatingDialog(context); + + final Uri emailUri = Uri( + scheme: 'mailto', + path: 'cabo-counter@felixkirchner.de', + query: 'subject=Feedback: Cabo Counter App' + '&body=Ich habe folgendes Feedback...', + ); + + switch (decision) { + case RATING_DIALOG_YES: + Globals.rateMyApp.showStarRateDialog(context); + case RATING_DIALOG_NO: + url.launchUrl(emailUri, mode: url.LaunchMode.externalApplication); + case RATING_DIALOG_CANCEL: + break; + } + } + @override void dispose() { gameManager.removeListener(_updateView); diff --git a/pubspec.yaml b/pubspec.yaml index 92ed456..bb9ffcf 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.4.0+411 +version: 0.4.0+437 environment: sdk: ^3.5.4 From 158e687f9f023ce5746377771ef5d33dc356dc06 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 23:40:22 +0200 Subject: [PATCH 043/182] updated launch mode --- lib/presentation/views/information_view.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/information_view.dart b/lib/presentation/views/information_view.dart index 1d0918b..2c5c739 100644 --- a/lib/presentation/views/information_view.dart +++ b/lib/presentation/views/information_view.dart @@ -53,16 +53,19 @@ class InformationView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( - onPressed: () => - launchUrl(Uri.parse('https://www.instagram.com/fx.kr')), + onPressed: () => launchUrl( + Uri.parse('https://www.instagram.com/flixcoo'), + mode: LaunchMode.externalApplication), icon: const Icon(FontAwesomeIcons.instagram)), IconButton( onPressed: () => launchUrl( - Uri.parse('mailto:felix.kirchner.fk@gmail.com')), + Uri.parse('mailto:felix.kirchner.fk@gmail.com'), + mode: LaunchMode.externalApplication), icon: const Icon(CupertinoIcons.envelope)), IconButton( - onPressed: () => - launchUrl(Uri.parse('https://www.github.com/flixcoo')), + onPressed: () => launchUrl( + Uri.parse('https://www.github.com/flixcoo'), + mode: LaunchMode.externalApplication), icon: const Icon(FontAwesomeIcons.github)), ], ), From 33c4b773dd991d35303a4c4a1e7150c9a98126e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 23:40:35 +0200 Subject: [PATCH 044/182] Small changes --- lib/presentation/views/main_menu_view.dart | 7 ++++--- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index ea330e5..17813df 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -9,7 +9,7 @@ import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart' as url; +import 'package:url_launcher/url_launcher.dart'; class MainMenuView extends StatefulWidget { const MainMenuView({super.key}); @@ -304,9 +304,10 @@ class _MainMenuViewState extends State { switch (decision) { case RATING_DIALOG_YES: - Globals.rateMyApp.showStarRateDialog(context); + if (context.mounted) Globals.rateMyApp.showStarRateDialog(context); + break; case RATING_DIALOG_NO: - url.launchUrl(emailUri, mode: url.LaunchMode.externalApplication); + launchUrl(emailUri, mode: LaunchMode.externalApplication); case RATING_DIALOG_CANCEL: break; } diff --git a/pubspec.yaml b/pubspec.yaml index bb9ffcf..71d1689 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.4.0+437 +version: 0.4.0+438 environment: sdk: ^3.5.4 From 37c539607f30603b57ec02cb2e543bd97f0907c3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 9 Jul 2025 23:40:40 +0200 Subject: [PATCH 045/182] Updated launch mode --- lib/presentation/views/settings_view.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 20c97ec..5b2a331 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -144,15 +144,19 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: AppLocalizations.of(context).create_issue, prefixIcon: FontAwesomeIcons.github, - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/issues')), + onPressed: () => launchUrl( + Uri.parse( + 'https://github.com/flixcoo/Cabo-Counter/issues'), + mode: LaunchMode.externalApplication), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( prefixText: AppLocalizations.of(context).wiki, prefixIcon: CupertinoIcons.book, - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/wiki')), + onPressed: () => launchUrl( + Uri.parse( + 'https://github.com/flixcoo/Cabo-Counter/wiki'), + mode: LaunchMode.externalApplication), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( From bb6d718c9e7925d9a1d4a7f5fe98cefab734991e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 16:37:56 +0200 Subject: [PATCH 046/182] Updated linting rules --- analysis_options.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2ce6b52..46e2dbe 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,4 @@ linter: prefer_const_literals_to_create_immutables: true unnecessary_const: true lines_longer_than_80_chars: false - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + constant_identifier_names: false From e4dba146427c392d9db2a3d7009867980487c020 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 16:38:41 +0200 Subject: [PATCH 047/182] Renamed folders --- lib/main.dart | 2 +- lib/presentation/views/active_game_view.dart | 2 +- lib/presentation/views/create_game_view.dart | 2 +- lib/presentation/views/graph_view.dart | 2 +- lib/presentation/views/main_menu_view.dart | 2 +- .../views/mode_selection_view.dart | 2 +- lib/presentation/views/round_view.dart | 2 +- lib/presentation/views/settings_view.dart | 28 ++++++++++++------- lib/presentation/views/tab_view.dart | 2 +- lib/services/version_service.dart | 4 +-- lib/utility/constants.dart | 15 ++++++++++ lib/utility/globals.dart | 3 -- 12 files changed, 43 insertions(+), 23 deletions(-) create mode 100644 lib/utility/constants.dart delete mode 100644 lib/utility/globals.dart diff --git a/lib/main.dart b/lib/main.dart index b826072..b714d58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/tab_view.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index ddc0299..effea40 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -1,6 +1,6 @@ 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/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart'; diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 2c04e74..6a19222 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -1,6 +1,6 @@ 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/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart'; import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/services/config_service.dart'; diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 345c670..1007007 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -1,5 +1,5 @@ import 'package:cabo_counter/data/game_session.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 230c4de..c1c14a9 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,5 +1,5 @@ import 'package:cabo_counter/data/game_manager.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/settings_view.dart'; diff --git a/lib/presentation/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart index 93cdc7a..547b0c7 100644 --- a/lib/presentation/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 4e114fe..a989139 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -1,5 +1,5 @@ import 'package:cabo_counter/data/game_session.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 84916c2..41532f3 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,9 +1,10 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/version_service.dart'; +import 'package:cabo_counter/utility/constants.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -93,7 +94,6 @@ class _SettingsViewState extends State { setState(() { _stepperKey1 = UniqueKey(); _stepperKey2 = UniqueKey(); - print('Config reset to default'); }); }, ) @@ -142,17 +142,25 @@ class _SettingsViewState extends State { margin: EdgeInsets.zero, children: [ CustomFormRow( - prefixText: AppLocalizations.of(context).create_issue, - prefixIcon: FontAwesomeIcons.github, - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/issues')), + prefixText: AppLocalizations.of(context).wiki, + prefixIcon: CupertinoIcons.book, + onPressed: () => + launchUrl(Uri.parse(Constants.GITHUB_WIKI_LINK)), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( - prefixText: AppLocalizations.of(context).wiki, - prefixIcon: CupertinoIcons.book, - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/wiki')), + prefixText: + AppLocalizations.of(context).privacy_policy, + prefixIcon: CupertinoIcons.doc_append, + onPressed: () => launchUrl( + Uri.parse(Constants.PRIVACY_POLICY_LINK)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).error_found, + prefixIcon: FontAwesomeIcons.github, + onPressed: () => launchUrl( + Uri.parse(Constants.GITHUB_ISSUES_LINK)), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index efa4311..999a90e 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/information_view.dart'; import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart index c23d187..7c306e3 100644 --- a/lib/services/version_service.dart +++ b/lib/services/version_service.dart @@ -1,4 +1,4 @@ -import 'package:cabo_counter/utility/globals.dart'; +import 'package:cabo_counter/utility/constants.dart'; import 'package:package_info_plus/package_info_plus.dart'; class VersionService { @@ -19,7 +19,7 @@ class VersionService { if (_version == '-.-.-') { return getVersionNumber(); } - return '${Globals.appDevPhase} $_version'; + return '${Constants.appDevPhase} $_version'; } static String getBuildNumber() { diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart new file mode 100644 index 0000000..215de73 --- /dev/null +++ b/lib/utility/constants.dart @@ -0,0 +1,15 @@ +import 'dart:core'; + +class Constants { + static String appDevPhase = 'Beta'; + static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; + static const String GITHUB_LINK = 'https://github.felixkirchner.de'; + static const String GITHUB_REPO_LINK = 'https://github.felixkirchner.de'; + static const String GITHUB_ISSUES_LINK = + 'https://github.com/flixcoo/Cabo-Counter/issues'; + static const String GITHUB_WIKI_LINK = + 'https://github.com/flixcoo/Cabo-Counter/issues'; + static const String EMAIL = 'cabocounter@felixkirchner.de'; + static const String PRIVACY_POLICY_LINK = + 'https://github.felixkirchner.de/Cabo-Counter/wiki/Privacy-Policy'; +} diff --git a/lib/utility/globals.dart b/lib/utility/globals.dart deleted file mode 100644 index e11a118..0000000 --- a/lib/utility/globals.dart +++ /dev/null @@ -1,3 +0,0 @@ -class Globals { - static String appDevPhase = 'Beta'; -} From b98b5b0ac99cd7bf80b1e67c3edf4bd5dae2f7fc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 16:38:56 +0200 Subject: [PATCH 048/182] Changed l10n files location --- l10n.yaml | 7 ++++--- lib/l10n/{ => arb}/untranslated_messages.json | 0 lib/l10n/{ => generated}/app_localizations.dart | 8 +++++++- lib/l10n/{ => generated}/app_localizations_de.dart | 3 +++ lib/l10n/{ => generated}/app_localizations_en.dart | 3 +++ 5 files changed, 17 insertions(+), 4 deletions(-) rename lib/l10n/{ => arb}/untranslated_messages.json (100%) rename lib/l10n/{ => generated}/app_localizations.dart (98%) rename lib/l10n/{ => generated}/app_localizations_de.dart (98%) rename lib/l10n/{ => generated}/app_localizations_en.dart (98%) diff --git a/l10n.yaml b/l10n.yaml index 239fdc6..f69305d 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,5 +1,6 @@ -arb-dir: lib/l10n +arb-dir: lib/l10n/arb template-arb-file: app_de.arb -untranslated-messages-file: lib/l10n/untranslated_messages.json +untranslated-messages-file: lib/l10n/arb/untranslated_messages.json nullable-getter: false -output-localization-file: app_localizations.dart \ No newline at end of file +output-localization-file: app_localizations.dart +output-dir: lib/l10n/generated \ No newline at end of file diff --git a/lib/l10n/untranslated_messages.json b/lib/l10n/arb/untranslated_messages.json similarity index 100% rename from lib/l10n/untranslated_messages.json rename to lib/l10n/arb/untranslated_messages.json diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/generated/app_localizations.dart similarity index 98% rename from lib/l10n/app_localizations.dart rename to lib/l10n/generated/app_localizations.dart index 9f85aab..ff14253 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -18,7 +18,7 @@ import 'app_localizations_en.dart'; /// `supportedLocales` list. For example: /// /// ```dart -/// import 'l10n/app_localizations.dart'; +/// import 'generated/app_localizations.dart'; /// /// return MaterialApp( /// localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -566,6 +566,12 @@ abstract class AppLocalizations { /// **'App-Version'** String get app_version; + /// No description provided for @privacy_policy. + /// + /// In de, this message translates to: + /// **'Datenschutzerklärung'** + String get privacy_policy; + /// No description provided for @build. /// /// In de, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart similarity index 98% rename from lib/l10n/app_localizations_de.dart rename to lib/l10n/generated/app_localizations_de.dart index 23b41ac..898df64 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -256,6 +256,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get app_version => 'App-Version'; + @override + String get privacy_policy => 'Datenschutzerklärung'; + @override String get build => 'Build-Nr.'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart similarity index 98% rename from lib/l10n/app_localizations_en.dart rename to lib/l10n/generated/app_localizations_en.dart index eea3896..c057432 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -253,6 +253,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_version => 'App Version'; + @override + String get privacy_policy => 'Privacy Policy'; + @override String get build => 'Build No.'; From 38d5151a3e86b38be678a8f4f026daade49b60b5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 16:39:12 +0200 Subject: [PATCH 049/182] Implemented new link constants --- lib/l10n/{ => arb}/app_de.arb | 1 + lib/l10n/{ => arb}/app_en.arb | 1 + lib/presentation/views/information_view.dart | 11 ++++++----- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) rename lib/l10n/{ => arb}/app_de.arb (99%) rename lib/l10n/{ => arb}/app_en.arb (99%) diff --git a/lib/l10n/app_de.arb b/lib/l10n/arb/app_de.arb similarity index 99% rename from lib/l10n/app_de.arb rename to lib/l10n/arb/app_de.arb index 0a76696..7615b27 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -103,6 +103,7 @@ "create_issue": "Issue erstellen", "wiki": "Wiki", "app_version": "App-Version", + "privacy_policy": "Datenschutzerklärung", "build": "Build-Nr.", "loading": "Lädt...", diff --git a/lib/l10n/app_en.arb b/lib/l10n/arb/app_en.arb similarity index 99% rename from lib/l10n/app_en.arb rename to lib/l10n/arb/app_en.arb index edca935..ee01699 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -103,6 +103,7 @@ "create_issue": "Create Issue", "wiki": "Wiki", "app_version": "App Version", + "privacy_policy": "Privacy Policy", "loading": "Loading...", "build": "Build No.", diff --git a/lib/presentation/views/information_view.dart b/lib/presentation/views/information_view.dart index 1d0918b..d31d7f6 100644 --- a/lib/presentation/views/information_view.dart +++ b/lib/presentation/views/information_view.dart @@ -1,4 +1,5 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/utility/constants.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -54,15 +55,15 @@ class InformationView extends StatelessWidget { children: [ IconButton( onPressed: () => - launchUrl(Uri.parse('https://www.instagram.com/fx.kr')), + launchUrl(Uri.parse(Constants.INSTAGRAM_LINK)), icon: const Icon(FontAwesomeIcons.instagram)), IconButton( - onPressed: () => launchUrl( - Uri.parse('mailto:felix.kirchner.fk@gmail.com')), + onPressed: () => + launchUrl(Uri.parse('mailto:${Constants.EMAIL}')), icon: const Icon(CupertinoIcons.envelope)), IconButton( onPressed: () => - launchUrl(Uri.parse('https://www.github.com/flixcoo')), + launchUrl(Uri.parse(Constants.GITHUB_LINK)), icon: const Icon(FontAwesomeIcons.github)), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 5edeba0..c24fb24 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.4.0+382 +version: 0.4.0+386 environment: sdk: ^3.5.4 From 08917f18b0a5f475385f49ae2c839a17b33f990d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 16:40:09 +0200 Subject: [PATCH 050/182] Changed privacy policy link --- lib/utility/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart index 215de73..f4f0c0f 100644 --- a/lib/utility/constants.dart +++ b/lib/utility/constants.dart @@ -11,5 +11,5 @@ class Constants { 'https://github.com/flixcoo/Cabo-Counter/issues'; static const String EMAIL = 'cabocounter@felixkirchner.de'; static const String PRIVACY_POLICY_LINK = - 'https://github.felixkirchner.de/Cabo-Counter/wiki/Privacy-Policy'; + 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; } From ef2c9065155ecf79e74b47aa8607639d15deb57a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:15:38 +0200 Subject: [PATCH 051/182] Corrected wiki link --- lib/utility/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart index f4f0c0f..d8562d5 100644 --- a/lib/utility/constants.dart +++ b/lib/utility/constants.dart @@ -8,7 +8,7 @@ class Constants { static const String GITHUB_ISSUES_LINK = 'https://github.com/flixcoo/Cabo-Counter/issues'; static const String GITHUB_WIKI_LINK = - 'https://github.com/flixcoo/Cabo-Counter/issues'; + 'https://github.com/flixcoo/Cabo-Counter/wiki'; static const String EMAIL = 'cabocounter@felixkirchner.de'; static const String PRIVACY_POLICY_LINK = 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; From 3d63fd13a8c42be2f72b7ca702763f340dc8af26 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:15:58 +0200 Subject: [PATCH 052/182] Removed import --- lib/utility/constants.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart index d8562d5..329642e 100644 --- a/lib/utility/constants.dart +++ b/lib/utility/constants.dart @@ -1,5 +1,3 @@ -import 'dart:core'; - class Constants { static String appDevPhase = 'Beta'; static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; From 5b3b84069d66e5e9563f63169c5ca86f064c301a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:23:08 +0200 Subject: [PATCH 053/182] Updated links --- lib/utility/constants.dart | 3 +-- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart index 329642e..fd6ebbc 100644 --- a/lib/utility/constants.dart +++ b/lib/utility/constants.dart @@ -1,8 +1,7 @@ class Constants { static String appDevPhase = 'Beta'; static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; - static const String GITHUB_LINK = 'https://github.felixkirchner.de'; - static const String GITHUB_REPO_LINK = 'https://github.felixkirchner.de'; + static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; static const String GITHUB_ISSUES_LINK = 'https://github.com/flixcoo/Cabo-Counter/issues'; static const String GITHUB_WIKI_LINK = diff --git a/pubspec.yaml b/pubspec.yaml index c24fb24..5faec22 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.4.0+386 +version: 0.4.0+387 environment: sdk: ^3.5.4 From 16b6dbb150ca5448a14a0f4f5d1f9131bcda5c43 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:24:50 +0200 Subject: [PATCH 054/182] Updated links to subdomains --- lib/utility/constants.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/utility/constants.dart b/lib/utility/constants.dart index fd6ebbc..4a79399 100644 --- a/lib/utility/constants.dart +++ b/lib/utility/constants.dart @@ -3,9 +3,8 @@ class Constants { static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; static const String GITHUB_ISSUES_LINK = - 'https://github.com/flixcoo/Cabo-Counter/issues'; - static const String GITHUB_WIKI_LINK = - 'https://github.com/flixcoo/Cabo-Counter/wiki'; + 'cabocounter-issues.felixkirchner.de'; + static const String GITHUB_WIKI_LINK = 'cabocounter-wiki.felixkirchner.de '; static const String EMAIL = 'cabocounter@felixkirchner.de'; static const String PRIVACY_POLICY_LINK = 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; From 7b787a6a4f4b1b2bdedf65c62ed25e43868b8cee Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:31:07 +0200 Subject: [PATCH 055/182] Updated file paths --- lib/core/constants.dart | 10 ++++++++++ lib/core/globals.dart | 11 ----------- lib/main.dart | 1 - lib/presentation/views/information_view.dart | 2 +- lib/presentation/views/main_menu_view.dart | 8 ++++---- lib/presentation/views/mode_selection_view.dart | 2 -- lib/presentation/views/settings_view.dart | 4 +--- lib/presentation/views/tab_view.dart | 1 - lib/services/version_service.dart | 3 +-- 9 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 lib/core/globals.dart diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 4a79399..ce8c30a 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,5 +1,8 @@ +import 'package:rate_my_app/rate_my_app.dart'; + class Constants { static String appDevPhase = 'Beta'; + static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; static const String GITHUB_ISSUES_LINK = @@ -8,4 +11,11 @@ class Constants { static const String EMAIL = 'cabocounter@felixkirchner.de'; static const String PRIVACY_POLICY_LINK = 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; + + static RateMyApp rateMyApp = RateMyApp( + appStoreIdentifier: '6747105718', + minDays: 15, + remindDays: 45, + minLaunches: 15, + remindLaunches: 40); } diff --git a/lib/core/globals.dart b/lib/core/globals.dart deleted file mode 100644 index 6bfd077..0000000 --- a/lib/core/globals.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:rate_my_app/rate_my_app.dart'; - -class Globals { - static String appDevPhase = 'Beta'; - static RateMyApp rateMyApp = RateMyApp( - appStoreIdentifier: '6747105718', - minDays: 15, - remindDays: 45, - minLaunches: 15, - remindLaunches: 40); -} diff --git a/lib/main.dart b/lib/main.dart index 4002dda..9279426 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,4 @@ import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/tab_view.dart'; import 'package:cabo_counter/services/config_service.dart'; diff --git a/lib/presentation/views/information_view.dart b/lib/presentation/views/information_view.dart index d31d7f6..712d709 100644 --- a/lib/presentation/views/information_view.dart +++ b/lib/presentation/views/information_view.dart @@ -1,5 +1,5 @@ +import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; -import 'package:cabo_counter/utility/constants.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 925b9ed..fee5c72 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,5 +1,5 @@ +import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/core/globals.dart'; import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart'; @@ -36,9 +36,9 @@ class _MainMenuViewState extends State { gameManager.addListener(_updateView); WidgetsBinding.instance.addPostFrameCallback((_) async { - await Globals.rateMyApp.init(); + await Constants.rateMyApp.init(); - if (Globals.rateMyApp.shouldOpenDialog) { + if (Constants.rateMyApp.shouldOpenDialog) { await Future.delayed(const Duration(milliseconds: 600)); if (!mounted) return; _handleFeedbackDialog(context); @@ -304,7 +304,7 @@ class _MainMenuViewState extends State { switch (decision) { case RATING_DIALOG_YES: - if (context.mounted) Globals.rateMyApp.showStarRateDialog(context); + if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); break; case RATING_DIALOG_NO: launchUrl(emailUri, mode: LaunchMode.externalApplication); diff --git a/lib/presentation/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart index 67ab5fb..a7d3ce7 100644 --- a/lib/presentation/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -1,7 +1,5 @@ import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; class ModeSelectionMenu extends StatelessWidget { diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 2f55ba3..c9a409d 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,13 +1,11 @@ +import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/version_service.dart'; -import 'package:cabo_counter/utility/constants.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index 1fd84d2..15d4637 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,5 +1,4 @@ import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/information_view.dart'; import 'package:cabo_counter/presentation/views/main_menu_view.dart'; diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart index ade934d..6511c69 100644 --- a/lib/services/version_service.dart +++ b/lib/services/version_service.dart @@ -1,5 +1,4 @@ -import 'package:cabo_counter/core/globals.dart'; -import 'package:cabo_counter/utility/constants.dart'; +import 'package:cabo_counter/core/constants.dart'; import 'package:package_info_plus/package_info_plus.dart'; class VersionService { From d5d318929e2b601d20095c4d1b1730c6b4611ed9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:42:05 +0200 Subject: [PATCH 056/182] Updated strings --- lib/l10n/arb/app_de.arb | 5 ++++ lib/l10n/arb/app_en.arb | 5 ++++ lib/l10n/generated/app_localizations.dart | 24 ++++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 13 +++++++++++ lib/l10n/generated/app_localizations_en.dart | 13 +++++++++++ lib/presentation/views/main_menu_view.dart | 8 +++---- pubspec.yaml | 2 +- 7 files changed, 65 insertions(+), 5 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 7615b27..64a6d02 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -30,6 +30,11 @@ } } }, + "rating_title": "Gefällt dir die App?", + "rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!", + "yes": "Ja", + "no": "Nein", + "overview": "Übersicht", "new_game": "Neues Spiel", "game_title": "Titel des Spiels", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ee01699..805a178 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -30,6 +30,11 @@ } } }, + "rating_title": "Do you like the app?", + "rating_message": "Feedback helps me to continuously improve the app. Thank you!", + "yes": "Yes", + "no": "No", + "overview": "Overview", "new_game": "New Game", "game_title": "Game Title", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index ff14253..c349078 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -218,6 +218,30 @@ abstract class AppLocalizations { /// **'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 @rating_title. + /// + /// In de, this message translates to: + /// **'Gefällt dir die App?'** + String get rating_title; + + /// No description provided for @rating_message. + /// + /// In de, this message translates to: + /// **'Feedback hilft mir, die App zu verbessern. Vielen Dank!'** + String get rating_message; + + /// No description provided for @yes. + /// + /// In de, this message translates to: + /// **'Ja'** + String get yes; + + /// No description provided for @no. + /// + /// In de, this message translates to: + /// **'Nein'** + String get no; + /// No description provided for @overview. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 898df64..d6fcf72 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -71,6 +71,19 @@ class AppLocalizationsDe extends AppLocalizations { return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; } + @override + String get rating_title => 'Gefällt dir die App?'; + + @override + String get rating_message => + 'Feedback hilft mir, die App zu verbessern. Vielen Dank!'; + + @override + String get yes => 'Ja'; + + @override + String get no => 'Nein'; + @override String get overview => 'Übersicht'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index c057432..4adaa4b 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -71,6 +71,19 @@ class AppLocalizationsEn extends AppLocalizations { return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.'; } + @override + String get rating_title => 'Do you like the app?'; + + @override + String get rating_message => + 'Feedback helps me to continuously improve the app. Thank you!'; + + @override + String get yes => 'Yes'; + + @override + String get no => 'No'; + @override String get overview => 'Overview'; diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index fee5c72..bf417f7 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -260,17 +260,17 @@ class _MainMenuViewState extends State { context: context, builder: (BuildContext context) { return CupertinoAlertDialog( - title: const Text('Gefällt dir die App?'), - content: const Text('Dein Feedback hilft uns weiter.'), + title: Text(AppLocalizations.of(context).rating_title), + content: Text(AppLocalizations.of(context).rating_message), actions: [ CupertinoDialogAction( - child: const Text('Ja'), + child: Text(AppLocalizations.of(context).yes), onPressed: () { Navigator.of(context).pop(RATING_DIALOG_YES); }, ), CupertinoDialogAction( - child: const Text('Nein'), + child: Text(AppLocalizations.of(context).no), onPressed: () { Navigator.of(context).pop(RATING_DIALOG_NO); }, diff --git a/pubspec.yaml b/pubspec.yaml index 35eac0b..f0d4ba4 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.4.0+443 +version: 0.4.0+451 environment: sdk: ^3.5.4 From 08c2b1d91a1d91f20b63ec4db506cd6a10fdbc9e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:42:47 +0200 Subject: [PATCH 057/182] Updated identifiers --- lib/l10n/arb/app_de.arb | 4 ++-- lib/l10n/arb/app_en.arb | 4 ++-- lib/l10n/generated/app_localizations.dart | 8 ++++---- lib/l10n/generated/app_localizations_de.dart | 4 ++-- lib/l10n/generated/app_localizations_en.dart | 4 ++-- lib/presentation/views/main_menu_view.dart | 4 ++-- pubspec.yaml | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 64a6d02..c4f5692 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -30,8 +30,8 @@ } } }, - "rating_title": "Gefällt dir die App?", - "rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!", + "pre_rating_title": "Gefällt dir die App?", + "pre_rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!", "yes": "Ja", "no": "Nein", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 805a178..d5ccd1a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -30,8 +30,8 @@ } } }, - "rating_title": "Do you like the app?", - "rating_message": "Feedback helps me to continuously improve the app. Thank you!", + "pre_rating_title": "Do you like the app?", + "pre_rating_message": "Feedback helps me to continuously improve the app. Thank you!", "yes": "Yes", "no": "No", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index c349078..74df468 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -218,17 +218,17 @@ abstract class AppLocalizations { /// **'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 @rating_title. + /// No description provided for @pre_rating_title. /// /// In de, this message translates to: /// **'Gefällt dir die App?'** - String get rating_title; + String get pre_rating_title; - /// No description provided for @rating_message. + /// No description provided for @pre_rating_message. /// /// In de, this message translates to: /// **'Feedback hilft mir, die App zu verbessern. Vielen Dank!'** - String get rating_message; + String get pre_rating_message; /// No description provided for @yes. /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index d6fcf72..ef180b1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -72,10 +72,10 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get rating_title => 'Gefällt dir die App?'; + String get pre_rating_title => 'Gefällt dir die App?'; @override - String get rating_message => + String get pre_rating_message => 'Feedback hilft mir, die App zu verbessern. Vielen Dank!'; @override diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 4adaa4b..12d45bc 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -72,10 +72,10 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get rating_title => 'Do you like the app?'; + String get pre_rating_title => 'Do you like the app?'; @override - String get rating_message => + String get pre_rating_message => 'Feedback helps me to continuously improve the app. Thank you!'; @override diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index bf417f7..166c48e 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -260,8 +260,8 @@ class _MainMenuViewState extends State { context: context, builder: (BuildContext context) { return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).rating_title), - content: Text(AppLocalizations.of(context).rating_message), + title: Text(AppLocalizations.of(context).pre_rating_title), + content: Text(AppLocalizations.of(context).pre_rating_message), actions: [ CupertinoDialogAction( child: Text(AppLocalizations.of(context).yes), diff --git a/pubspec.yaml b/pubspec.yaml index f0d4ba4..795551a 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.4.0+451 +version: 0.4.0+452 environment: sdk: ^3.5.4 From 4a918fbbdb04514280fb0712ecab9f38de05b344 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 17:43:09 +0200 Subject: [PATCH 058/182] Added break in switch case --- lib/presentation/views/main_menu_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 166c48e..085a209 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -308,6 +308,7 @@ class _MainMenuViewState extends State { break; case RATING_DIALOG_NO: launchUrl(emailUri, mode: LaunchMode.externalApplication); + break; case RATING_DIALOG_CANCEL: break; } From 9519a6a531c0fedcee1761f4b81c63194f18f017 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 18:51:37 +0200 Subject: [PATCH 059/182] Updated strings --- lib/l10n/arb/app_de.arb | 5 ++++ lib/l10n/arb/app_en.arb | 5 ++++ lib/l10n/generated/app_localizations.dart | 30 ++++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 16 +++++++++++ lib/l10n/generated/app_localizations_en.dart | 16 +++++++++++ 5 files changed, 72 insertions(+) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c4f5692..d89399b 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -34,6 +34,11 @@ "pre_rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!", "yes": "Ja", "no": "Nein", + "bad_rating_title": "Unzufrieden mit der App?", + "bad_rating_message": "Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!", + "contact_email": "E-Mail schreiben", + "email_subject": "Feedback: Cabo Counter App", + "email_body": "Ich habe folgendes Feedback...", "overview": "Übersicht", "new_game": "Neues Spiel", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d5ccd1a..3f24635 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -34,6 +34,11 @@ "pre_rating_message": "Feedback helps me to continuously improve the app. Thank you!", "yes": "Yes", "no": "No", + "bad_rating_title": "Not satisfied?", + "bad_rating_message": "If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.", + "contact_email": "Contac vía E-Mail", + "email_subject": "Feedback: Cabo Counter App", + "email_body": "I have the following feedback...", "overview": "Overview", "new_game": "New Game", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 74df468..af778ae 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -242,6 +242,36 @@ abstract class AppLocalizations { /// **'Nein'** String get no; + /// No description provided for @bad_rating_title. + /// + /// In de, this message translates to: + /// **'Unzufrieden mit der App?'** + String get bad_rating_title; + + /// No description provided for @bad_rating_message. + /// + /// In de, this message translates to: + /// **'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!'** + String get bad_rating_message; + + /// No description provided for @contact_email. + /// + /// In de, this message translates to: + /// **'E-Mail schreiben'** + String get contact_email; + + /// No description provided for @email_subject. + /// + /// In de, this message translates to: + /// **'Feedback: Cabo Counter App'** + String get email_subject; + + /// No description provided for @email_body. + /// + /// In de, this message translates to: + /// **'Ich habe folgendes Feedback...'** + String get email_body; + /// No description provided for @overview. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index ef180b1..afe558f 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -84,6 +84,22 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no => 'Nein'; + @override + String get bad_rating_title => 'Unzufrieden mit der App?'; + + @override + String get bad_rating_message => + 'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!'; + + @override + String get contact_email => 'E-Mail schreiben'; + + @override + String get email_subject => 'Feedback: Cabo Counter App'; + + @override + String get email_body => 'Ich habe folgendes Feedback...'; + @override String get overview => 'Übersicht'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 12d45bc..d3e433b 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -84,6 +84,22 @@ class AppLocalizationsEn extends AppLocalizations { @override String get no => 'No'; + @override + String get bad_rating_title => 'Not satisfied?'; + + @override + String get bad_rating_message => + 'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.'; + + @override + String get contact_email => 'Contac vía E-Mail'; + + @override + String get email_subject => 'Feedback: Cabo Counter App'; + + @override + String get email_body => 'I have the following feedback...'; + @override String get overview => 'Overview'; From 6ca7f2d9780ed279f09826ad44b7e4e5ff785627 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 18:51:52 +0200 Subject: [PATCH 060/182] Implemented new popup --- lib/presentation/views/main_menu_view.dart | 117 +++++++++++++++------ 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 085a209..05c4a07 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -21,9 +21,11 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; - static const int RATING_DIALOG_YES = 1; - static const int RATING_DIALOG_NO = 0; - static const int RATING_DIALOG_CANCEL = -1; + static const int PRE_RATING_DIALOG_YES = 10; + static const int PRE_RATING_DIALOG_NO = 11; + static const int PRE_RATING_DIALOG_CANCEL = 12; + static const int BAD_RATING_DIALOG_EMAIL = 20; + static const int BAD_RATING_DIALOG_CANCEL = 21; @override initState() { @@ -94,13 +96,15 @@ class _MainMenuViewState extends State { const SizedBox(height: 30), // Abstand von oben Center( child: GestureDetector( - onTap: () => Navigator.push( + onTap: () => _handleFeedbackDialog( + context) /*Navigator.push( context, CupertinoPageRoute( builder: (context) => const CreateGameView(), ), - ), + )*/ + , child: Icon( CupertinoIcons.plus, size: 60, @@ -255,6 +259,48 @@ class _MainMenuViewState extends State { return shouldDelete; } + /// Handles the feedback dialog when the conditions for rating are met. + /// It shows a dialog asking the user if they like the app, + /// and based on their response, it either opens the rating dialog or an email client for feedback. + Future _handleFeedbackDialog(BuildContext context) async { + final String emailSubject = AppLocalizations.of(context).email_subject; + final String emailBody = AppLocalizations.of(context).email_body; + + final Uri emailUri = Uri( + scheme: 'mailto', + path: Constants.EMAIL, + query: 'subject=$emailSubject' + '&body=$emailBody', + ); + + int decision = await _showPreRatingDialog(context); + int decision2 = 0; + + // so that the bad rating dialog is not shown immediately + await Future.delayed(const Duration(milliseconds: 300)); + + switch (decision) { + case PRE_RATING_DIALOG_YES: + if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); + break; + case PRE_RATING_DIALOG_NO: + if (context.mounted) decision2 = await _showBadRatingDialog(context); + if (decision2 == BAD_RATING_DIALOG_EMAIL) { + if (context.mounted) { + launchUrl(emailUri); + } + } + break; + case PRE_RATING_DIALOG_CANCEL: + break; + } + } + + /// Shows a dialog asking the user if they like the app. + /// Returns the user's decision as an integer. + /// - PRE_RATING_DIALOG_YES: User likes the app and wants to rate it. + /// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback. + /// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog. Future _showPreRatingDialog(BuildContext context) async { int? answer = await showCupertinoDialog( context: context, @@ -266,19 +312,19 @@ class _MainMenuViewState extends State { CupertinoDialogAction( child: Text(AppLocalizations.of(context).yes), onPressed: () { - Navigator.of(context).pop(RATING_DIALOG_YES); + Navigator.of(context).pop(PRE_RATING_DIALOG_YES); }, ), CupertinoDialogAction( child: Text(AppLocalizations.of(context).no), onPressed: () { - Navigator.of(context).pop(RATING_DIALOG_NO); + Navigator.of(context).pop(PRE_RATING_DIALOG_NO); }, ), CupertinoDialogAction( isDestructiveAction: true, onPressed: () { - Navigator.of(context).pop(RATING_DIALOG_CANCEL); + Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL); }, child: Text(AppLocalizations.of(context).cancel), ), @@ -286,32 +332,41 @@ class _MainMenuViewState extends State { ); }, ); - return answer ?? RATING_DIALOG_CANCEL; + return answer ?? PRE_RATING_DIALOG_CANCEL; } - /// Handles the feedback dialog when the conditions for rating are met. - /// It shows a dialog asking the user if they like the app, - /// and based on their response, it either opens the rating dialog or an email client for feedback. - Future _handleFeedbackDialog(BuildContext context) async { - int decision = await _showPreRatingDialog(context); - - final Uri emailUri = Uri( - scheme: 'mailto', - path: 'cabo-counter@felixkirchner.de', - query: 'subject=Feedback: Cabo Counter App' - '&body=Ich habe folgendes Feedback...', + /// Shows a dialog asking the user for feedback if they do not like the app. + /// Returns the user's decision as an integer. + /// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback. + /// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog. + Future _showBadRatingDialog(BuildContext context) async { + int? answer = await showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).bad_rating_title), + content: Text(AppLocalizations.of(context).bad_rating_message), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).contact_email, + style: const TextStyle(fontWeight: FontWeight.bold)), + onPressed: () { + Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL); + }, + ), + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel, + style: + const TextStyle(color: CupertinoColors.destructiveRed)), + onPressed: () { + Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL); + }, + ), + ], + ); + }, ); - - switch (decision) { - case RATING_DIALOG_YES: - if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); - break; - case RATING_DIALOG_NO: - launchUrl(emailUri, mode: LaunchMode.externalApplication); - break; - case RATING_DIALOG_CANCEL: - break; - } + return answer ?? PRE_RATING_DIALOG_CANCEL; } @override From ca9c3a15f3ad7b0c3dd91fcd9e8f887a566c8292 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 18:51:59 +0200 Subject: [PATCH 061/182] Corrected links --- lib/core/constants.dart | 5 +++-- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index ce8c30a..10b062d 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -6,8 +6,9 @@ class Constants { static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; static const String GITHUB_ISSUES_LINK = - 'cabocounter-issues.felixkirchner.de'; - static const String GITHUB_WIKI_LINK = 'cabocounter-wiki.felixkirchner.de '; + 'https://cabocounter-issues.felixkirchner.de'; + static const String GITHUB_WIKI_LINK = + 'https://cabocounter-wiki.felixkirchner.de'; static const String EMAIL = 'cabocounter@felixkirchner.de'; static const String PRIVACY_POLICY_LINK = 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; diff --git a/pubspec.yaml b/pubspec.yaml index 795551a..4625269 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.4.0+452 +version: 0.4.0+456 environment: sdk: ^3.5.4 From 51a2a789e7615549eeced90c274c03eaac6799a7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 18:52:03 +0200 Subject: [PATCH 062/182] Changed color --- lib/presentation/views/active_game_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 35404bb..9ecae1b 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -235,7 +235,8 @@ class _ActiveGameViewState extends State { child: Text( AppLocalizations.of(context).end_game, style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.red), + fontWeight: FontWeight.bold, + color: CupertinoColors.destructiveRed), ), onPressed: () { setState(() { From 89cdb4f824b65bb4f184d9cf7d82289ecbe8f1a9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 18:55:24 +0200 Subject: [PATCH 063/182] Ensured rating dialog wont show in Beta --- lib/presentation/views/main_menu_view.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 05c4a07..61ec010 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -40,7 +40,8 @@ class _MainMenuViewState extends State { WidgetsBinding.instance.addPostFrameCallback((_) async { await Constants.rateMyApp.init(); - if (Constants.rateMyApp.shouldOpenDialog) { + if (Constants.rateMyApp.shouldOpenDialog && + Constants.appDevPhase != 'Beta') { await Future.delayed(const Duration(milliseconds: 600)); if (!mounted) return; _handleFeedbackDialog(context); @@ -96,15 +97,13 @@ class _MainMenuViewState extends State { const SizedBox(height: 30), // Abstand von oben Center( child: GestureDetector( - onTap: () => _handleFeedbackDialog( - context) /*Navigator.push( + onTap: () => Navigator.push( context, CupertinoPageRoute( builder: (context) => const CreateGameView(), ), - )*/ - , + ), child: Icon( CupertinoIcons.plus, size: 60, From 5fc951844bd985de8110ab278a21b76cfca6a3e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:04:35 +0200 Subject: [PATCH 064/182] Refactoring --- lib/presentation/views/main_menu_view.dart | 171 ++++++++++----------- 1 file changed, 79 insertions(+), 92 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 61ec010..fac1d7c 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -21,11 +21,11 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; - static const int PRE_RATING_DIALOG_YES = 10; - static const int PRE_RATING_DIALOG_NO = 11; - static const int PRE_RATING_DIALOG_CANCEL = 12; - static const int BAD_RATING_DIALOG_EMAIL = 20; - static const int BAD_RATING_DIALOG_CANCEL = 21; + static const int PRE_RATING_DIALOG_YES = 1; + static const int PRE_RATING_DIALOG_NO = 0; + static const int PRE_RATING_DIALOG_CANCEL = -1; + static const int BAD_RATING_DIALOG_EMAIL = 1; + static const int BAD_RATING_DIALOG_CANCEL = 0; @override initState() { @@ -222,42 +222,6 @@ class _MainMenuViewState extends State { return AppLocalizations.of(context).unlimited; } - /// Shows a confirmation dialog to delete all game sessions. - /// Returns true if the user confirms the deletion, false otherwise. - /// [gameTitle] is the title of the game session to be deleted. - Future _showDeleteGamePopup(String gameTitle) async { - bool? shouldDelete = await showCupertinoDialog( - context: context, - builder: (context) { - return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).delete_game_title), - content: Text( - AppLocalizations.of(context).delete_game_message(gameTitle)), - actions: [ - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, false); - }, - child: Text(AppLocalizations.of(context).cancel), - ), - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, true); - }, - child: Text( - AppLocalizations.of(context).delete, - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.red), - ), - ), - ], - ); - }, - ) ?? - false; - return shouldDelete; - } - /// Handles the feedback dialog when the conditions for rating are met. /// It shows a dialog asking the user if they like the app, /// and based on their response, it either opens the rating dialog or an email client for feedback. @@ -295,43 +259,76 @@ class _MainMenuViewState extends State { } } + /// Shows a Cupertino dialog with a title, content, and a list of actions. + Future _showCupertinoChoiceDialog({ + required String title, + required String content, + required List<({String label, VoidCallback onPressed})> actions, + }) { + return showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text(title), + content: Text(content), + actions: actions + .map((action) => CupertinoDialogAction( + onPressed: action.onPressed, + child: Text(action.label), + )) + .toList(), + ); + }, + ); + } + + /// Shows a confirmation dialog to delete all game sessions. + /// Returns true if the user confirms the deletion, false otherwise. + /// [gameTitle] is the title of the game session to be deleted. + Future _showDeleteGamePopup(String gameTitle) async { + return await _showCupertinoChoiceDialog( + title: AppLocalizations.of(context).delete_game_title, + content: AppLocalizations.of(context).delete_game_message(gameTitle), + actions: [ + ( + label: AppLocalizations.of(context).cancel, + onPressed: () => Navigator.of(context).pop(false) + ), + ( + label: AppLocalizations.of(context).delete, + onPressed: () => Navigator.of(context).pop(true) + ), + ], + ) ?? + false; + } + /// Shows a dialog asking the user if they like the app. /// Returns the user's decision as an integer. /// - PRE_RATING_DIALOG_YES: User likes the app and wants to rate it. /// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback. /// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog. Future _showPreRatingDialog(BuildContext context) async { - int? answer = await showCupertinoDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).pre_rating_title), - content: Text(AppLocalizations.of(context).pre_rating_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).yes), - onPressed: () { - Navigator.of(context).pop(PRE_RATING_DIALOG_YES); - }, + return await _showCupertinoChoiceDialog( + title: AppLocalizations.of(context).pre_rating_title, + content: AppLocalizations.of(context).pre_rating_message, + actions: [ + ( + label: AppLocalizations.of(context).yes, + onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_YES) ), - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).no), - onPressed: () { - Navigator.of(context).pop(PRE_RATING_DIALOG_NO); - }, + ( + label: AppLocalizations.of(context).no, + onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_NO) ), - CupertinoDialogAction( - isDestructiveAction: true, - onPressed: () { - Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL); - }, - child: Text(AppLocalizations.of(context).cancel), + ( + label: AppLocalizations.of(context).cancel, + onPressed: () => + Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL) ), ], - ); - }, - ); - return answer ?? PRE_RATING_DIALOG_CANCEL; + ) ?? + PRE_RATING_DIALOG_CANCEL; } /// Shows a dialog asking the user for feedback if they do not like the app. @@ -339,33 +336,23 @@ class _MainMenuViewState extends State { /// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback. /// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog. Future _showBadRatingDialog(BuildContext context) async { - int? answer = await showCupertinoDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).bad_rating_title), - content: Text(AppLocalizations.of(context).bad_rating_message), - actions: [ - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).contact_email, - style: const TextStyle(fontWeight: FontWeight.bold)), - onPressed: () { - Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL); - }, + return await _showCupertinoChoiceDialog( + title: AppLocalizations.of(context).bad_rating_title, + content: AppLocalizations.of(context).bad_rating_message, + actions: [ + ( + label: AppLocalizations.of(context).contact_email, + onPressed: () => + Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL) ), - CupertinoDialogAction( - child: Text(AppLocalizations.of(context).cancel, - style: - const TextStyle(color: CupertinoColors.destructiveRed)), - onPressed: () { - Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL); - }, + ( + label: AppLocalizations.of(context).cancel, + onPressed: () => + Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL) ), ], - ); - }, - ); - return answer ?? PRE_RATING_DIALOG_CANCEL; + ) ?? + BAD_RATING_DIALOG_CANCEL; } @override From a9584a531ddb9ede73bd2f181066574b3cba2cee Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:08:44 +0200 Subject: [PATCH 065/182] Adding const --- lib/core/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 10b062d..0d32ceb 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,7 +1,7 @@ import 'package:rate_my_app/rate_my_app.dart'; class Constants { - static String appDevPhase = 'Beta'; + static const String appDevPhase = 'Beta'; static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; From 7e76af7a2068ee82f35f4969ad5dc66106154918 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:10:04 +0200 Subject: [PATCH 066/182] Renamed variables --- lib/presentation/views/main_menu_view.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index fac1d7c..979c854 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -236,19 +236,21 @@ class _MainMenuViewState extends State { '&body=$emailBody', ); - int decision = await _showPreRatingDialog(context); - int decision2 = 0; + int preRatingDecision = await _showPreRatingDialog(context); + int badRatingDecision = BAD_RATING_DIALOG_CANCEL; // so that the bad rating dialog is not shown immediately await Future.delayed(const Duration(milliseconds: 300)); - switch (decision) { + switch (preRatingDecision) { case PRE_RATING_DIALOG_YES: if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); break; case PRE_RATING_DIALOG_NO: - if (context.mounted) decision2 = await _showBadRatingDialog(context); - if (decision2 == BAD_RATING_DIALOG_EMAIL) { + if (context.mounted) { + badRatingDecision = await _showBadRatingDialog(context); + } + if (badRatingDecision == BAD_RATING_DIALOG_EMAIL) { if (context.mounted) { launchUrl(emailUri); } From d3693c20f12f9e5fa031116c33adad25768537b1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:10:49 +0200 Subject: [PATCH 067/182] Corrected links --- lib/core/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 0d32ceb..5b6e90e 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -4,7 +4,7 @@ class Constants { static const String appDevPhase = 'Beta'; static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; - static const String GITHUB_LINK = 'https://github1.felixkirchner.de'; + static const String GITHUB_LINK = 'https://github.felixkirchner.de'; static const String GITHUB_ISSUES_LINK = 'https://cabocounter-issues.felixkirchner.de'; static const String GITHUB_WIKI_LINK = From b5ef2df0c6880f919af9511e370cef03586e7101 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:16:57 +0200 Subject: [PATCH 068/182] updated Dialog function --- lib/presentation/views/main_menu_view.dart | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 979c854..09e4c26 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -265,7 +265,7 @@ class _MainMenuViewState extends State { Future _showCupertinoChoiceDialog({ required String title, required String content, - required List<({String label, VoidCallback onPressed})> actions, + required List<({Widget content, VoidCallback onPressed})> actions, }) { return showCupertinoDialog( context: context, @@ -276,7 +276,7 @@ class _MainMenuViewState extends State { actions: actions .map((action) => CupertinoDialogAction( onPressed: action.onPressed, - child: Text(action.label), + child: action.content, )) .toList(), ); @@ -293,13 +293,17 @@ class _MainMenuViewState extends State { content: AppLocalizations.of(context).delete_game_message(gameTitle), actions: [ ( - label: AppLocalizations.of(context).cancel, + content: Text(AppLocalizations.of(context).cancel), onPressed: () => Navigator.of(context).pop(false) ), ( - label: AppLocalizations.of(context).delete, - onPressed: () => Navigator.of(context).pop(true) - ), + content: Text(AppLocalizations.of(context).delete, + style: const TextStyle( + color: CupertinoColors.destructiveRed, + fontWeight: FontWeight.bold, + )), + onPressed: () => Navigator.of(context).pop(false) + ) ], ) ?? false; @@ -316,15 +320,15 @@ class _MainMenuViewState extends State { content: AppLocalizations.of(context).pre_rating_message, actions: [ ( - label: AppLocalizations.of(context).yes, + content: Text(AppLocalizations.of(context).yes), onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_YES) ), ( - label: AppLocalizations.of(context).no, + content: Text(AppLocalizations.of(context).no), onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_NO) ), ( - label: AppLocalizations.of(context).cancel, + content: Text(AppLocalizations.of(context).cancel), onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL) ), @@ -343,12 +347,12 @@ class _MainMenuViewState extends State { content: AppLocalizations.of(context).bad_rating_message, actions: [ ( - label: AppLocalizations.of(context).contact_email, + content: Text(AppLocalizations.of(context).contact_email), onPressed: () => Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL) ), ( - label: AppLocalizations.of(context).cancel, + content: Text(AppLocalizations.of(context).cancel), onPressed: () => Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL) ), From ac6db7fdd90d13792059c730a2e1d928b97744ea Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:26:58 +0200 Subject: [PATCH 069/182] Added version number in about view --- .../views/{information_view.dart => about_view.dart} | 11 ++++++++--- lib/presentation/views/tab_view.dart | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) rename lib/presentation/views/{information_view.dart => about_view.dart} (87%) diff --git a/lib/presentation/views/information_view.dart b/lib/presentation/views/about_view.dart similarity index 87% rename from lib/presentation/views/information_view.dart rename to lib/presentation/views/about_view.dart index 712d709..128f91f 100644 --- a/lib/presentation/views/information_view.dart +++ b/lib/presentation/views/about_view.dart @@ -1,12 +1,13 @@ import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/services/version_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; -class InformationView extends StatelessWidget { - const InformationView({super.key}); +class AboutView extends StatelessWidget { + const AboutView({super.key}); @override Widget build(BuildContext context) { @@ -29,9 +30,13 @@ class InformationView extends StatelessWidget { ), ), ), + Text( + '${AppLocalizations.of(context).app_version} ${VersionService.getVersionWithBuild()}', + style: TextStyle(fontSize: 15, color: Colors.grey[300]), + ), Padding( padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 30), + const EdgeInsets.symmetric(horizontal: 20, vertical: 15), child: SizedBox( height: 200, child: Image.asset('assets/cabo_counter-logo_rounded.png'), diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index 15d4637..0c98cc7 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,6 +1,6 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; -import 'package:cabo_counter/presentation/views/information_view.dart'; +import 'package:cabo_counter/presentation/views/about_view.dart'; import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:flutter/cupertino.dart'; @@ -39,7 +39,7 @@ class _TabViewState extends State { if (index == 0) { return const MainMenuView(); } else { - return const InformationView(); + return const AboutView(); } }); }, diff --git a/pubspec.yaml b/pubspec.yaml index 4625269..52ae1fc 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.4.0+456 +version: 0.4.0+459 environment: sdk: ^3.5.4 From 034ce19c60dbca5425f1c28549561dc4d3fa3258 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:36:17 +0200 Subject: [PATCH 070/182] Changed order and corrected return --- lib/presentation/views/main_menu_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 09e4c26..1e8a67a 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -292,16 +292,16 @@ class _MainMenuViewState extends State { title: AppLocalizations.of(context).delete_game_title, content: AppLocalizations.of(context).delete_game_message(gameTitle), actions: [ - ( - content: Text(AppLocalizations.of(context).cancel), - onPressed: () => Navigator.of(context).pop(false) - ), ( content: Text(AppLocalizations.of(context).delete, style: const TextStyle( color: CupertinoColors.destructiveRed, fontWeight: FontWeight.bold, )), + onPressed: () => Navigator.of(context).pop(true) + ), + ( + content: Text(AppLocalizations.of(context).cancel), onPressed: () => Navigator.of(context).pop(false) ) ], From 3a74ca0d9801bfe69e174d4224063d5fcf2f8d02 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 19:37:53 +0200 Subject: [PATCH 071/182] Changed translation --- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 3f24635..467135e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -36,7 +36,7 @@ "no": "No", "bad_rating_title": "Not satisfied?", "bad_rating_message": "If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.", - "contact_email": "Contac vía E-Mail", + "contact_email": "Contac via E-Mail", "email_subject": "Feedback: Cabo Counter App", "email_body": "I have the following feedback...", diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index d3e433b..a986058 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -92,7 +92,7 @@ class AppLocalizationsEn extends AppLocalizations { 'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.'; @override - String get contact_email => 'Contac vía E-Mail'; + String get contact_email => 'Contac via E-Mail'; @override String get email_subject => 'Feedback: Cabo Counter App'; diff --git a/pubspec.yaml b/pubspec.yaml index 52ae1fc..33ef0fe 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.4.0+459 +version: 0.4.0+461 environment: sdk: ^3.5.4 From 5ede7e92780efa001326b6422e335f0c04225915 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:03:58 +0200 Subject: [PATCH 072/182] Changed popups because of unmounted context errors --- lib/presentation/views/main_menu_view.dart | 166 ++++++++++----------- pubspec.yaml | 2 +- 2 files changed, 82 insertions(+), 86 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 1e8a67a..2a95346 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -75,14 +75,12 @@ class _MainMenuViewState extends State { icon: const Icon(CupertinoIcons.settings, size: 30)), middle: const Text('Cabo Counter'), trailing: IconButton( - onPressed: () => { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const CreateGameView(), - ), - ) - }, + onPressed: () => Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const CreateGameView(), + ), + ), icon: const Icon(CupertinoIcons.add)), ), child: CupertinoPageScaffold( @@ -146,7 +144,7 @@ class _MainMenuViewState extends State { final String gameTitle = gameManager .gameList[index].gameTitle; return await _showDeleteGamePopup( - gameTitle); + gameTitle, context); }, onDismissed: (direction) { gameManager @@ -261,50 +259,37 @@ class _MainMenuViewState extends State { } } - /// Shows a Cupertino dialog with a title, content, and a list of actions. - Future _showCupertinoChoiceDialog({ - required String title, - required String content, - required List<({Widget content, VoidCallback onPressed})> actions, - }) { - return showCupertinoDialog( - context: context, - builder: (BuildContext context) { - return CupertinoAlertDialog( - title: Text(title), - content: Text(content), - actions: actions - .map((action) => CupertinoDialogAction( - onPressed: action.onPressed, - child: action.content, - )) - .toList(), - ); - }, - ); - } - /// Shows a confirmation dialog to delete all game sessions. /// Returns true if the user confirms the deletion, false otherwise. /// [gameTitle] is the title of the game session to be deleted. - Future _showDeleteGamePopup(String gameTitle) async { - return await _showCupertinoChoiceDialog( - title: AppLocalizations.of(context).delete_game_title, - content: AppLocalizations.of(context).delete_game_message(gameTitle), - actions: [ - ( - content: Text(AppLocalizations.of(context).delete, - style: const TextStyle( - color: CupertinoColors.destructiveRed, - fontWeight: FontWeight.bold, - )), - onPressed: () => Navigator.of(context).pop(true) - ), - ( - content: Text(AppLocalizations.of(context).cancel), - onPressed: () => Navigator.of(context).pop(false) - ) - ], + Future _showDeleteGamePopup( + String gameTitle, BuildContext context) async { + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: Text( + AppLocalizations.of(context).delete_game_title, + ), + content: Text(AppLocalizations.of(context) + .delete_game_message(gameTitle)), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(false); + }), + CupertinoDialogAction( + isDestructiveAction: true, + isDefaultAction: true, + child: Text( + AppLocalizations.of(context).delete, + ), + onPressed: () { + Navigator.of(context).pop(true); + }) + ]); + }, ) ?? false; } @@ -315,25 +300,32 @@ class _MainMenuViewState extends State { /// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback. /// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog. Future _showPreRatingDialog(BuildContext context) async { - return await _showCupertinoChoiceDialog( - title: AppLocalizations.of(context).pre_rating_title, - content: AppLocalizations.of(context).pre_rating_message, - actions: [ - ( - content: Text(AppLocalizations.of(context).yes), - onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_YES) - ), - ( - content: Text(AppLocalizations.of(context).no), - onPressed: () => Navigator.of(context).pop(PRE_RATING_DIALOG_NO) - ), - ( - content: Text(AppLocalizations.of(context).cancel), - onPressed: () => - Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL) - ), - ], - ) ?? + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).pre_rating_title), + content: + Text(AppLocalizations.of(context).pre_rating_message), + actions: [ + CupertinoDialogAction( + onPressed: () => + Navigator.of(context).pop(PRE_RATING_DIALOG_YES), + isDefaultAction: true, + child: Text(AppLocalizations.of(context).yes), + ), + CupertinoDialogAction( + onPressed: () => + Navigator.of(context).pop(PRE_RATING_DIALOG_NO), + child: Text(AppLocalizations.of(context).no), + ), + CupertinoDialogAction( + onPressed: () => + Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL), + isDestructiveAction: true, + child: Text(AppLocalizations.of(context).cancel), + ) + ], + )) ?? PRE_RATING_DIALOG_CANCEL; } @@ -342,22 +334,26 @@ class _MainMenuViewState extends State { /// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback. /// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog. Future _showBadRatingDialog(BuildContext context) async { - return await _showCupertinoChoiceDialog( - title: AppLocalizations.of(context).bad_rating_title, - content: AppLocalizations.of(context).bad_rating_message, - actions: [ - ( - content: Text(AppLocalizations.of(context).contact_email), - onPressed: () => - Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL) - ), - ( - content: Text(AppLocalizations.of(context).cancel), - onPressed: () => - Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL) - ), - ], - ) ?? + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).bad_rating_title), + content: + Text(AppLocalizations.of(context).bad_rating_message), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () => + Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL), + child: Text(AppLocalizations.of(context).contact_email), + ), + CupertinoDialogAction( + isDestructiveAction: true, + onPressed: () => + Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL), + child: Text(AppLocalizations.of(context).cancel)) + ], + )) ?? BAD_RATING_DIALOG_CANCEL; } diff --git a/pubspec.yaml b/pubspec.yaml index 33ef0fe..31146e6 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.4.0+461 +version: 0.4.0+467 environment: sdk: ^3.5.4 From f3114c6c32a3d1633b44a6d330a8471d89ed541e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:04:11 +0200 Subject: [PATCH 073/182] corrected string typo --- lib/l10n/arb/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 467135e..e01e242 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -36,7 +36,7 @@ "no": "No", "bad_rating_title": "Not satisfied?", "bad_rating_message": "If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.", - "contact_email": "Contac via E-Mail", + "contact_email": "Contact via E-Mail", "email_subject": "Feedback: Cabo Counter App", "email_body": "I have the following feedback...", From a8ca853c24c24226bc6475da9c99b1fdc0ee0049 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:12:58 +0200 Subject: [PATCH 074/182] Replaced int constants with enums --- lib/presentation/views/main_menu_view.dart | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 2a95346..ee36d1f 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -11,6 +11,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; +enum PreRatingDialogDecision { yes, no, cancel } + +enum BadRatingDialogDecision { email, cancel } + class MainMenuView extends StatefulWidget { const MainMenuView({super.key}); @@ -21,11 +25,6 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; - static const int PRE_RATING_DIALOG_YES = 1; - static const int PRE_RATING_DIALOG_NO = 0; - static const int PRE_RATING_DIALOG_CANCEL = -1; - static const int BAD_RATING_DIALOG_EMAIL = 1; - static const int BAD_RATING_DIALOG_CANCEL = 0; @override initState() { @@ -234,27 +233,28 @@ class _MainMenuViewState extends State { '&body=$emailBody', ); - int preRatingDecision = await _showPreRatingDialog(context); - int badRatingDecision = BAD_RATING_DIALOG_CANCEL; + PreRatingDialogDecision preRatingDecision = + await _showPreRatingDialog(context); + BadRatingDialogDecision badRatingDecision = BadRatingDialogDecision.cancel; // so that the bad rating dialog is not shown immediately await Future.delayed(const Duration(milliseconds: 300)); switch (preRatingDecision) { - case PRE_RATING_DIALOG_YES: + case PreRatingDialogDecision.yes: if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); break; - case PRE_RATING_DIALOG_NO: + case PreRatingDialogDecision.no: if (context.mounted) { badRatingDecision = await _showBadRatingDialog(context); } - if (badRatingDecision == BAD_RATING_DIALOG_EMAIL) { + if (badRatingDecision == BadRatingDialogDecision.email) { if (context.mounted) { launchUrl(emailUri); } } break; - case PRE_RATING_DIALOG_CANCEL: + case PreRatingDialogDecision.cancel: break; } } @@ -262,7 +262,7 @@ class _MainMenuViewState extends State { /// Shows a confirmation dialog to delete all game sessions. /// Returns true if the user confirms the deletion, false otherwise. /// [gameTitle] is the title of the game session to be deleted. - Future _showDeleteGamePopup( + Future _showDeleteGamePopup( String gameTitle, BuildContext context) async { return await showCupertinoDialog( context: context, @@ -299,8 +299,9 @@ class _MainMenuViewState extends State { /// - PRE_RATING_DIALOG_YES: User likes the app and wants to rate it. /// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback. /// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog. - Future _showPreRatingDialog(BuildContext context) async { - return await showCupertinoDialog( + Future _showPreRatingDialog( + BuildContext context) async { + return await showCupertinoDialog( context: context, builder: (BuildContext context) => CupertinoAlertDialog( title: Text(AppLocalizations.of(context).pre_rating_title), @@ -308,33 +309,33 @@ class _MainMenuViewState extends State { Text(AppLocalizations.of(context).pre_rating_message), actions: [ CupertinoDialogAction( - onPressed: () => - Navigator.of(context).pop(PRE_RATING_DIALOG_YES), + onPressed: () => Navigator.of(context) + .pop(PreRatingDialogDecision.yes), isDefaultAction: true, child: Text(AppLocalizations.of(context).yes), ), CupertinoDialogAction( onPressed: () => - Navigator.of(context).pop(PRE_RATING_DIALOG_NO), + Navigator.of(context).pop(PreRatingDialogDecision.no), child: Text(AppLocalizations.of(context).no), ), CupertinoDialogAction( - onPressed: () => - Navigator.of(context).pop(PRE_RATING_DIALOG_CANCEL), + onPressed: () => Navigator.of(context).pop(), isDestructiveAction: true, child: Text(AppLocalizations.of(context).cancel), ) ], )) ?? - PRE_RATING_DIALOG_CANCEL; + PreRatingDialogDecision.cancel; } /// Shows a dialog asking the user for feedback if they do not like the app. /// Returns the user's decision as an integer. /// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback. /// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog. - Future _showBadRatingDialog(BuildContext context) async { - return await showCupertinoDialog( + Future _showBadRatingDialog( + BuildContext context) async { + return await showCupertinoDialog( context: context, builder: (BuildContext context) => CupertinoAlertDialog( title: Text(AppLocalizations.of(context).bad_rating_title), @@ -343,18 +344,17 @@ class _MainMenuViewState extends State { actions: [ CupertinoDialogAction( isDefaultAction: true, - onPressed: () => - Navigator.of(context).pop(BAD_RATING_DIALOG_EMAIL), + onPressed: () => Navigator.of(context) + .pop(BadRatingDialogDecision.email), child: Text(AppLocalizations.of(context).contact_email), ), CupertinoDialogAction( isDestructiveAction: true, - onPressed: () => - Navigator.of(context).pop(BAD_RATING_DIALOG_CANCEL), + onPressed: () => Navigator.of(context).pop(), child: Text(AppLocalizations.of(context).cancel)) ], )) ?? - BAD_RATING_DIALOG_CANCEL; + BadRatingDialogDecision.cancel; } @override From 2f225c9b23412245a94aac5996b4964b34ea1979 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:13:54 +0200 Subject: [PATCH 075/182] Renamed Stepper to CustomStepper --- lib/presentation/views/settings_view.dart | 6 +++--- lib/presentation/widgets/custom_form_row.dart | 4 ++-- .../widgets/{stepper.dart => custom_stepper.dart} | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) rename lib/presentation/widgets/{stepper.dart => custom_stepper.dart} (90%) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index c9a409d..9bed7fc 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -2,7 +2,7 @@ import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; -import 'package:cabo_counter/presentation/widgets/stepper.dart'; +import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/version_service.dart'; @@ -54,7 +54,7 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: 'Cabo-Strafe', prefixIcon: CupertinoIcons.bolt_fill, - suffixWidget: Stepper( + suffixWidget: CustomStepper( key: _stepperKey1, initialValue: ConfigService.caboPenalty, minValue: 0, @@ -71,7 +71,7 @@ class _SettingsViewState extends State { CustomFormRow( prefixText: 'Punkte-Limit', prefixIcon: FontAwesomeIcons.bullseye, - suffixWidget: Stepper( + suffixWidget: CustomStepper( key: _stepperKey2, initialValue: ConfigService.pointLimit, minValue: 30, diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart index 96067c3..7a2526b 100644 --- a/lib/presentation/widgets/custom_form_row.dart +++ b/lib/presentation/widgets/custom_form_row.dart @@ -1,5 +1,5 @@ import 'package:cabo_counter/core/custom_theme.dart'; -import 'package:cabo_counter/presentation/widgets/stepper.dart'; +import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; import 'package:flutter/cupertino.dart'; class CustomFormRow extends StatefulWidget { @@ -43,7 +43,7 @@ class _CustomFormRowState extends State { Text(widget.prefixText), ], ), - padding: suffixWidget is Stepper + padding: suffixWidget is CustomStepper ? const EdgeInsets.fromLTRB(15, 0, 0, 0) : const EdgeInsets.symmetric(vertical: 10, horizontal: 15), child: suffixWidget, diff --git a/lib/presentation/widgets/stepper.dart b/lib/presentation/widgets/custom_stepper.dart similarity index 90% rename from lib/presentation/widgets/stepper.dart rename to lib/presentation/widgets/custom_stepper.dart index 5d0bce8..a05a4cb 100644 --- a/lib/presentation/widgets/stepper.dart +++ b/lib/presentation/widgets/custom_stepper.dart @@ -1,13 +1,13 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:flutter/cupertino.dart'; // Für iOS-Style -class Stepper extends StatefulWidget { +class CustomStepper extends StatefulWidget { final int minValue; final int maxValue; final int? initialValue; final int step; final ValueChanged onChanged; - const Stepper({ + const CustomStepper({ super.key, required this.minValue, required this.maxValue, @@ -18,10 +18,10 @@ class Stepper extends StatefulWidget { @override // ignore: library_private_types_in_public_api - _StepperState createState() => _StepperState(); + _CustomStepperState createState() => _CustomStepperState(); } -class _StepperState extends State { +class _CustomStepperState extends State { late int _value; @override From 1c505916f3a42a2a3fda44af57b8047ed6c3788c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:14:50 +0200 Subject: [PATCH 076/182] Changed argument order --- lib/presentation/views/main_menu_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index ee36d1f..f6c1a32 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -143,7 +143,7 @@ class _MainMenuViewState extends State { final String gameTitle = gameManager .gameList[index].gameTitle; return await _showDeleteGamePopup( - gameTitle, context); + context, gameTitle); }, onDismissed: (direction) { gameManager @@ -263,7 +263,7 @@ class _MainMenuViewState extends State { /// Returns true if the user confirms the deletion, false otherwise. /// [gameTitle] is the title of the game session to be deleted. Future _showDeleteGamePopup( - String gameTitle, BuildContext context) async { + BuildContext context, String gameTitle) async { return await showCupertinoDialog( context: context, builder: (BuildContext context) { From 964c179e6929d6274b4a22de83d67cd9bf84dd9c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 10 Jul 2025 20:46:11 +0200 Subject: [PATCH 077/182] Reordered properties --- lib/presentation/views/main_menu_view.dart | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index f6c1a32..1f8b5d0 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -275,19 +275,21 @@ class _MainMenuViewState extends State { .delete_game_message(gameTitle)), actions: [ CupertinoDialogAction( - child: Text(AppLocalizations.of(context).cancel), - onPressed: () { - Navigator.of(context).pop(false); - }), + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(AppLocalizations.of(context).cancel), + ), CupertinoDialogAction( - isDestructiveAction: true, - isDefaultAction: true, - child: Text( - AppLocalizations.of(context).delete, - ), - onPressed: () { - Navigator.of(context).pop(true); - }) + isDestructiveAction: true, + isDefaultAction: true, + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + AppLocalizations.of(context).delete, + ), + ) ]); }, ) ?? From b3c70f711ad98be4f8a37c4aadfdb99c55d93c66 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:18:29 +0200 Subject: [PATCH 078/182] Implemented empty builder for GraphView --- lib/l10n/arb/app_de.arb | 1 + lib/l10n/arb/app_en.arb | 6 ++- lib/l10n/generated/app_localizations.dart | 6 +++ lib/l10n/generated/app_localizations_de.dart | 4 ++ lib/l10n/generated/app_localizations_en.dart | 8 +++- lib/presentation/views/graph_view.dart | 46 ++++++++++++++------ pubspec.yaml | 2 +- 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index d89399b..acff3c1 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -85,6 +85,7 @@ "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", "game_process": "Spielverlauf", + "empty_graph_text": "Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e01e242..4a0735a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -74,18 +74,20 @@ "done": "Done", "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", - - "game_process": "Spielverlauf", "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.", + "game_process": "Scoring History", + "empty_graph_text": "You must play at least two rounds for the game progress graph to be displayed.", + "settings": "Settings", "cabo_penalty": "Cabo Penalty", "cabo_penalty_subtitle": "... for falsely calling Cabo.", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index af778ae..c139891 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -476,6 +476,12 @@ abstract class AppLocalizations { /// **'Spielverlauf'** String get game_process; + /// No description provided for @empty_graph_text. + /// + /// In de, this message translates to: + /// **'Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'** + String get empty_graph_text; + /// No description provided for @settings. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index afe558f..b61feb0 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -210,6 +210,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get game_process => 'Spielverlauf'; + @override + String get empty_graph_text => + 'Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'; + @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index a986058..fcfd494 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -92,7 +92,7 @@ class AppLocalizationsEn extends AppLocalizations { 'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.'; @override - String get contact_email => 'Contac via E-Mail'; + String get contact_email => 'Contact via E-Mail'; @override String get email_subject => 'Feedback: Cabo Counter App'; @@ -205,7 +205,11 @@ class AppLocalizationsEn extends AppLocalizations { 'Do you want to end the game? The game gets marked as finished and cannot be continued.'; @override - String get game_process => 'Spielverlauf'; + String get game_process => 'Scoring History'; + + @override + String get empty_graph_text => + 'You must play at least two rounds for the game progress graph to be displayed.'; @override String get settings => 'Settings'; diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 1007007..9ee0bbc 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -26,21 +26,39 @@ class _GraphViewState extends State { @override Widget build(BuildContext context) { return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(AppLocalizations.of(context).game_process), - previousPageTitle: AppLocalizations.of(context).back, - ), - 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(), + navigationBar: CupertinoNavigationBar( + middle: Text(AppLocalizations.of(context).game_process), + previousPageTitle: AppLocalizations.of(context).back, ), - ), - ); + child: widget.gameSession.roundNumber > 2 + ? 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(), + ), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Center( + child: Icon(CupertinoIcons.chart_bar_alt_fill, size: 60), + ), + const SizedBox(height: 10), // Abstand von oben + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Text( + AppLocalizations.of(context).empty_graph_text, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + ), + ], + )); } /// Returns a list of LineSeries representing the cumulative scores of each player. diff --git a/pubspec.yaml b/pubspec.yaml index 31146e6..802d487 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.4.0+467 +version: 0.4.0+470 environment: sdk: ^3.5.4 From 668328300ad4c4821529b1b17da7320af3ee91bc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:34:39 +0200 Subject: [PATCH 079/182] Added jitterStip to prevent the graphs overlaying each other --- lib/core/custom_theme.dart | 7 ++++++ lib/presentation/views/graph_view.dart | 34 ++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 77a2f5b..259d5fd 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -6,6 +6,13 @@ class CustomTheme { static Color backgroundColor = const Color(0xFF101010); static Color backgroundTintColor = CupertinoColors.darkBackgroundGray; + // graph colors + static const Color graphColor1 = Color(0xFFF44336); + static const Color graphColor2 = Color(0xFF2196F3); //FF2196F3 + static const Color graphColor3 = Color(0xFFFFA726); //FFFFA726 + static const Color graphColor4 = Color(0xFF9C27B0); //9C27B0 + static final Color graphColor5 = primaryColor; + static TextStyle modeTitle = TextStyle( color: primaryColor, fontSize: 20, diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 9ee0bbc..1e91a98 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -1,7 +1,7 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; class GraphView extends StatefulWidget { @@ -15,12 +15,12 @@ class GraphView extends StatefulWidget { class _GraphViewState extends State { /// List of colors for the graph lines. - List lineColors = [ - Colors.red, - Colors.blue, - Colors.orange.shade400, - Colors.purple, - Colors.green, + final List lineColors = [ + CustomTheme.graphColor1, + CustomTheme.graphColor2, + CustomTheme.graphColor3, + CustomTheme.graphColor4, + CustomTheme.graphColor5 ]; @override @@ -36,7 +36,10 @@ class _GraphViewState extends State { child: SfCartesianChart( legend: const Legend( isVisible: true, position: LegendPosition.bottom), - primaryXAxis: const NumericAxis(), + primaryXAxis: const NumericAxis( + interval: 1, + decimalPlaces: 0, + ), primaryYAxis: const NumericAxis(), series: getCumulativeScores(), ), @@ -64,7 +67,7 @@ class _GraphViewState extends State { /// 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() { + List> getCumulativeScores() { final rounds = widget.gameSession.roundList; final playerCount = widget.gameSession.players.length; final playerNames = widget.gameSession.players; @@ -79,21 +82,26 @@ class _GraphViewState extends State { } } + const double jitterStep = 0.15; + /// 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) + (j) => ( + j + 1, + cumulativeScores[i][j] + (i - playerCount ~/ 2) * jitterStep + ), ); /// Create a LineSeries for the player /// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score. - return LineSeries<(int, int), int>( + return LineSeries<(int, num), int>( name: playerNames[i], dataSource: data, - xValueMapper: (record, _) => record.$1, // Runde - yValueMapper: (record, _) => record.$2, // Punktestand + xValueMapper: (record, _) => record.$1, + yValueMapper: (record, _) => record.$2, markerSettings: const MarkerSettings(isVisible: true), color: lineColors[i], ); From 4448fd2c39df7a01f3f50f5a8e54351de8bf8580 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:44:21 +0200 Subject: [PATCH 080/182] Removed german comments --- lib/presentation/views/graph_view.dart | 2 +- lib/presentation/views/main_menu_view.dart | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 1e91a98..50fb12a 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -51,7 +51,7 @@ class _GraphViewState extends State { const Center( child: Icon(CupertinoIcons.chart_bar_alt_fill, size: 60), ), - const SizedBox(height: 10), // Abstand von oben + const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Text( diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 1f8b5d0..fa537c6 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -88,10 +88,9 @@ class _MainMenuViewState extends State { ? const Center(child: CupertinoActivityIndicator()) : gameManager.gameList.isEmpty ? Column( - mainAxisAlignment: - MainAxisAlignment.center, // Oben ausrichten + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 30), // Abstand von oben + const SizedBox(height: 30), Center( child: GestureDetector( onTap: () => Navigator.push( @@ -107,7 +106,7 @@ class _MainMenuViewState extends State { color: CustomTheme.primaryColor, ), )), - const SizedBox(height: 10), // Abstand von oben + const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 70), From 5fa8d1fa3de4ce8fb186a65c1dc8255b6f5d77d7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:49:19 +0200 Subject: [PATCH 081/182] Added comment to jitter calculation --- lib/presentation/views/graph_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 50fb12a..7736bb1 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -91,6 +91,9 @@ class _GraphViewState extends State { cumulativeScores[i].length, (j) => ( j + 1, + + // Add a small jitter to the cumulative scores to prevent overlapping data points in the graph. + // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. cumulativeScores[i][j] + (i - playerCount ~/ 2) * jitterStep ), ); From 1494cb08a5943af1e36c2918d94798a1d901a18e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:50:11 +0200 Subject: [PATCH 082/182] Overhauled comments in CustomTheme --- lib/core/custom_theme.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 259d5fd..a00340b 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -6,11 +6,11 @@ class CustomTheme { static Color backgroundColor = const Color(0xFF101010); static Color backgroundTintColor = CupertinoColors.darkBackgroundGray; - // graph colors + // Line Colors for GraphView static const Color graphColor1 = Color(0xFFF44336); - static const Color graphColor2 = Color(0xFF2196F3); //FF2196F3 - static const Color graphColor3 = Color(0xFFFFA726); //FFFFA726 - static const Color graphColor4 = Color(0xFF9C27B0); //9C27B0 + static const Color graphColor2 = Color(0xFF2196F3); + static const Color graphColor3 = Color(0xFFFFA726); + static const Color graphColor4 = Color(0xFF9C27B0); static final Color graphColor5 = primaryColor; static TextStyle modeTitle = TextStyle( From ebe3dd6954506cc25f06e0dd2b73198f2a269b44 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 10:52:47 +0200 Subject: [PATCH 083/182] Updated version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 802d487..fbacd1e 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.4.0+470 +version: 0.4.1+470 environment: sdk: ^3.5.4 From 55a61be44b2a3876a7b7fbee5ad244cf8f95d5ff Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:10:06 +0200 Subject: [PATCH 084/182] Added Delete all games button to Settings --- lib/l10n/arb/app_de.arb | 3 + lib/l10n/arb/app_en.arb | 3 + lib/l10n/generated/app_localizations.dart | 18 ++ lib/l10n/generated/app_localizations_de.dart | 10 + lib/l10n/generated/app_localizations_en.dart | 12 +- lib/presentation/views/settings_view.dart | 321 ++++++++++--------- pubspec.yaml | 2 +- 7 files changed, 222 insertions(+), 147 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index d89399b..113cce1 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -95,6 +95,9 @@ "game_data": "Spieldaten", "import_data": "Spieldaten importieren", "export_data": "Spieldaten exportieren", + "delete_data": "Alle Spieldaten löschen", + "delete_data_title": "Spieldaten löschen?", + "delete_data_message": "Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "app": "App", "import_success_title": "Import erfolgreich", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e01e242..1f5fe3c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -95,6 +95,9 @@ "game_data": "Game Data", "import_data": "Import Data", "export_data": "Export Data", + "delete_data": "Delete all Game Data", + "delete_data_title": "Delete game data?", + "delete_data_message": "Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "app": "App", "import_success_title": "Import successful", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index af778ae..979d46b 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -530,6 +530,24 @@ abstract class AppLocalizations { /// **'Spieldaten exportieren'** String get export_data; + /// No description provided for @delete_data. + /// + /// In de, this message translates to: + /// **'Alle Spieldaten löschen'** + String get delete_data; + + /// No description provided for @delete_data_title. + /// + /// In de, this message translates to: + /// **'Spieldaten löschen?'** + String get delete_data_title; + + /// No description provided for @delete_data_message. + /// + /// In de, this message translates to: + /// **'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** + String get delete_data_message; + /// No description provided for @app. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index afe558f..c4b2491 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -237,6 +237,16 @@ class AppLocalizationsDe extends AppLocalizations { @override String get export_data => 'Spieldaten exportieren'; + @override + String get delete_data => 'Alle Spieldaten löschen'; + + @override + String get delete_data_title => 'Spieldaten löschen?'; + + @override + String get delete_data_message => + 'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + @override String get app => 'App'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index a986058..3cbde9c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -92,7 +92,7 @@ class AppLocalizationsEn extends AppLocalizations { 'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.'; @override - String get contact_email => 'Contac via E-Mail'; + String get contact_email => 'Contact via E-Mail'; @override String get email_subject => 'Feedback: Cabo Counter App'; @@ -234,6 +234,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_data => 'Export Data'; + @override + String get delete_data => 'Delete all Game Data'; + + @override + String get delete_data_title => 'Delete game data?'; + + @override + String get delete_data_message => + 'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + @override String get app => 'App'; diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 9bed7fc..ab8ec7d 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -32,162 +32,193 @@ class _SettingsViewState extends State { middle: Text(AppLocalizations.of(context).settings), ), child: SafeArea( - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).points, - style: CustomTheme.rowTitle, - ), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).points, + style: CustomTheme.rowTitle, ), - Padding( - padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), - child: CupertinoFormSection.insetGrouped( - backgroundColor: CustomTheme.backgroundColor, - margin: EdgeInsets.zero, - children: [ - CustomFormRow( - prefixText: 'Cabo-Strafe', - prefixIcon: CupertinoIcons.bolt_fill, - suffixWidget: CustomStepper( - key: _stepperKey1, - initialValue: ConfigService.caboPenalty, - minValue: 0, - maxValue: 50, - step: 1, - onChanged: (newCaboPenalty) { - setState(() { - ConfigService.setCaboPenalty(newCaboPenalty); - ConfigService.caboPenalty = newCaboPenalty; - }); - }, - ), - ), - CustomFormRow( - prefixText: 'Punkte-Limit', - prefixIcon: FontAwesomeIcons.bullseye, - suffixWidget: CustomStepper( - key: _stepperKey2, - initialValue: ConfigService.pointLimit, - minValue: 30, - maxValue: 1000, - step: 10, - onChanged: (newPointLimit) { - setState(() { - ConfigService.setPointLimit(newPointLimit); - ConfigService.pointLimit = newPointLimit; - }); - }, - ), - ), - CustomFormRow( - prefixText: - AppLocalizations.of(context).reset_to_default, - prefixIcon: CupertinoIcons.arrow_counterclockwise, - onPressed: () { - ConfigService.resetConfig(); + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).cabo_penalty, + prefixIcon: CupertinoIcons.bolt_fill, + suffixWidget: CustomStepper( + key: _stepperKey1, + initialValue: ConfigService.caboPenalty, + minValue: 0, + maxValue: 50, + step: 1, + onChanged: (newCaboPenalty) { setState(() { - _stepperKey1 = UniqueKey(); - _stepperKey2 = UniqueKey(); + ConfigService.setCaboPenalty(newCaboPenalty); + ConfigService.caboPenalty = newCaboPenalty; }); }, - ) - ])), - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).game_data, - style: CustomTheme.rowTitle, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), - child: CupertinoFormSection.insetGrouped( - backgroundColor: CustomTheme.backgroundColor, - margin: EdgeInsets.zero, - children: [ - CustomFormRow( - prefixText: AppLocalizations.of(context).import_data, - prefixIcon: CupertinoIcons.square_arrow_down, - onPressed: () async { - final status = - await LocalStorageService.importJsonFile(); - showFeedbackDialog(status); + ), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).point_limit, + prefixIcon: FontAwesomeIcons.bullseye, + suffixWidget: CustomStepper( + key: _stepperKey2, + initialValue: ConfigService.pointLimit, + minValue: 30, + maxValue: 1000, + step: 10, + onChanged: (newPointLimit) { + setState(() { + ConfigService.setPointLimit(newPointLimit); + ConfigService.pointLimit = newPointLimit; + }); }, - suffixWidget: const CupertinoListTileChevron(), ), - CustomFormRow( - prefixText: AppLocalizations.of(context).export_data, - prefixIcon: CupertinoIcons.square_arrow_up, - onPressed: () => LocalStorageService.exportGameData(), - suffixWidget: const CupertinoListTileChevron(), - ), - ])), - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).app, - style: CustomTheme.rowTitle, - ), + ), + CustomFormRow( + prefixText: + AppLocalizations.of(context).reset_to_default, + prefixIcon: CupertinoIcons.arrow_counterclockwise, + onPressed: () { + ConfigService.resetConfig(); + setState(() { + _stepperKey1 = UniqueKey(); + _stepperKey2 = UniqueKey(); + }); + }, + ) + ])), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).game_data, + style: CustomTheme.rowTitle, ), - Padding( - padding: const EdgeInsets.fromLTRB(10, 15, 10, 0), - child: CupertinoFormSection.insetGrouped( - backgroundColor: CustomTheme.backgroundColor, - margin: EdgeInsets.zero, - children: [ - CustomFormRow( - prefixText: AppLocalizations.of(context).wiki, - prefixIcon: CupertinoIcons.book, - onPressed: () => - launchUrl(Uri.parse(Constants.GITHUB_WIKI_LINK)), - suffixWidget: const CupertinoListTileChevron(), - ), - CustomFormRow( - prefixText: - AppLocalizations.of(context).privacy_policy, - prefixIcon: CupertinoIcons.doc_append, - onPressed: () => launchUrl( - Uri.parse(Constants.PRIVACY_POLICY_LINK)), - suffixWidget: const CupertinoListTileChevron(), - ), - CustomFormRow( - prefixText: AppLocalizations.of(context).error_found, - prefixIcon: FontAwesomeIcons.github, - onPressed: () => launchUrl( - Uri.parse(Constants.GITHUB_ISSUES_LINK)), - suffixWidget: const CupertinoListTileChevron(), - ), - CustomFormRow( - prefixText: - AppLocalizations.of(context).app_version, - prefixIcon: CupertinoIcons.tag, - onPressed: null, - suffixWidget: Text(VersionService.getVersion(), - style: TextStyle( - color: CustomTheme.primaryColor, - ))), - CustomFormRow( - prefixText: AppLocalizations.of(context).build, - prefixIcon: CupertinoIcons.number, - onPressed: null, - suffixWidget: Text(VersionService.getBuildNumber(), - style: TextStyle( - color: CustomTheme.primaryColor, - ))), - ])), - ], - ), - ], + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).import_data, + prefixIcon: CupertinoIcons.square_arrow_down, + onPressed: () async { + final status = + await LocalStorageService.importJsonFile(); + showFeedbackDialog(status); + }, + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).export_data, + prefixIcon: CupertinoIcons.square_arrow_up, + onPressed: () => LocalStorageService.exportGameData(), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).delete_data, + prefixIcon: CupertinoIcons.trash, + onPressed: () => _deleteAllGames(), + ), + ])), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).app, + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 0), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).wiki, + prefixIcon: CupertinoIcons.book, + onPressed: () => + launchUrl(Uri.parse(Constants.GITHUB_WIKI_LINK)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).privacy_policy, + prefixIcon: CupertinoIcons.doc_append, + onPressed: () => + launchUrl(Uri.parse(Constants.PRIVACY_POLICY_LINK)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).error_found, + prefixIcon: FontAwesomeIcons.github, + onPressed: () => + launchUrl(Uri.parse(Constants.GITHUB_ISSUES_LINK)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).app_version, + prefixIcon: CupertinoIcons.tag, + onPressed: null, + suffixWidget: Text(VersionService.getVersion(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), + CustomFormRow( + prefixText: AppLocalizations.of(context).build, + prefixIcon: CupertinoIcons.number, + onPressed: null, + suffixWidget: Text(VersionService.getBuildNumber(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), + ])), + const SizedBox(height: 50) + ], + ), )), ); } + /// Shows a dialog to confirm the deletion of all game data. + /// When confirmed, it deletes all game data from local storage. + void _deleteAllGames() { + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).delete_data_title), + content: Text(AppLocalizations.of(context).delete_data_message), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel), + onPressed: () => Navigator.pop(context), + ), + CupertinoDialogAction( + isDestructiveAction: true, + isDefaultAction: true, + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + LocalStorageService.deleteAllGames(); + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + } + void showFeedbackDialog(ImportStatus status) { if (status == ImportStatus.canceled) return; final (title, message) = _getDialogContent(status); diff --git a/pubspec.yaml b/pubspec.yaml index 31146e6..ca5ef95 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.4.0+467 +version: 0.4.2+470 environment: sdk: ^3.5.4 From 9e0b8f937420588bfc212e915b58c7503bcfe3c6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:12:44 +0200 Subject: [PATCH 085/182] Updated version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1f8766a..12dae39 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.4.1+473 +version: 0.4.2+473 environment: sdk: ^3.5.4 From 00b9ac62f73b3226e8b21de265e2ba921d2e5a81 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:16:12 +0200 Subject: [PATCH 086/182] Updated en string --- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 27efba1..782008a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -99,7 +99,7 @@ "export_data": "Export Data", "delete_data": "Delete all Game Data", "delete_data_title": "Delete game data?", - "delete_data_message": "Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_data_message": "Are you sure you want to delete all game data? This action cannot be undone.", "app": "App", "import_success_title": "Import successful", diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 983a90e..138c633 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -246,7 +246,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get delete_data_message => - 'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + 'Are you sure you want to delete all game data? This action cannot be undone.'; @override String get app => 'App'; From 340a02207086e73ddf64375f1815b5dff701d586 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:40:26 +0200 Subject: [PATCH 087/182] Updated RoundView buttons when game is finished --- lib/presentation/views/round_view.dart | 33 ++++++++++++++------------ pubspec.yaml | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 1e02cc3..6a31c2a 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -73,7 +73,8 @@ class _RoundViewState extends State { resizeToAvoidBottomInset: false, navigationBar: CupertinoNavigationBar( transitionBetweenRoutes: true, - middle: Text(AppLocalizations.of(context).results), + middle: Text( + '${AppLocalizations.of(context).results}${gameSession.isGameFinished ? ' \u{1F512}' : ''}'), leading: CupertinoButton( padding: EdgeInsets.zero, onPressed: () => @@ -293,21 +294,23 @@ class _RoundViewState extends State { : null, child: Text(AppLocalizations.of(context).done), ), - CupertinoButton( - onPressed: _areRoundInputsValid() - ? () { - _finishRound(); - LocalStorageService.saveGameSessions(); - if (widget.gameSession.isGameFinished == true) { - Navigator.pop(context); - } else { - Navigator.pop( - context, widget.roundNumber + 1); + if (!widget.gameSession.isGameFinished) + CupertinoButton( + onPressed: _areRoundInputsValid() + ? () { + _finishRound(); + LocalStorageService.saveGameSessions(); + if (widget.gameSession.isGameFinished == + true) { + Navigator.pop(context); + } else { + Navigator.pop( + context, widget.roundNumber + 1); + } } - } - : null, - child: Text(AppLocalizations.of(context).next_round), - ), + : null, + child: Text(AppLocalizations.of(context).next_round), + ), ], ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 12dae39..71d96fb 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.4.2+473 +version: 0.4.2+474 environment: sdk: ^3.5.4 From 5a1c3ef09aaaabaa3a28af138a67656c2b2595ff Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:51:21 +0200 Subject: [PATCH 088/182] Changed lock emoji to CuperinoIcons.lock and placed it in trailing of app bar --- lib/presentation/views/round_view.dart | 9 +++++++-- pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 6a31c2a..0f4e809 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -73,14 +73,19 @@ class _RoundViewState extends State { resizeToAvoidBottomInset: false, navigationBar: CupertinoNavigationBar( transitionBetweenRoutes: true, - middle: Text( - '${AppLocalizations.of(context).results}${gameSession.isGameFinished ? ' \u{1F512}' : ''}'), leading: CupertinoButton( padding: EdgeInsets.zero, onPressed: () => {LocalStorageService.saveGameSessions(), Navigator.pop(context)}, child: Text(AppLocalizations.of(context).cancel), ), + middle: Text(AppLocalizations.of(context).results), + trailing: widget.gameSession.isGameFinished + ? const Icon( + CupertinoIcons.lock, + size: 25, + ) + : null, ), child: Stack( children: [ diff --git a/pubspec.yaml b/pubspec.yaml index 71d96fb..553ec0f 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.4.2+474 +version: 0.4.2+476 environment: sdk: ^3.5.4 From df3a16151d39200ce1aa338d4c264af2b64667c8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:51:54 +0200 Subject: [PATCH 089/182] Simplified comparison --- lib/presentation/views/round_view.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 0f4e809..b4d2212 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -305,8 +305,7 @@ class _RoundViewState extends State { ? () { _finishRound(); LocalStorageService.saveGameSessions(); - if (widget.gameSession.isGameFinished == - true) { + if (widget.gameSession.isGameFinished) { Navigator.pop(context); } else { Navigator.pop( From 1e1601e431920f3a5b2958c8d493aa0424cc1720 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 11:52:23 +0200 Subject: [PATCH 090/182] Updated version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 553ec0f..8e8a264 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.4.2+476 +version: 0.4.3+476 environment: sdk: ^3.5.4 From cf0d7af343ced4affd85b195b0e5b46fb46fb6e9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 13:56:02 +0200 Subject: [PATCH 091/182] Corrected scaling --- lib/presentation/views/round_view.dart | 36 +++----------------------- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index b4d2212..39e5cc8 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -67,7 +67,6 @@ 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, @@ -126,9 +125,8 @@ class _RoundViewState extends State { return MapEntry( index, Padding( - padding: EdgeInsets.symmetric( - horizontal: 4 + - _getSegmentedControlPadding(maxLength), + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 6, ), child: FittedBox( @@ -137,10 +135,8 @@ class _RoundViewState extends State { name, textAlign: TextAlign.center, maxLines: 1, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, - fontSize: _getSegmentedControlFontSize( - maxLength), ), ), ), @@ -393,32 +389,6 @@ class _RoundViewState extends State { } } - double _getSegmentedControlFontSize(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 _getSegmentedControlPadding(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 8e8a264..ab51129 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.4.3+476 +version: 0.4.3+477 environment: sdk: ^3.5.4 From 546ac1d9967e7d289eb9af82bd8f2ca697d09d40 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 11 Jul 2025 14:06:06 +0200 Subject: [PATCH 092/182] Updates constant names and lint rule --- analysis_options.yaml | 1 - lib/core/constants.dart | 12 ++++++------ lib/presentation/views/about_view.dart | 6 +++--- lib/presentation/views/main_menu_view.dart | 2 +- lib/presentation/views/settings_view.dart | 6 +++--- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 46e2dbe..b3623a1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,4 +11,3 @@ linter: prefer_const_literals_to_create_immutables: true unnecessary_const: true lines_longer_than_80_chars: false - constant_identifier_names: false diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 5b6e90e..e716464 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -3,14 +3,14 @@ import 'package:rate_my_app/rate_my_app.dart'; class Constants { static const String appDevPhase = 'Beta'; - static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de'; - static const String GITHUB_LINK = 'https://github.felixkirchner.de'; - static const String GITHUB_ISSUES_LINK = + static const String kInstagramLink = 'https://instagram.felixkirchner.de'; + static const String kGithubLink = 'https://github.felixkirchner.de'; + static const String kGithubIssuesLink = 'https://cabocounter-issues.felixkirchner.de'; - static const String GITHUB_WIKI_LINK = + static const String kGithubWikiLink = 'https://cabocounter-wiki.felixkirchner.de'; - static const String EMAIL = 'cabocounter@felixkirchner.de'; - static const String PRIVACY_POLICY_LINK = + static const String kEmail = 'cabocounter@felixkirchner.de'; + static const String kPrivacyPolicyLink = 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; static RateMyApp rateMyApp = RateMyApp( diff --git a/lib/presentation/views/about_view.dart b/lib/presentation/views/about_view.dart index 128f91f..481e294 100644 --- a/lib/presentation/views/about_view.dart +++ b/lib/presentation/views/about_view.dart @@ -60,15 +60,15 @@ class AboutView extends StatelessWidget { children: [ IconButton( onPressed: () => - launchUrl(Uri.parse(Constants.INSTAGRAM_LINK)), + launchUrl(Uri.parse(Constants.kInstagramLink)), icon: const Icon(FontAwesomeIcons.instagram)), IconButton( onPressed: () => - launchUrl(Uri.parse('mailto:${Constants.EMAIL}')), + launchUrl(Uri.parse('mailto:${Constants.kEmail}')), icon: const Icon(CupertinoIcons.envelope)), IconButton( onPressed: () => - launchUrl(Uri.parse(Constants.GITHUB_LINK)), + launchUrl(Uri.parse(Constants.kGithubLink)), icon: const Icon(FontAwesomeIcons.github)), ], ), diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index fa537c6..86ff208 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -227,7 +227,7 @@ class _MainMenuViewState extends State { final Uri emailUri = Uri( scheme: 'mailto', - path: Constants.EMAIL, + path: Constants.kEmail, query: 'subject=$emailSubject' '&body=$emailBody', ); diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index ab8ec7d..d6f0833 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -149,21 +149,21 @@ class _SettingsViewState extends State { prefixText: AppLocalizations.of(context).wiki, prefixIcon: CupertinoIcons.book, onPressed: () => - launchUrl(Uri.parse(Constants.GITHUB_WIKI_LINK)), + launchUrl(Uri.parse(Constants.kGithubWikiLink)), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( prefixText: AppLocalizations.of(context).privacy_policy, prefixIcon: CupertinoIcons.doc_append, onPressed: () => - launchUrl(Uri.parse(Constants.PRIVACY_POLICY_LINK)), + launchUrl(Uri.parse(Constants.kPrivacyPolicyLink)), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( prefixText: AppLocalizations.of(context).error_found, prefixIcon: FontAwesomeIcons.github, onPressed: () => - launchUrl(Uri.parse(Constants.GITHUB_ISSUES_LINK)), + launchUrl(Uri.parse(Constants.kGithubIssuesLink)), suffixWidget: const CupertinoListTileChevron(), ), CustomFormRow( From c155d8c5cadec5cadda27d925dfa7472d2d84b6f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 12 Jul 2025 20:46:32 +0200 Subject: [PATCH 093/182] HOTFIX: Graph showed wrong data --- lib/presentation/views/graph_view.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 7736bb1..e9ed4d1 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -77,7 +77,7 @@ class _GraphViewState extends State { for (var round in rounds) { for (int i = 0; i < playerCount; i++) { - runningTotals[i] += round.scores[i]; + runningTotals[i] += round.scoreUpdates[i]; cumulativeScores[i].add(runningTotals[i]); } } diff --git a/pubspec.yaml b/pubspec.yaml index ab51129..2f82cdf 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.4.3+477 +version: 0.4.3+479 environment: sdk: ^3.5.4 From b9c3e1dd4e70ba142569637b23f76b78867acc19 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 12 Jul 2025 21:03:27 +0200 Subject: [PATCH 094/182] Graph starts at round 0 now where all players have 0 points --- lib/presentation/views/graph_view.dart | 25 +++++++++++++++++++------ pubspec.yaml | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index e9ed4d1..4c03dd4 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -88,15 +88,28 @@ class _GraphViewState extends State { /// Each series contains data points for each round return List.generate(playerCount, (i) { final data = List.generate( + cumulativeScores[i].length + 1, + (j) => ( + j, + j == 0 + ? 0 // 0 Points at at the start of the game + + // Adds a small jitter to the cumulative scores to prevent overlapping data points in the graph. + // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. + : cumulativeScores[i][j - 1] + (i - playerCount ~/ 2) * jitterStep + ), + ); /*List.generate( cumulativeScores[i].length, (j) => ( - j + 1, - - // Add a small jitter to the cumulative scores to prevent overlapping data points in the graph. - // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. - cumulativeScores[i][j] + (i - playerCount ~/ 2) * jitterStep + j, + j == 0 + ? 0 // In Runde 0 immer 0 Punkte + : + // Add a small jitter to the cumulative scores to prevent overlapping data points in the graph. + // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. + cumulativeScores[i][j] + (i - playerCount ~/ 2) * jitterStep ), - ); + );*/ /// Create a LineSeries for the player /// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score. diff --git a/pubspec.yaml b/pubspec.yaml index 2f82cdf..1440d14 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.4.3+479 +version: 0.4.3+481 environment: sdk: ^3.5.4 From 5780155954c386d11ff81ab411b8fd5290a4b574 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 12 Jul 2025 21:03:51 +0200 Subject: [PATCH 095/182] Adjusted jitterStep --- lib/presentation/views/graph_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 4c03dd4..9fa4e0a 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -82,7 +82,7 @@ class _GraphViewState extends State { } } - const double jitterStep = 0.15; + const double jitterStep = 0.05; /// Create a list of LineSeries for each player /// Each series contains data points for each round From 97da4cadffa6efa410cf41a034529ccc14acd038 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:21:37 +0200 Subject: [PATCH 096/182] Removed dead code --- lib/presentation/views/graph_view.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 9fa4e0a..a6e6042 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -98,18 +98,7 @@ class _GraphViewState extends State { // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. : cumulativeScores[i][j - 1] + (i - playerCount ~/ 2) * jitterStep ), - ); /*List.generate( - cumulativeScores[i].length, - (j) => ( - j, - j == 0 - ? 0 // In Runde 0 immer 0 Punkte - : - // Add a small jitter to the cumulative scores to prevent overlapping data points in the graph. - // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. - cumulativeScores[i][j] + (i - playerCount ~/ 2) * jitterStep - ), - );*/ + ); /// Create a LineSeries for the player /// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score. From 0b1b71e227f7785a00a41e1dfd383ab72ae5dd62 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:37:16 +0200 Subject: [PATCH 097/182] Updated Y-Axis and removed values under y = 0 --- lib/presentation/views/graph_view.dart | 11 +++++++---- pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index a6e6042..21f37d8 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -40,7 +40,10 @@ class _GraphViewState extends State { interval: 1, decimalPlaces: 0, ), - primaryYAxis: const NumericAxis(), + primaryYAxis: const NumericAxis( + interval: 1, + decimalPlaces: 0, + ), series: getCumulativeScores(), ), ) @@ -82,7 +85,7 @@ class _GraphViewState extends State { } } - const double jitterStep = 0.05; + const double jitterStep = 0.03; /// Create a list of LineSeries for each player /// Each series contains data points for each round @@ -91,8 +94,8 @@ class _GraphViewState extends State { cumulativeScores[i].length + 1, (j) => ( j, - j == 0 - ? 0 // 0 Points at at the start of the game + j == 0 || cumulativeScores[i][j - 1] == 0 + ? 0 // 0 Points at at the start of the game OR value is 0 (dont subtract jitter step) // Adds a small jitter to the cumulative scores to prevent overlapping data points in the graph. // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. diff --git a/pubspec.yaml b/pubspec.yaml index 1440d14..c793316 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.4.3+481 +version: 0.4.3+483 environment: sdk: ^3.5.4 From af630539dbf31edd8d6298f6ba20106c8cfe0d21 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:46:32 +0200 Subject: [PATCH 098/182] Changed overflow mode --- lib/presentation/views/graph_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 21f37d8..09aba16 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -35,7 +35,9 @@ class _GraphViewState extends State { padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), child: SfCartesianChart( legend: const Legend( - isVisible: true, position: LegendPosition.bottom), + overflowMode: LegendItemOverflowMode.wrap, + isVisible: true, + position: LegendPosition.bottom), primaryXAxis: const NumericAxis( interval: 1, decimalPlaces: 0, From 7733d3bd5c8ba7f4c8f11dd4b3dca22ad52fac65 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:55:28 +0200 Subject: [PATCH 099/182] Replaced string & if statement with visibility widget --- lib/presentation/views/active_game_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 9ecae1b..704952a 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -121,7 +121,7 @@ class _ActiveGameViewState extends State { children: [ CupertinoListTile( title: Text( - AppLocalizations.of(context).statistics, + AppLocalizations.of(context).game_process, ), backgroundColorActivated: CustomTheme.backgroundColor, @@ -131,8 +131,9 @@ class _ActiveGameViewState extends State { builder: (_) => GraphView( gameSession: gameSession, )))), - if (!gameSession.isPointsLimitEnabled) - CupertinoListTile( + Visibility( + visible: !gameSession.isPointsLimitEnabled, + child: CupertinoListTile( title: Text( AppLocalizations.of(context).end_game, style: gameSession.roundNumber > 1 && @@ -148,6 +149,7 @@ class _ActiveGameViewState extends State { _showEndGameDialog(); } }), + ), CupertinoListTile( title: Text( AppLocalizations.of(context).delete_game, From fee6cc3a339f45c503255627f3c8bd01b5a77716 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:56:32 +0200 Subject: [PATCH 100/182] updated accessability of graph view --- lib/presentation/views/graph_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 09aba16..fe36de9 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -30,7 +30,7 @@ class _GraphViewState extends State { middle: Text(AppLocalizations.of(context).game_process), previousPageTitle: AppLocalizations.of(context).back, ), - child: widget.gameSession.roundNumber > 2 + child: widget.gameSession.roundNumber > 1 ? Padding( padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), child: SfCartesianChart( From ee5319533a9e6f16b755e1469169bfb8617e5a90 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 00:57:08 +0200 Subject: [PATCH 101/182] Changed string for GraphView title --- lib/l10n/arb/app_de.arb | 3 +-- lib/l10n/arb/app_en.arb | 4 +--- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- pubspec.yaml | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4b55a8d..b072d63 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -74,7 +74,6 @@ "done": "Fertig", "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", @@ -85,7 +84,7 @@ "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", "game_process": "Spielverlauf", - "empty_graph_text": "Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", + "empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 782008a..a649362 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -74,8 +74,6 @@ "done": "Done", "next_round": "Next Round", - - "statistics": "Statistics", "end_game": "End Game", "delete_game": "Delete Game", "new_game_same_settings": "New Game with same Settings", @@ -86,7 +84,7 @@ "end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.", "game_process": "Scoring History", - "empty_graph_text": "You must play at least two rounds for the game progress graph to be displayed.", + "empty_graph_text": "You must play at least one round for the game progress graph to be displayed.", "settings": "Settings", "cabo_penalty": "Cabo Penalty", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index d805b45..c54cca0 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -479,7 +479,7 @@ abstract class AppLocalizations { /// No description provided for @empty_graph_text. /// /// In de, this message translates to: - /// **'Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'** + /// **'Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'** String get empty_graph_text; /// No description provided for @settings. diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 4acdb1c..7bd7af0 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -212,7 +212,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get empty_graph_text => - 'Du musst mindestens zwei Runden spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'; + 'Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'; @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 138c633..5403a95 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -209,7 +209,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get empty_graph_text => - 'You must play at least two rounds for the game progress graph to be displayed.'; + 'You must play at least one round for the game progress graph to be displayed.'; @override String get settings => 'Settings'; diff --git a/pubspec.yaml b/pubspec.yaml index c793316..8241466 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.4.3+483 +version: 0.4.4+484 environment: sdk: ^3.5.4 From 1157ab70ec41a30e93500c1a7143ff57c1b7b27a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 11:49:47 +0200 Subject: [PATCH 102/182] Updated comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/presentation/views/graph_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index fe36de9..d322bd0 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -97,7 +97,7 @@ class _GraphViewState extends State { (j) => ( j, j == 0 || cumulativeScores[i][j - 1] == 0 - ? 0 // 0 Points at at the start of the game OR value is 0 (dont subtract jitter step) + ? 0 // 0 points at the start of the game or when the value is 0 (don't subtract jitter step) // Adds a small jitter to the cumulative scores to prevent overlapping data points in the graph. // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. From 75568a1a4bf3657bdd8bdf6ddfece0339dfcb85e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 11:55:46 +0200 Subject: [PATCH 103/182] Updated generated files --- lib/l10n/generated/app_localizations.dart | 6 ------ lib/l10n/generated/app_localizations_de.dart | 3 --- lib/l10n/generated/app_localizations_en.dart | 3 --- pubspec.yaml | 2 +- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index c54cca0..2059f1b 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -416,12 +416,6 @@ abstract class AppLocalizations { /// **'Nächste Runde'** String get next_round; - /// No description provided for @statistics. - /// - /// In de, this message translates to: - /// **'Statistiken'** - String get statistics; - /// No description provided for @end_game. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 7bd7af0..068711f 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -178,9 +178,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get next_round => 'Nächste Runde'; - @override - String get statistics => 'Statistiken'; - @override String get end_game => 'Spiel beenden'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 5403a95..06b5c03 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -175,9 +175,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get next_round => 'Next Round'; - @override - String get statistics => 'Statistics'; - @override String get end_game => 'End Game'; diff --git a/pubspec.yaml b/pubspec.yaml index 8241466..da2c087 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.4.4+484 +version: 0.4.4+485 environment: sdk: ^3.5.4 From 01e0c70ac89e52c07e0223a303915d5633265612 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 12:30:34 +0200 Subject: [PATCH 104/182] Updated version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20b0cae..23ec7d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CABO Counter -![Version](https://img.shields.io/badge/Version-0.3.8-orange) +![Version](https://img.shields.io/badge/Version-0.4.4-orange) ![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) From 8565382fab793c55cac5981a34826601a4293bd0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 12:48:24 +0200 Subject: [PATCH 105/182] Beta-Version 0.4.4 (#105) * Update README.md * Tried new design for im- and export-button * Moved views to presentation folder * Moved widgets to presentation folder * Implemented CustomRowForm Widget * Used new custom form row * Removed double information * Refactored methods to private * Changed label * Modified paddings and text color * Changed string * Updated CustomFormRow padding and pressed handler * Implemented various new forms of CustomFormRow into SettingsView * Implemented VersionService * Updated strings, added wiki button * Corrected replaced string * Added import dialog feedback (got lost in refactoring) * Corrected function duplication * changed suffixWidget assignment and moved stepperKeys * Changed icons * Added rate_my_app package * Renamed folder * Implement native rating dialog * Implemented logic for pre rating and refactored rating dialog * updated launch mode * Small changes * Updated launch mode * Updated linting rules * Renamed folders * Changed l10n files location * Implemented new link constants * Changed privacy policy link * Corrected wiki link * Removed import * Updated links * Updated links to subdomains * Updated file paths * Updated strings * Updated identifiers * Added break in switch case * Updated strings * Implemented new popup * Corrected links * Changed color * Ensured rating dialog wont show in Beta * Refactoring * Adding const * Renamed variables * Corrected links * updated Dialog function * Added version number in about view * Changed order and corrected return * Changed translation * Changed popups because of unmounted context errors * corrected string typo * Replaced int constants with enums * Renamed Stepper to CustomStepper * Changed argument order * Reordered properties * Implemented empty builder for GraphView * Added jitterStip to prevent the graphs overlaying each other * Removed german comments * Added comment to jitter calculation * Overhauled comments in CustomTheme * Updated version * Added Delete all games button to Settings * Updated version * Updated en string * Updated RoundView buttons when game is finished * Changed lock emoji to CuperinoIcons.lock and placed it in trailing of app bar * Simplified comparison * Updated version * Corrected scaling * Updates constant names and lint rule * HOTFIX: Graph showed wrong data * Graph starts at round 0 now where all players have 0 points * Adjusted jitterStep * Removed dead code * Updated Y-Axis and removed values under y = 0 * Changed overflow mode * Replaced string & if statement with visibility widget * updated accessability of graph view * Changed string for GraphView title * Updated comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated generated files * Updated version in README --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- analysis_options.yaml | 3 - l10n.yaml | 7 +- lib/core/constants.dart | 22 ++ lib/{utility => core}/custom_theme.dart | 7 + lib/l10n/{ => arb}/app_de.arb | 29 +- lib/l10n/{ => arb}/app_en.arb | 33 ++- lib/l10n/{ => arb}/untranslated_messages.json | 0 .../{ => generated}/app_localizations.dart | 116 +++++++- .../{ => generated}/app_localizations_de.dart | 63 +++- .../{ => generated}/app_localizations_en.dart | 61 +++- lib/main.dart | 8 +- .../views/about_view.dart} | 22 +- .../views/active_game_view.dart | 21 +- .../views/create_game_view.dart | 8 +- lib/presentation/views/graph_view.dart | 120 ++++++++ .../views/main_menu_view.dart | 202 ++++++++++--- .../views/mode_selection_view.dart | 4 +- lib/{ => presentation}/views/round_view.dart | 77 ++--- lib/presentation/views/settings_view.dart | 269 ++++++++++++++++++ lib/{ => presentation}/views/tab_view.dart | 10 +- lib/presentation/widgets/custom_form_row.dart | 53 ++++ .../widgets/custom_stepper.dart} | 17 +- lib/services/local_storage_service.dart | 26 +- lib/services/version_service.dart | 32 +++ lib/utility/globals.dart | 3 - lib/views/graph_view.dart | 84 ------ lib/views/settings_view.dart | 267 ----------------- pubspec.yaml | 3 +- 29 files changed, 1024 insertions(+), 545 deletions(-) create mode 100644 lib/core/constants.dart rename lib/{utility => core}/custom_theme.dart (71%) rename lib/l10n/{ => arb}/app_de.arb (80%) rename lib/l10n/{ => arb}/app_en.arb (80%) rename lib/l10n/{ => arb}/untranslated_messages.json (100%) rename lib/l10n/{ => generated}/app_localizations.dart (85%) rename lib/l10n/{ => generated}/app_localizations_de.dart (81%) rename lib/l10n/{ => generated}/app_localizations_en.dart (80%) rename lib/{views/information_view.dart => presentation/views/about_view.dart} (74%) rename lib/{ => presentation}/views/active_game_view.dart (95%) rename lib/{ => presentation}/views/create_game_view.dart (97%) create mode 100644 lib/presentation/views/graph_view.dart rename lib/{ => presentation}/views/main_menu_view.dart (57%) rename lib/{ => presentation}/views/mode_selection_view.dart (92%) rename lib/{ => presentation}/views/round_view.dart (89%) create mode 100644 lib/presentation/views/settings_view.dart rename lib/{ => presentation}/views/tab_view.dart (80%) create mode 100644 lib/presentation/widgets/custom_form_row.dart rename lib/{widgets/stepper.dart => presentation/widgets/custom_stepper.dart} (75%) create mode 100644 lib/services/version_service.dart delete mode 100644 lib/utility/globals.dart delete mode 100644 lib/views/graph_view.dart delete mode 100644 lib/views/settings_view.dart diff --git a/README.md b/README.md index 20b0cae..23ec7d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CABO Counter -![Version](https://img.shields.io/badge/Version-0.3.8-orange) +![Version](https://img.shields.io/badge/Version-0.4.4-orange) ![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) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2ce6b52..b3623a1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,3 @@ linter: prefer_const_literals_to_create_immutables: true unnecessary_const: true lines_longer_than_80_chars: false - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/l10n.yaml b/l10n.yaml index 239fdc6..f69305d 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,5 +1,6 @@ -arb-dir: lib/l10n +arb-dir: lib/l10n/arb template-arb-file: app_de.arb -untranslated-messages-file: lib/l10n/untranslated_messages.json +untranslated-messages-file: lib/l10n/arb/untranslated_messages.json nullable-getter: false -output-localization-file: app_localizations.dart \ No newline at end of file +output-localization-file: app_localizations.dart +output-dir: lib/l10n/generated \ No newline at end of file diff --git a/lib/core/constants.dart b/lib/core/constants.dart new file mode 100644 index 0000000..e716464 --- /dev/null +++ b/lib/core/constants.dart @@ -0,0 +1,22 @@ +import 'package:rate_my_app/rate_my_app.dart'; + +class Constants { + static const String appDevPhase = 'Beta'; + + static const String kInstagramLink = 'https://instagram.felixkirchner.de'; + static const String kGithubLink = 'https://github.felixkirchner.de'; + static const String kGithubIssuesLink = + 'https://cabocounter-issues.felixkirchner.de'; + static const String kGithubWikiLink = + 'https://cabocounter-wiki.felixkirchner.de'; + static const String kEmail = 'cabocounter@felixkirchner.de'; + static const String kPrivacyPolicyLink = + 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; + + static RateMyApp rateMyApp = RateMyApp( + appStoreIdentifier: '6747105718', + minDays: 15, + remindDays: 45, + minLaunches: 15, + remindLaunches: 40); +} diff --git a/lib/utility/custom_theme.dart b/lib/core/custom_theme.dart similarity index 71% rename from lib/utility/custom_theme.dart rename to lib/core/custom_theme.dart index 77a2f5b..a00340b 100644 --- a/lib/utility/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -6,6 +6,13 @@ class CustomTheme { static Color backgroundColor = const Color(0xFF101010); static Color backgroundTintColor = CupertinoColors.darkBackgroundGray; + // Line Colors for GraphView + static const Color graphColor1 = Color(0xFFF44336); + static const Color graphColor2 = Color(0xFF2196F3); + static const Color graphColor3 = Color(0xFFFFA726); + static const Color graphColor4 = Color(0xFF9C27B0); + static final Color graphColor5 = primaryColor; + static TextStyle modeTitle = TextStyle( color: primaryColor, fontSize: 20, diff --git a/lib/l10n/app_de.arb b/lib/l10n/arb/app_de.arb similarity index 80% rename from lib/l10n/app_de.arb rename to lib/l10n/arb/app_de.arb index 9281ac1..b072d63 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -30,6 +30,16 @@ } } }, + "pre_rating_title": "Gefällt dir die App?", + "pre_rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!", + "yes": "Ja", + "no": "Nein", + "bad_rating_title": "Unzufrieden mit der App?", + "bad_rating_message": "Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!", + "contact_email": "E-Mail schreiben", + "email_subject": "Feedback: Cabo Counter App", + "email_body": "Ich habe folgendes Feedback...", + "overview": "Übersicht", "new_game": "Neues Spiel", "game_title": "Titel des Spiels", @@ -64,7 +74,6 @@ "done": "Fertig", "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", @@ -75,6 +84,7 @@ "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", "game_process": "Spielverlauf", + "empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", @@ -83,8 +93,12 @@ "point_limit_subtitle": "... hier ist Schluss", "reset_to_default": "Auf Standard zurücksetzen", "game_data": "Spieldaten", - "import_data": "Daten importieren", - "export_data": "Daten exportieren", + "import_data": "Spieldaten importieren", + "export_data": "Spieldaten exportieren", + "delete_data": "Alle Spieldaten löschen", + "delete_data_title": "Spieldaten löschen?", + "delete_data_message": "Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "app": "App", "import_success_title": "Import erfolgreich", "import_success_message":"Die Spieldaten wurden erfolgreich importiert.", @@ -92,16 +106,19 @@ "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_title": "Import fehlgeschlagen", "import_generic_error_message": "Der Import ist fehlgeschlagen.", "export_error_title": "Fehler", "export_error_message": "Datei konnte nicht exportiert werden", + "error_found": "Fehler gefunden?", "create_issue": "Issue erstellen", + "wiki": "Wiki", "app_version": "App-Version", - "build": "Build", - "load_version": "Lade Version...", + "privacy_policy": "Datenschutzerklärung", + "build": "Build-Nr.", + "loading": "Lädt...", "about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! " } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/arb/app_en.arb similarity index 80% rename from lib/l10n/app_en.arb rename to lib/l10n/arb/app_en.arb index 8b328ba..a649362 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -30,6 +30,16 @@ } } }, + "pre_rating_title": "Do you like the app?", + "pre_rating_message": "Feedback helps me to continuously improve the app. Thank you!", + "yes": "Yes", + "no": "No", + "bad_rating_title": "Not satisfied?", + "bad_rating_message": "If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.", + "contact_email": "Contact via E-Mail", + "email_subject": "Feedback: Cabo Counter App", + "email_body": "I have the following feedback...", + "overview": "Overview", "new_game": "New Game", "game_title": "Game Title", @@ -64,13 +74,17 @@ "done": "Done", "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", + "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.", - "game_process": "Spielverlauf", + "game_process": "Scoring History", + "empty_graph_text": "You must play at least one round for the game progress graph to be displayed.", "settings": "Settings", "cabo_penalty": "Cabo Penalty", @@ -81,10 +95,10 @@ "game_data": "Game Data", "import_data": "Import Data", "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.", + "delete_data": "Delete all Game Data", + "delete_data_title": "Delete game data?", + "delete_data_message": "Are you sure you want to delete all game data? This action cannot be undone.", + "app": "App", "import_success_title": "Import successful", "import_success_message":"The game data has been successfully imported.", @@ -97,11 +111,14 @@ "export_error_title": "Export failed", "export_error_message": "Could not export file", + "error_found": "Found a bug?", "create_issue": "Create Issue", + "wiki": "Wiki", "app_version": "App Version", - "load_version": "Loading version...", - "build": "Build", + "privacy_policy": "Privacy Policy", + "loading": "Loading...", + "build": "Build No.", "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/untranslated_messages.json b/lib/l10n/arb/untranslated_messages.json similarity index 100% rename from lib/l10n/untranslated_messages.json rename to lib/l10n/arb/untranslated_messages.json diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/generated/app_localizations.dart similarity index 85% rename from lib/l10n/app_localizations.dart rename to lib/l10n/generated/app_localizations.dart index c836194..2059f1b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -18,7 +18,7 @@ import 'app_localizations_en.dart'; /// `supportedLocales` list. For example: /// /// ```dart -/// import 'l10n/app_localizations.dart'; +/// import 'generated/app_localizations.dart'; /// /// return MaterialApp( /// localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -218,6 +218,60 @@ abstract class AppLocalizations { /// **'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 @pre_rating_title. + /// + /// In de, this message translates to: + /// **'Gefällt dir die App?'** + String get pre_rating_title; + + /// No description provided for @pre_rating_message. + /// + /// In de, this message translates to: + /// **'Feedback hilft mir, die App zu verbessern. Vielen Dank!'** + String get pre_rating_message; + + /// No description provided for @yes. + /// + /// In de, this message translates to: + /// **'Ja'** + String get yes; + + /// No description provided for @no. + /// + /// In de, this message translates to: + /// **'Nein'** + String get no; + + /// No description provided for @bad_rating_title. + /// + /// In de, this message translates to: + /// **'Unzufrieden mit der App?'** + String get bad_rating_title; + + /// No description provided for @bad_rating_message. + /// + /// In de, this message translates to: + /// **'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!'** + String get bad_rating_message; + + /// No description provided for @contact_email. + /// + /// In de, this message translates to: + /// **'E-Mail schreiben'** + String get contact_email; + + /// No description provided for @email_subject. + /// + /// In de, this message translates to: + /// **'Feedback: Cabo Counter App'** + String get email_subject; + + /// No description provided for @email_body. + /// + /// In de, this message translates to: + /// **'Ich habe folgendes Feedback...'** + String get email_body; + /// No description provided for @overview. /// /// In de, this message translates to: @@ -362,12 +416,6 @@ abstract class AppLocalizations { /// **'Nächste Runde'** String get next_round; - /// No description provided for @statistics. - /// - /// In de, this message translates to: - /// **'Statistiken'** - String get statistics; - /// No description provided for @end_game. /// /// In de, this message translates to: @@ -422,6 +470,12 @@ abstract class AppLocalizations { /// **'Spielverlauf'** String get game_process; + /// No description provided for @empty_graph_text. + /// + /// In de, this message translates to: + /// **'Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'** + String get empty_graph_text; + /// No description provided for @settings. /// /// In de, this message translates to: @@ -467,15 +521,39 @@ abstract class AppLocalizations { /// No description provided for @import_data. /// /// In de, this message translates to: - /// **'Daten importieren'** + /// **'Spieldaten importieren'** String get import_data; /// No description provided for @export_data. /// /// In de, this message translates to: - /// **'Daten exportieren'** + /// **'Spieldaten exportieren'** String get export_data; + /// No description provided for @delete_data. + /// + /// In de, this message translates to: + /// **'Alle Spieldaten löschen'** + String get delete_data; + + /// No description provided for @delete_data_title. + /// + /// In de, this message translates to: + /// **'Spieldaten löschen?'** + String get delete_data_title; + + /// No description provided for @delete_data_message. + /// + /// In de, this message translates to: + /// **'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** + String get delete_data_message; + + /// No description provided for @app. + /// + /// In de, this message translates to: + /// **'App'** + String get app; + /// No description provided for @import_success_title. /// /// In de, this message translates to: @@ -548,23 +626,35 @@ abstract class AppLocalizations { /// **'Issue erstellen'** String get create_issue; + /// No description provided for @wiki. + /// + /// In de, this message translates to: + /// **'Wiki'** + String get wiki; + /// No description provided for @app_version. /// /// In de, this message translates to: /// **'App-Version'** String get app_version; + /// No description provided for @privacy_policy. + /// + /// In de, this message translates to: + /// **'Datenschutzerklärung'** + String get privacy_policy; + /// No description provided for @build. /// /// In de, this message translates to: - /// **'Build'** + /// **'Build-Nr.'** String get build; - /// No description provided for @load_version. + /// No description provided for @loading. /// /// In de, this message translates to: - /// **'Lade Version...'** - String get load_version; + /// **'Lädt...'** + String get loading; /// No description provided for @about_text. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart similarity index 81% rename from lib/l10n/app_localizations_de.dart rename to lib/l10n/generated/app_localizations_de.dart index 5b9d841..068711f 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -71,6 +71,35 @@ class AppLocalizationsDe extends AppLocalizations { return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; } + @override + String get pre_rating_title => 'Gefällt dir die App?'; + + @override + String get pre_rating_message => + 'Feedback hilft mir, die App zu verbessern. Vielen Dank!'; + + @override + String get yes => 'Ja'; + + @override + String get no => 'Nein'; + + @override + String get bad_rating_title => 'Unzufrieden mit der App?'; + + @override + String get bad_rating_message => + 'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!'; + + @override + String get contact_email => 'E-Mail schreiben'; + + @override + String get email_subject => 'Feedback: Cabo Counter App'; + + @override + String get email_body => 'Ich habe folgendes Feedback...'; + @override String get overview => 'Übersicht'; @@ -149,9 +178,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get next_round => 'Nächste Runde'; - @override - String get statistics => 'Statistiken'; - @override String get end_game => 'Spiel beenden'; @@ -181,6 +207,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get game_process => 'Spielverlauf'; + @override + String get empty_graph_text => + 'Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.'; + @override String get settings => 'Einstellungen'; @@ -203,10 +233,23 @@ class AppLocalizationsDe extends AppLocalizations { String get game_data => 'Spieldaten'; @override - String get import_data => 'Daten importieren'; + String get import_data => 'Spieldaten importieren'; @override - String get export_data => 'Daten exportieren'; + String get export_data => 'Spieldaten exportieren'; + + @override + String get delete_data => 'Alle Spieldaten löschen'; + + @override + String get delete_data_title => 'Spieldaten löschen?'; + + @override + String get delete_data_message => + 'Bist du sicher, dass du alle Spieldaten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; + + @override + String get app => 'App'; @override String get import_success_title => 'Import erfolgreich'; @@ -247,14 +290,20 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_issue => 'Issue erstellen'; + @override + String get wiki => 'Wiki'; + @override String get app_version => 'App-Version'; @override - String get build => 'Build'; + String get privacy_policy => 'Datenschutzerklärung'; @override - String get load_version => 'Lade Version...'; + String get build => 'Build-Nr.'; + + @override + String get loading => 'Lädt...'; @override String get about_text => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart similarity index 80% rename from lib/l10n/app_localizations_en.dart rename to lib/l10n/generated/app_localizations_en.dart index c98dddd..06b5c03 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -71,6 +71,35 @@ class AppLocalizationsEn extends AppLocalizations { return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.'; } + @override + String get pre_rating_title => 'Do you like the app?'; + + @override + String get pre_rating_message => + 'Feedback helps me to continuously improve the app. Thank you!'; + + @override + String get yes => 'Yes'; + + @override + String get no => 'No'; + + @override + String get bad_rating_title => 'Not satisfied?'; + + @override + String get bad_rating_message => + 'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.'; + + @override + String get contact_email => 'Contact via E-Mail'; + + @override + String get email_subject => 'Feedback: Cabo Counter App'; + + @override + String get email_body => 'I have the following feedback...'; + @override String get overview => 'Overview'; @@ -146,9 +175,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get next_round => 'Next Round'; - @override - String get statistics => 'Statistics'; - @override String get end_game => 'End Game'; @@ -176,7 +202,11 @@ class AppLocalizationsEn extends AppLocalizations { 'Do you want to end the game? The game gets marked as finished and cannot be continued.'; @override - String get game_process => 'Spielverlauf'; + String get game_process => 'Scoring History'; + + @override + String get empty_graph_text => + 'You must play at least one round for the game progress graph to be displayed.'; @override String get settings => 'Settings'; @@ -205,6 +235,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_data => 'Export Data'; + @override + String get delete_data => 'Delete all Game Data'; + + @override + String get delete_data_title => 'Delete game data?'; + + @override + String get delete_data_message => + 'Are you sure you want to delete all game data? This action cannot be undone.'; + + @override + String get app => 'App'; + @override String get import_success_title => 'Import successful'; @@ -244,14 +287,20 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_issue => 'Create Issue'; + @override + String get wiki => 'Wiki'; + @override String get app_version => 'App Version'; @override - String get build => 'Build'; + String get privacy_policy => 'Privacy Policy'; @override - String get load_version => 'Loading version...'; + String get build => 'Build No.'; + + @override + String get loading => 'Loading...'; @override String get about_text => diff --git a/lib/main.dart b/lib/main.dart index cd3d05f..9279426 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,9 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/tab_view.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/views/tab_view.dart'; +import 'package:cabo_counter/services/version_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -13,6 +14,7 @@ Future main() async { await ConfigService.initConfig(); ConfigService.pointLimit = await ConfigService.getPointLimit(); ConfigService.caboPenalty = await ConfigService.getCaboPenalty(); + await VersionService.init(); runApp(const App()); } diff --git a/lib/views/information_view.dart b/lib/presentation/views/about_view.dart similarity index 74% rename from lib/views/information_view.dart rename to lib/presentation/views/about_view.dart index 1d0918b..481e294 100644 --- a/lib/views/information_view.dart +++ b/lib/presentation/views/about_view.dart @@ -1,11 +1,13 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/core/constants.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/services/version_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; -class InformationView extends StatelessWidget { - const InformationView({super.key}); +class AboutView extends StatelessWidget { + const AboutView({super.key}); @override Widget build(BuildContext context) { @@ -28,9 +30,13 @@ class InformationView extends StatelessWidget { ), ), ), + Text( + '${AppLocalizations.of(context).app_version} ${VersionService.getVersionWithBuild()}', + style: TextStyle(fontSize: 15, color: Colors.grey[300]), + ), Padding( padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 30), + const EdgeInsets.symmetric(horizontal: 20, vertical: 15), child: SizedBox( height: 200, child: Image.asset('assets/cabo_counter-logo_rounded.png'), @@ -54,15 +60,15 @@ class InformationView extends StatelessWidget { children: [ IconButton( onPressed: () => - launchUrl(Uri.parse('https://www.instagram.com/fx.kr')), + launchUrl(Uri.parse(Constants.kInstagramLink)), icon: const Icon(FontAwesomeIcons.instagram)), IconButton( - onPressed: () => launchUrl( - Uri.parse('mailto:felix.kirchner.fk@gmail.com')), + onPressed: () => + launchUrl(Uri.parse('mailto:${Constants.kEmail}')), icon: const Icon(CupertinoIcons.envelope)), IconButton( onPressed: () => - launchUrl(Uri.parse('https://www.github.com/flixcoo')), + launchUrl(Uri.parse(Constants.kGithubLink)), icon: const Icon(FontAwesomeIcons.github)), ], ), diff --git a/lib/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart similarity index 95% rename from lib/views/active_game_view.dart rename to lib/presentation/views/active_game_view.dart index 5945a3e..704952a 100644 --- a/lib/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -1,11 +1,11 @@ +import 'package:cabo_counter/core/custom_theme.dart'; 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/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/create_game_view.dart'; +import 'package:cabo_counter/presentation/views/graph_view.dart'; +import 'package:cabo_counter/presentation/views/round_view.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'; -import 'package:cabo_counter/views/round_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -121,7 +121,7 @@ class _ActiveGameViewState extends State { children: [ CupertinoListTile( title: Text( - AppLocalizations.of(context).statistics, + AppLocalizations.of(context).game_process, ), backgroundColorActivated: CustomTheme.backgroundColor, @@ -131,8 +131,9 @@ class _ActiveGameViewState extends State { builder: (_) => GraphView( gameSession: gameSession, )))), - if (!gameSession.isPointsLimitEnabled) - CupertinoListTile( + Visibility( + visible: !gameSession.isPointsLimitEnabled, + child: CupertinoListTile( title: Text( AppLocalizations.of(context).end_game, style: gameSession.roundNumber > 1 && @@ -148,6 +149,7 @@ class _ActiveGameViewState extends State { _showEndGameDialog(); } }), + ), CupertinoListTile( title: Text( AppLocalizations.of(context).delete_game, @@ -235,7 +237,8 @@ class _ActiveGameViewState extends State { child: Text( AppLocalizations.of(context).end_game, style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.red), + fontWeight: FontWeight.bold, + color: CupertinoColors.destructiveRed), ), onPressed: () { setState(() { diff --git a/lib/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart similarity index 97% rename from lib/views/create_game_view.dart rename to lib/presentation/views/create_game_view.dart index f6099f4..0d23494 100644 --- a/lib/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -1,10 +1,10 @@ +import 'package:cabo_counter/core/custom_theme.dart'; 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/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/active_game_view.dart'; +import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/services/config_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; -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 { diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart new file mode 100644 index 0000000..d322bd0 --- /dev/null +++ b/lib/presentation/views/graph_view.dart @@ -0,0 +1,120 @@ +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +class GraphView extends StatefulWidget { + 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. + final List lineColors = [ + CustomTheme.graphColor1, + CustomTheme.graphColor2, + CustomTheme.graphColor3, + CustomTheme.graphColor4, + CustomTheme.graphColor5 + ]; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(AppLocalizations.of(context).game_process), + previousPageTitle: AppLocalizations.of(context).back, + ), + child: widget.gameSession.roundNumber > 1 + ? Padding( + padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), + child: SfCartesianChart( + legend: const Legend( + overflowMode: LegendItemOverflowMode.wrap, + isVisible: true, + position: LegendPosition.bottom), + primaryXAxis: const NumericAxis( + interval: 1, + decimalPlaces: 0, + ), + primaryYAxis: const NumericAxis( + interval: 1, + decimalPlaces: 0, + ), + series: getCumulativeScores(), + ), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Center( + child: Icon(CupertinoIcons.chart_bar_alt_fill, size: 60), + ), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Text( + AppLocalizations.of(context).empty_graph_text, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + ), + ], + )); + } + + /// 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.scoreUpdates[i]; + cumulativeScores[i].add(runningTotals[i]); + } + } + + const double jitterStep = 0.03; + + /// 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 + 1, + (j) => ( + j, + j == 0 || cumulativeScores[i][j - 1] == 0 + ? 0 // 0 points at the start of the game or when the value is 0 (don't subtract jitter step) + + // Adds a small jitter to the cumulative scores to prevent overlapping data points in the graph. + // The jitter is centered around zero by subtracting playerCount ~/ 2 from the player index i. + : cumulativeScores[i][j - 1] + (i - playerCount ~/ 2) * jitterStep + ), + ); + + /// Create a LineSeries for the player + /// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score. + return LineSeries<(int, num), int>( + name: playerNames[i], + dataSource: data, + xValueMapper: (record, _) => record.$1, + yValueMapper: (record, _) => record.$2, + markerSettings: const MarkerSettings(isVisible: true), + color: lineColors[i], + ); + }); + } +} diff --git a/lib/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart similarity index 57% rename from lib/views/main_menu_view.dart rename to lib/presentation/views/main_menu_view.dart index e087cfc..86ff208 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -1,13 +1,19 @@ +import 'package:cabo_counter/core/constants.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_manager.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/active_game_view.dart'; +import 'package:cabo_counter/presentation/views/create_game_view.dart'; +import 'package:cabo_counter/presentation/views/settings_view.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/views/active_game_view.dart'; -import 'package:cabo_counter/views/create_game_view.dart'; -import 'package:cabo_counter/views/settings_view.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +enum PreRatingDialogDecision { yes, no, cancel } + +enum BadRatingDialogDecision { email, cancel } class MainMenuView extends StatefulWidget { const MainMenuView({super.key}); @@ -29,6 +35,17 @@ class _MainMenuViewState extends State { }); }); gameManager.addListener(_updateView); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Constants.rateMyApp.init(); + + if (Constants.rateMyApp.shouldOpenDialog && + Constants.appDevPhase != 'Beta') { + await Future.delayed(const Duration(milliseconds: 600)); + if (!mounted) return; + _handleFeedbackDialog(context); + } + }); } void _updateView() { @@ -57,14 +74,12 @@ class _MainMenuViewState extends State { icon: const Icon(CupertinoIcons.settings, size: 30)), middle: const Text('Cabo Counter'), trailing: IconButton( - onPressed: () => { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const CreateGameView(), - ), - ) - }, + onPressed: () => Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const CreateGameView(), + ), + ), icon: const Icon(CupertinoIcons.add)), ), child: CupertinoPageScaffold( @@ -73,10 +88,9 @@ class _MainMenuViewState extends State { ? const Center(child: CupertinoActivityIndicator()) : gameManager.gameList.isEmpty ? Column( - mainAxisAlignment: - MainAxisAlignment.center, // Oben ausrichten + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 30), // Abstand von oben + const SizedBox(height: 30), Center( child: GestureDetector( onTap: () => Navigator.push( @@ -92,7 +106,7 @@ class _MainMenuViewState extends State { color: CustomTheme.primaryColor, ), )), - const SizedBox(height: 10), // Abstand von oben + const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 70), @@ -128,7 +142,7 @@ class _MainMenuViewState extends State { final String gameTitle = gameManager .gameList[index].gameTitle; return await _showDeleteGamePopup( - gameTitle); + context, gameTitle); }, onDismissed: (direction) { gameManager @@ -204,40 +218,144 @@ class _MainMenuViewState extends State { return AppLocalizations.of(context).unlimited; } + /// Handles the feedback dialog when the conditions for rating are met. + /// It shows a dialog asking the user if they like the app, + /// and based on their response, it either opens the rating dialog or an email client for feedback. + Future _handleFeedbackDialog(BuildContext context) async { + final String emailSubject = AppLocalizations.of(context).email_subject; + final String emailBody = AppLocalizations.of(context).email_body; + + final Uri emailUri = Uri( + scheme: 'mailto', + path: Constants.kEmail, + query: 'subject=$emailSubject' + '&body=$emailBody', + ); + + PreRatingDialogDecision preRatingDecision = + await _showPreRatingDialog(context); + BadRatingDialogDecision badRatingDecision = BadRatingDialogDecision.cancel; + + // so that the bad rating dialog is not shown immediately + await Future.delayed(const Duration(milliseconds: 300)); + + switch (preRatingDecision) { + case PreRatingDialogDecision.yes: + if (context.mounted) Constants.rateMyApp.showStarRateDialog(context); + break; + case PreRatingDialogDecision.no: + if (context.mounted) { + badRatingDecision = await _showBadRatingDialog(context); + } + if (badRatingDecision == BadRatingDialogDecision.email) { + if (context.mounted) { + launchUrl(emailUri); + } + } + break; + case PreRatingDialogDecision.cancel: + break; + } + } + /// Shows a confirmation dialog to delete all game sessions. /// Returns true if the user confirms the deletion, false otherwise. /// [gameTitle] is the title of the game session to be deleted. - Future _showDeleteGamePopup(String gameTitle) async { - bool? shouldDelete = await showCupertinoDialog( + Future _showDeleteGamePopup( + BuildContext context, String gameTitle) async { + return await showCupertinoDialog( context: context, - builder: (context) { + builder: (BuildContext context) { return CupertinoAlertDialog( - title: Text(AppLocalizations.of(context).delete_game_title), - content: Text( - AppLocalizations.of(context).delete_game_message(gameTitle)), - actions: [ - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, false); - }, - child: Text(AppLocalizations.of(context).cancel), + title: Text( + AppLocalizations.of(context).delete_game_title, ), - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, true); - }, - child: Text( - AppLocalizations.of(context).delete, - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.red), + content: Text(AppLocalizations.of(context) + .delete_game_message(gameTitle)), + actions: [ + CupertinoDialogAction( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(AppLocalizations.of(context).cancel), ), - ), - ], - ); + CupertinoDialogAction( + isDestructiveAction: true, + isDefaultAction: true, + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + AppLocalizations.of(context).delete, + ), + ) + ]); }, ) ?? false; - return shouldDelete; + } + + /// Shows a dialog asking the user if they like the app. + /// Returns the user's decision as an integer. + /// - PRE_RATING_DIALOG_YES: User likes the app and wants to rate it. + /// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback. + /// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog. + Future _showPreRatingDialog( + BuildContext context) async { + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).pre_rating_title), + content: + Text(AppLocalizations.of(context).pre_rating_message), + actions: [ + CupertinoDialogAction( + onPressed: () => Navigator.of(context) + .pop(PreRatingDialogDecision.yes), + isDefaultAction: true, + child: Text(AppLocalizations.of(context).yes), + ), + CupertinoDialogAction( + onPressed: () => + Navigator.of(context).pop(PreRatingDialogDecision.no), + child: Text(AppLocalizations.of(context).no), + ), + CupertinoDialogAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: Text(AppLocalizations.of(context).cancel), + ) + ], + )) ?? + PreRatingDialogDecision.cancel; + } + + /// Shows a dialog asking the user for feedback if they do not like the app. + /// Returns the user's decision as an integer. + /// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback. + /// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog. + Future _showBadRatingDialog( + BuildContext context) async { + return await showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).bad_rating_title), + content: + Text(AppLocalizations.of(context).bad_rating_message), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () => Navigator.of(context) + .pop(BadRatingDialogDecision.email), + child: Text(AppLocalizations.of(context).contact_email), + ), + CupertinoDialogAction( + isDestructiveAction: true, + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context).cancel)) + ], + )) ?? + BadRatingDialogDecision.cancel; } @override diff --git a/lib/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart similarity index 92% rename from lib/views/mode_selection_view.dart rename to lib/presentation/views/mode_selection_view.dart index 93cdc7a..a7d3ce7 100644 --- a/lib/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -1,5 +1,5 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; class ModeSelectionMenu extends StatelessWidget { diff --git a/lib/views/round_view.dart b/lib/presentation/views/round_view.dart similarity index 89% rename from lib/views/round_view.dart rename to lib/presentation/views/round_view.dart index 4e114fe..39e5cc8 100644 --- a/lib/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -1,7 +1,7 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_session.dart'; -import 'package:cabo_counter/l10n/app_localizations.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; @@ -67,19 +67,24 @@ 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, navigationBar: CupertinoNavigationBar( transitionBetweenRoutes: true, - middle: Text(AppLocalizations.of(context).results), leading: CupertinoButton( padding: EdgeInsets.zero, onPressed: () => {LocalStorageService.saveGameSessions(), Navigator.pop(context)}, child: Text(AppLocalizations.of(context).cancel), ), + middle: Text(AppLocalizations.of(context).results), + trailing: widget.gameSession.isGameFinished + ? const Icon( + CupertinoIcons.lock, + size: 25, + ) + : null, ), child: Stack( children: [ @@ -120,9 +125,8 @@ class _RoundViewState extends State { return MapEntry( index, Padding( - padding: EdgeInsets.symmetric( - horizontal: 4 + - _getSegmentedControlPadding(maxLength), + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 6, ), child: FittedBox( @@ -131,10 +135,8 @@ class _RoundViewState extends State { name, textAlign: TextAlign.center, maxLines: 1, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, - fontSize: _getSegmentedControlFontSize( - maxLength), ), ), ), @@ -293,21 +295,22 @@ class _RoundViewState extends State { : null, child: Text(AppLocalizations.of(context).done), ), - CupertinoButton( - onPressed: _areRoundInputsValid() - ? () { - _finishRound(); - LocalStorageService.saveGameSessions(); - if (widget.gameSession.isGameFinished == true) { - Navigator.pop(context); - } else { - Navigator.pop( - context, widget.roundNumber + 1); + if (!widget.gameSession.isGameFinished) + CupertinoButton( + onPressed: _areRoundInputsValid() + ? () { + _finishRound(); + LocalStorageService.saveGameSessions(); + if (widget.gameSession.isGameFinished) { + Navigator.pop(context); + } else { + Navigator.pop( + context, widget.roundNumber + 1); + } } - } - : null, - child: Text(AppLocalizations.of(context).next_round), - ), + : null, + child: Text(AppLocalizations.of(context).next_round), + ), ], ), ); @@ -386,32 +389,6 @@ class _RoundViewState extends State { } } - double _getSegmentedControlFontSize(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 _getSegmentedControlPadding(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/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart new file mode 100644 index 0000000..d6f0833 --- /dev/null +++ b/lib/presentation/views/settings_view.dart @@ -0,0 +1,269 @@ +import 'package:cabo_counter/core/constants.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; +import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; +import 'package:cabo_counter/services/config_service.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; +import 'package:cabo_counter/services/version_service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SettingsView extends StatefulWidget { + const SettingsView({super.key}); + + @override + State createState() => _SettingsViewState(); +} + +class _SettingsViewState extends State { + UniqueKey _stepperKey1 = UniqueKey(); + UniqueKey _stepperKey2 = UniqueKey(); + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(AppLocalizations.of(context).settings), + ), + child: SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).points, + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).cabo_penalty, + prefixIcon: CupertinoIcons.bolt_fill, + suffixWidget: CustomStepper( + key: _stepperKey1, + initialValue: ConfigService.caboPenalty, + minValue: 0, + maxValue: 50, + step: 1, + onChanged: (newCaboPenalty) { + setState(() { + ConfigService.setCaboPenalty(newCaboPenalty); + ConfigService.caboPenalty = newCaboPenalty; + }); + }, + ), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).point_limit, + prefixIcon: FontAwesomeIcons.bullseye, + suffixWidget: CustomStepper( + key: _stepperKey2, + initialValue: ConfigService.pointLimit, + minValue: 30, + maxValue: 1000, + step: 10, + onChanged: (newPointLimit) { + setState(() { + ConfigService.setPointLimit(newPointLimit); + ConfigService.pointLimit = newPointLimit; + }); + }, + ), + ), + CustomFormRow( + prefixText: + AppLocalizations.of(context).reset_to_default, + prefixIcon: CupertinoIcons.arrow_counterclockwise, + onPressed: () { + ConfigService.resetConfig(); + setState(() { + _stepperKey1 = UniqueKey(); + _stepperKey2 = UniqueKey(); + }); + }, + ) + ])), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).game_data, + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 10), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).import_data, + prefixIcon: CupertinoIcons.square_arrow_down, + onPressed: () async { + final status = + await LocalStorageService.importJsonFile(); + showFeedbackDialog(status); + }, + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).export_data, + prefixIcon: CupertinoIcons.square_arrow_up, + onPressed: () => LocalStorageService.exportGameData(), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).delete_data, + prefixIcon: CupertinoIcons.trash, + onPressed: () => _deleteAllGames(), + ), + ])), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).app, + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 15, 10, 0), + child: CupertinoFormSection.insetGrouped( + backgroundColor: CustomTheme.backgroundColor, + margin: EdgeInsets.zero, + children: [ + CustomFormRow( + prefixText: AppLocalizations.of(context).wiki, + prefixIcon: CupertinoIcons.book, + onPressed: () => + launchUrl(Uri.parse(Constants.kGithubWikiLink)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).privacy_policy, + prefixIcon: CupertinoIcons.doc_append, + onPressed: () => + launchUrl(Uri.parse(Constants.kPrivacyPolicyLink)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).error_found, + prefixIcon: FontAwesomeIcons.github, + onPressed: () => + launchUrl(Uri.parse(Constants.kGithubIssuesLink)), + suffixWidget: const CupertinoListTileChevron(), + ), + CustomFormRow( + prefixText: AppLocalizations.of(context).app_version, + prefixIcon: CupertinoIcons.tag, + onPressed: null, + suffixWidget: Text(VersionService.getVersion(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), + CustomFormRow( + prefixText: AppLocalizations.of(context).build, + prefixIcon: CupertinoIcons.number, + onPressed: null, + suffixWidget: Text(VersionService.getBuildNumber(), + style: TextStyle( + color: CustomTheme.primaryColor, + ))), + ])), + const SizedBox(height: 50) + ], + ), + )), + ); + } + + /// Shows a dialog to confirm the deletion of all game data. + /// When confirmed, it deletes all game data from local storage. + void _deleteAllGames() { + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text(AppLocalizations.of(context).delete_data_title), + content: Text(AppLocalizations.of(context).delete_data_message), + actions: [ + CupertinoDialogAction( + child: Text(AppLocalizations.of(context).cancel), + onPressed: () => Navigator.pop(context), + ), + CupertinoDialogAction( + isDestructiveAction: true, + isDefaultAction: true, + child: Text(AppLocalizations.of(context).delete), + onPressed: () { + LocalStorageService.deleteAllGames(); + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + } + + void showFeedbackDialog(ImportStatus status) { + if (status == ImportStatus.canceled) return; + 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), + ), + ], + ); + }); + } + + (String, String) _getDialogContent(ImportStatus status) { + switch (status) { + 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 ('', ''); + } + } +} diff --git a/lib/views/tab_view.dart b/lib/presentation/views/tab_view.dart similarity index 80% rename from lib/views/tab_view.dart rename to lib/presentation/views/tab_view.dart index 4abd411..0c98cc7 100644 --- a/lib/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -1,7 +1,7 @@ -import 'package:cabo_counter/l10n/app_localizations.dart'; -import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/views/information_view.dart'; -import 'package:cabo_counter/views/main_menu_view.dart'; +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/about_view.dart'; +import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:flutter/cupertino.dart'; class TabView extends StatefulWidget { @@ -39,7 +39,7 @@ class _TabViewState extends State { if (index == 0) { return const MainMenuView(); } else { - return const InformationView(); + return const AboutView(); } }); }, diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart new file mode 100644 index 0000000..7a2526b --- /dev/null +++ b/lib/presentation/widgets/custom_form_row.dart @@ -0,0 +1,53 @@ +import 'package:cabo_counter/core/custom_theme.dart'; +import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; +import 'package:flutter/cupertino.dart'; + +class CustomFormRow extends StatefulWidget { + final String prefixText; + final IconData prefixIcon; + final Widget? suffixWidget; + final void Function()? onPressed; + const CustomFormRow({ + super.key, + required this.prefixText, + required this.prefixIcon, + this.onPressed, + this.suffixWidget, + }); + + @override + State createState() => _CustomFormRowState(); +} + +class _CustomFormRowState extends State { + late Widget suffixWidget; + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + suffixWidget = widget.suffixWidget ?? const SizedBox.shrink(); + return CupertinoButton( + padding: EdgeInsets.zero, + onPressed: widget.onPressed, + child: CupertinoFormRow( + prefix: Row( + children: [ + Icon( + widget.prefixIcon, + color: CustomTheme.primaryColor, + ), + const SizedBox(width: 10), + Text(widget.prefixText), + ], + ), + padding: suffixWidget is CustomStepper + ? const EdgeInsets.fromLTRB(15, 0, 0, 0) + : const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + child: suffixWidget, + ), + ); + } +} diff --git a/lib/widgets/stepper.dart b/lib/presentation/widgets/custom_stepper.dart similarity index 75% rename from lib/widgets/stepper.dart rename to lib/presentation/widgets/custom_stepper.dart index 879235e..a05a4cb 100644 --- a/lib/widgets/stepper.dart +++ b/lib/presentation/widgets/custom_stepper.dart @@ -1,12 +1,13 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:flutter/cupertino.dart'; // Für iOS-Style -class Stepper extends StatefulWidget { +class CustomStepper extends StatefulWidget { final int minValue; final int maxValue; final int? initialValue; final int step; final ValueChanged onChanged; - const Stepper({ + const CustomStepper({ super.key, required this.minValue, required this.maxValue, @@ -17,10 +18,10 @@ class Stepper extends StatefulWidget { @override // ignore: library_private_types_in_public_api - _StepperState createState() => _StepperState(); + _CustomStepperState createState() => _CustomStepperState(); } -class _StepperState extends State { +class _CustomStepperState extends State { late int _value; @override @@ -34,18 +35,20 @@ class _StepperState extends State { Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, children: [ CupertinoButton( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.zero, onPressed: _decrement, child: const Icon(CupertinoIcons.minus), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Text('$_value', style: const TextStyle(fontSize: 18)), + child: Text('$_value', + style: TextStyle(fontSize: 18, color: CustomTheme.white)), ), CupertinoButton( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.zero, onPressed: _increment, child: const Icon(CupertinoIcons.add), ), diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 12130a1..29ac58b 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -21,7 +21,7 @@ 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 getGameDataAsJsonFile() { + 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 = getGameDataAsJsonFile(); + final jsonFile = _getGameDataAsJsonFile(); await file.writeAsString(jsonFile); print( '[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.'); @@ -70,7 +70,7 @@ class LocalStorageService { return false; } - if (!await validateJsonSchema(jsonString, true)) { + if (!await _validateJsonSchema(jsonString, true)) { print( '[local_storage_service.dart] Die Datei konnte nicht validiert werden'); gameManager.gameList = []; @@ -105,7 +105,7 @@ class LocalStorageService { /// 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( + static Future _exportJsonData( String jsonString, String fileName, ) async { @@ -133,16 +133,16 @@ class LocalStorageService { /// Opens the file picker to export all game sessions as a JSON file. static Future exportGameData() async { - String jsonString = getGameDataAsJsonFile(); + String jsonString = _getGameDataAsJsonFile(); String fileName = 'cabo_counter-game_data'; - return exportJsonData(jsonString, fileName); + return _exportJsonData(jsonString, fileName); } /// Opens the file picker to save a single game session as a JSON file. static Future exportSingleGameSession(GameSession session) async { String jsonString = json.encode(session.toJson()); String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}'; - return exportJsonData(jsonString, fileName); + return _exportJsonData(jsonString, fileName); } /// Opens the file picker to import a JSON file and loads the game data from it. @@ -162,7 +162,7 @@ class LocalStorageService { try { final jsonString = await _readFileContent(path.files.single); - if (await validateJsonSchema(jsonString, true)) { + if (await _validateJsonSchema(jsonString, true)) { // Checks if the JSON String is in the gameList format final jsonData = json.decode(jsonString) as List; @@ -172,12 +172,12 @@ class LocalStorageService { .toList(); for (GameSession s in importedList) { - importSession(s); + _importSession(s); } - } else if (await validateJsonSchema(jsonString, false)) { + } else if (await _validateJsonSchema(jsonString, false)) { // Checks if the JSON String is in the single game format final jsonData = json.decode(jsonString) as Map; - importSession(GameSession.fromJson(jsonData)); + _importSession(GameSession.fromJson(jsonData)); } else { return ImportStatus.validationError; } @@ -198,7 +198,7 @@ class LocalStorageService { } /// Imports a single game session into the gameList. - static Future importSession(GameSession session) async { + static Future _importSession(GameSession session) async { if (gameManager.gameExistsInGameList(session.id)) { print( '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.'); @@ -221,7 +221,7 @@ class LocalStorageService { /// 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( + static Future _validateJsonSchema( String jsonString, bool isGameList) async { final String schemaString; diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart new file mode 100644 index 0000000..6511c69 --- /dev/null +++ b/lib/services/version_service.dart @@ -0,0 +1,32 @@ +import 'package:cabo_counter/core/constants.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class VersionService { + static String _version = '-.-.-'; + static String _buildNumber = '-'; + + static Future init() async { + var packageInfo = await PackageInfo.fromPlatform(); + _version = packageInfo.version; + _buildNumber = packageInfo.buildNumber; + } + + static String getVersionNumber() { + return _version; + } + + static String getVersion() { + if (_version == '-.-.-') { + return getVersionNumber(); + } + return '${Constants.appDevPhase} $_version'; + } + + static String getBuildNumber() { + return _buildNumber; + } + + static String getVersionWithBuild() { + return '$_version ($_buildNumber)'; + } +} diff --git a/lib/utility/globals.dart b/lib/utility/globals.dart deleted file mode 100644 index e11a118..0000000 --- a/lib/utility/globals.dart +++ /dev/null @@ -1,3 +0,0 @@ -class Globals { - static String appDevPhase = 'Beta'; -} diff --git a/lib/views/graph_view.dart b/lib/views/graph_view.dart deleted file mode 100644 index 345c670..0000000 --- a/lib/views/graph_view.dart +++ /dev/null @@ -1,84 +0,0 @@ -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 { - 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_process), - previousPageTitle: AppLocalizations.of(context).back, - ), - 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/lib/views/settings_view.dart b/lib/views/settings_view.dart deleted file mode 100644 index 2e86122..0000000 --- a/lib/views/settings_view.dart +++ /dev/null @@ -1,267 +0,0 @@ -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/widgets/stepper.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class SettingsView extends StatefulWidget { - const SettingsView({super.key}); - - @override - State createState() => _SettingsViewState(); -} - -class _SettingsViewState extends State { - UniqueKey _stepperKey1 = UniqueKey(); - UniqueKey _stepperKey2 = UniqueKey(); - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(AppLocalizations.of(context).settings), - ), - child: SafeArea( - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).points, - style: CustomTheme.rowTitle, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoListTile( - padding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context).cabo_penalty), - subtitle: Text( - AppLocalizations.of(context).cabo_penalty_subtitle), - trailing: Stepper( - key: _stepperKey1, - initialValue: ConfigService.caboPenalty, - minValue: 0, - maxValue: 50, - step: 1, - onChanged: (newCaboPenalty) { - setState(() { - ConfigService.setCaboPenalty(newCaboPenalty); - ConfigService.caboPenalty = newCaboPenalty; - }); - }, - ), - )), - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoListTile( - padding: EdgeInsets.zero, - title: Text(AppLocalizations.of(context).point_limit), - subtitle: - Text(AppLocalizations.of(context).point_limit_subtitle), - trailing: Stepper( - key: _stepperKey2, - initialValue: ConfigService.pointLimit, - minValue: 30, - maxValue: 1000, - step: 10, - onChanged: (newPointLimit) { - setState(() { - ConfigService.setPointLimit(newPointLimit); - ConfigService.pointLimit = newPointLimit; - }); - }, - ), - )), - Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), - child: Center( - heightFactor: 0.9, - child: CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () => setState(() { - ConfigService.resetConfig(); - _stepperKey1 = UniqueKey(); - _stepperKey2 = UniqueKey(); - }), - child: - Text(AppLocalizations.of(context).reset_to_default), - ), - )), - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).game_data, - style: CustomTheme.rowTitle, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 30), - child: Center( - heightFactor: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CupertinoButton( - color: CustomTheme.primaryColor, - sizeStyle: CupertinoButtonSize.medium, - child: Text( - AppLocalizations.of(context).import_data, - style: - TextStyle(color: CustomTheme.backgroundColor), - ), - onPressed: () async { - final success = - await LocalStorageService.importJsonFile(); - showFeedbackDialog(success); - }), - const SizedBox( - width: 20, - ), - CupertinoButton( - color: CustomTheme.primaryColor, - sizeStyle: CupertinoButtonSize.medium, - child: Text( - AppLocalizations.of(context).export_data, - style: - TextStyle(color: CustomTheme.backgroundColor), - ), - onPressed: () async { - final success = - await LocalStorageService.exportGameData(); - 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), - ), - ], - ), - ); - } - }, - ), - ], - )), - ) - ], - ), - Positioned( - bottom: 30, - left: 0, - right: 0, - child: Column( - children: [ - Center( - child: Text(AppLocalizations.of(context).error_found), - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 30), - child: Center( - child: CupertinoButton( - onPressed: () => launchUrl(Uri.parse( - 'https://github.com/flixcoo/Cabo-Counter/issues')), - child: Text(AppLocalizations.of(context).create_issue), - ), - ), - ), - FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - '${Globals.appDevPhase} ${snapshot.data!.version} ' - '(${AppLocalizations.of(context).build} ${snapshot.data!.buildNumber})', - textAlign: TextAlign.center, - ); - } else if (snapshot.hasError) { - return Text( - '${AppLocalizations.of(context).app_version} -.-.- (${AppLocalizations.of(context).build} -)', - textAlign: TextAlign.center, - ); - } - return Text( - AppLocalizations.of(context).load_version, - textAlign: TextAlign.center, - ); - }, - ) - ], - )), - ], - )), - ); - } - - Future _getPackageInfo() async { - return await PackageInfo.fromPlatform(); - } - - void showFeedbackDialog(ImportStatus status) { - if (status == ImportStatus.canceled) return; - 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), - ), - ], - ); - }); - } - - (String, String) _getDialogContent(ImportStatus status) { - switch (status) { - 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 ('', ''); - } - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 562b189..da2c087 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+331 +version: 0.4.4+485 environment: sdk: ^3.5.4 @@ -27,6 +27,7 @@ dependencies: intl: any syncfusion_flutter_charts: ^30.1.37 uuid: ^4.5.1 + rate_my_app: ^2.3.2 dev_dependencies: flutter_test: From d296465b0422581620cabf0b34b41b8a2933d38d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 19:56:31 +0200 Subject: [PATCH 106/182] Updated point reduction --- lib/data/game_session.dart | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index 36c4c4e..d40e105 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -282,8 +282,8 @@ class GameSession extends ChangeNotifier { for (int i = 0; i < players.length; i++) { if (playerScores[i] == pointLimit) { print('${players[i]} hat genau 100 Punkte erreicht und bekommt ' - 'deswegen 50 Punkte abgezogen'); - roundList[roundNumber - 1].scoreUpdates[i] -= 50; + 'deswegen ${(pointLimit / 2).round()} Punkte abgezogen'); + roundList[roundNumber - 1].scoreUpdates[i] -= (pointLimit / 2).round(); } } _sumPoints(); diff --git a/pubspec.yaml b/pubspec.yaml index da2c087..7946bbb 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.4.4+485 +version: 0.4.4+486 environment: sdk: ^3.5.4 From 68477158e5768519f59a58788c7e2e54f1931b2f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 13 Jul 2025 23:57:15 +0200 Subject: [PATCH 107/182] Implemented bonus popup --- lib/data/game_session.dart | 11 +++-- lib/presentation/views/round_view.dart | 65 +++++++++++++++++++++++--- pubspec.yaml | 2 +- test/data/game_session_test.dart | 12 ++--- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index d40e105..54c095b 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -243,10 +243,11 @@ class GameSession extends ChangeNotifier { /// It then checks if any player has exceeded 100 points. If so, it sets /// isGameFinished to true and calls the _setWinner() method to determine /// the winner. - Future updatePoints() async { + List updatePoints() { + List bonusPlayers = []; _sumPoints(); if (isPointsLimitEnabled) { - _checkHundredPointsReached(); + bonusPlayers = _checkHundredPointsReached(); for (int i = 0; i < playerScores.length; i++) { if (playerScores[i] > pointLimit) { @@ -258,6 +259,7 @@ class GameSession extends ChangeNotifier { } } notifyListeners(); + return bonusPlayers; } @visibleForTesting @@ -278,15 +280,18 @@ class GameSession extends ChangeNotifier { /// Checks if a player has reached 100 points in the current round. /// If so, it updates the [scoreUpdate] List by subtracting 50 points from /// the corresponding round update. - void _checkHundredPointsReached() { + List _checkHundredPointsReached() { + List bonusPlayers = []; for (int i = 0; i < players.length; i++) { if (playerScores[i] == pointLimit) { + bonusPlayers.add(i); print('${players[i]} hat genau 100 Punkte erreicht und bekommt ' 'deswegen ${(pointLimit / 2).round()} Punkte abgezogen'); roundList[roundNumber - 1].scoreUpdates[i] -= (pointLimit / 2).round(); } } _sumPoints(); + return bonusPlayers; } /// Determines the winner of the game session. diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 39e5cc8..602c702 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -287,8 +287,11 @@ class _RoundViewState extends State { children: [ CupertinoButton( onPressed: _areRoundInputsValid() - ? () { - _finishRound(); + ? () async { + List boni = _finishRound(); + if (boni.isNotEmpty) { + await _showBonusPopup(context, boni); + } LocalStorageService.saveGameSessions(); Navigator.pop(context); } @@ -298,8 +301,11 @@ class _RoundViewState extends State { if (!widget.gameSession.isGameFinished) CupertinoButton( onPressed: _areRoundInputsValid() - ? () { - _finishRound(); + ? () async { + List boni = _finishRound(); + if (boni.isNotEmpty) { + await _showBonusPopup(context, boni); + } LocalStorageService.saveGameSessions(); if (widget.gameSession.isGameFinished) { Navigator.pop(context); @@ -359,7 +365,7 @@ class _RoundViewState extends State { /// every player. If the round is the highest round played in this game, /// it expands the player score lists. At the end it updates the score /// array for the game. - void _finishRound() { + List _finishRound() { print('===================================='); print('Runde ${widget.roundNumber} beendet'); // The shown round is smaller than the newest round @@ -381,12 +387,59 @@ class _RoundViewState extends State { widget.gameSession.calculateScoredPoints( widget.roundNumber, roundScores, _caboPlayerIndex); } - widget.gameSession.updatePoints(); + List bonusPlayers = widget.gameSession.updatePoints(); if (widget.gameSession.isGameFinished == true) { print('Das Spiel ist beendet'); } else if (widget.roundNumber == widget.gameSession.roundNumber) { widget.gameSession.increaseRound(); } + return bonusPlayers; + } + + /// Shows a popup dialog with the bonus information. + Future _showBonusPopup( + BuildContext context, List bonusPlayers) async { + print('Bonus Popup wird angezeigt'); + int pointLimit = widget.gameSession.pointLimit; + int bonusPoints = (pointLimit / 2).round(); + + String resultText = _getPopupString(pointLimit, bonusPoints, bonusPlayers); + + await showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: const Text('Bonus!'), + content: Text(resultText), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ), + ); + return true; + } + + /// Generates the string for the bonus popup. + /// It takes the [pointLimit], [bonusPoints] and the list of [bonusPlayers] + /// and returns a formatted string. + String _getPopupString( + int pointLimit, int bonusPoints, List bonusPlayers) { + List nameList = + bonusPlayers.map((i) => widget.gameSession.players[i]).toList(); + String resultText = ''; + if (nameList.length == 1) { + resultText = + '${nameList.first} hat exakt das Punktelimit von $pointLimit Punkten erreicht und bekommt deshalb $bonusPoints Punkte abgezogen!'; + } else { + resultText = nameList.length == 2 + ? '${nameList[0]} & ${nameList[1]}' + : '${nameList.sublist(0, nameList.length - 1).join(', ')} & ${nameList.last}'; + resultText += + ' haben exakt das Punktelimit von $pointLimit Punkten erreicht und bekommen deshalb $bonusPoints Punkte abgezogen!'; + } + return resultText; } @override diff --git a/pubspec.yaml b/pubspec.yaml index 7946bbb..f2b4949 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.4.4+486 +version: 0.4.4+488 environment: sdk: ^3.5.4 diff --git a/test/data/game_session_test.dart b/test/data/game_session_test.dart index de4e284..4ca2158 100644 --- a/test/data/game_session_test.dart +++ b/test/data/game_session_test.dart @@ -114,15 +114,15 @@ void main() { expect(session.roundList[0].caboPlayerIndex, 0); }); - test('updatePoints - game not finished', () async { + test('updatePoints - game not finished', () { session.addRoundScoresToList(1, [10, 20, 30], [10, 20, 30], 0); - await session.updatePoints(); + session.updatePoints(); expect(session.isGameFinished, isFalse); }); - test('updatePoints - game finished', () async { + test('updatePoints - game finished', () { session.addRoundScoresToList(1, [101, 20, 30], [101, 20, 30], 0); - await session.updatePoints(); + session.updatePoints(); expect(session.isGameFinished, isTrue); }); @@ -154,9 +154,9 @@ void main() { expect(session.playerScores, equals([50, 0, 30])); }); - test('_setWinner via updatePoints', () async { + test('_setWinner via updatePoints', () { session.addRoundScoresToList(1, [101, 20, 30], [101, 0, 30], 1); - await session.updatePoints(); + session.updatePoints(); expect(session.winner, 'Bob'); // Bob has lowest score (20) }); }); From 0a0da96a3f68b939177c403825077e8e4b06052c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:01:11 +0200 Subject: [PATCH 108/182] Created Strings for popup --- lib/l10n/arb/app_de.arb | 16 +++++++++++++++ lib/l10n/arb/app_en.arb | 16 +++++++++++++++ lib/l10n/generated/app_localizations.dart | 13 ++++++++++++ lib/l10n/generated/app_localizations_de.dart | 17 ++++++++++++++++ lib/l10n/generated/app_localizations_en.dart | 17 ++++++++++++++++ lib/presentation/views/round_view.dart | 21 ++++++++++---------- pubspec.yaml | 2 +- 7 files changed, 91 insertions(+), 11 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index b072d63..355ab43 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -73,6 +73,22 @@ "kamikaze": "Kamikaze", "done": "Fertig", "next_round": "Nächste Runde", + "bonus_points_title": "Bonus-Punkte!", + "bonus_points_message": "{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}", + "@bonus_points_message": { + "placeholders": { + "names": { + "type": "String" + }, + "pointLimit": { + "type": "int" + }, + "bonusPoints": { + "type": "int" + } + } + }, + "end_game": "Spiel beenden", "delete_game": "Spiel löschen", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a649362..08072f1 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -73,6 +73,22 @@ "kamikaze": "Kamikaze", "done": "Done", "next_round": "Next Round", + "bonus_points_title": "Bonus-Points!", + "bonus_points_message": "{playerCount, plural, =1{{names} has reached exactly the point limit of {pointLimit} points and therefore gets {bonusPoints} points deducted!} other{{names} have reached exactly the point limit of {pointLimit} points and therefore get {bonusPoints} points deducted!}}", + "@bonus_points_message": { + "placeholders": { + "names": { + "type": "String" + }, + "pointLimit": { + "type": "int" + }, + "bonusPoints": { + "type": "int" + } + } + }, + "end_game": "End Game", "delete_game": "Delete Game", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 2059f1b..5c91414 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -416,6 +416,19 @@ abstract class AppLocalizations { /// **'Nächste Runde'** String get next_round; + /// No description provided for @bonus_points_title. + /// + /// In de, this message translates to: + /// **'Bonus-Punkte!'** + String get bonus_points_title; + + /// No description provided for @bonus_points_message. + /// + /// In de, this message translates to: + /// **'{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}'** + String bonus_points_message( + String names, int pointLimit, int bonusPoints, num playerCount); + /// No description provided for @end_game. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 068711f..2007e48 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -178,6 +178,23 @@ class AppLocalizationsDe extends AppLocalizations { @override String get next_round => 'Nächste Runde'; + @override + String get bonus_points_title => 'Bonus-Punkte!'; + + @override + String bonus_points_message( + String names, int pointLimit, int bonusPoints, num playerCount) { + String _temp0 = intl.Intl.pluralLogic( + playerCount, + locale: localeName, + other: + '$names haben exakt das Punktelimit von $pointLimit Punkten erreicht und bekommen deshalb jeweils $bonusPoints Punkte abgezogen!', + one: + '$names hat exakt das Punktelimit von $pointLimit Punkten erreicht und bekommt deshalb $bonusPoints Punkte abgezogen!', + ); + return '$_temp0'; + } + @override String get end_game => 'Spiel beenden'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 06b5c03..0830f23 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -175,6 +175,23 @@ class AppLocalizationsEn extends AppLocalizations { @override String get next_round => 'Next Round'; + @override + String get bonus_points_title => 'Bonus-Points!'; + + @override + String bonus_points_message( + String names, int pointLimit, int bonusPoints, num playerCount) { + String _temp0 = intl.Intl.pluralLogic( + playerCount, + locale: localeName, + other: + '$names have reached exactly the point limit of $pointLimit points and therefore get $bonusPoints points deducted!', + one: + '$names has reached exactly the point limit of $pointLimit points and therefore gets $bonusPoints points deducted!', + ); + return '$_temp0'; + } + @override String get end_game => 'End Game'; diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 602c702..fdf2b13 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -403,17 +403,18 @@ class _RoundViewState extends State { int pointLimit = widget.gameSession.pointLimit; int bonusPoints = (pointLimit / 2).round(); - String resultText = _getPopupString(pointLimit, bonusPoints, bonusPlayers); + String resultText = + _getBonusPopupMessageString(pointLimit, bonusPoints, bonusPlayers); await showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( - title: const Text('Bonus!'), + title: Text(AppLocalizations.of(context).bonus_points_title), content: Text(resultText), actions: [ CupertinoDialogAction( - child: const Text('OK'), - onPressed: () => Navigator.of(context).pop(true), + child: Text(AppLocalizations.of(context).ok), + onPressed: () => Navigator.of(context).pop(), ), ], ), @@ -421,23 +422,23 @@ class _RoundViewState extends State { return true; } - /// Generates the string for the bonus popup. + /// Generates the message string for the bonus popup. /// It takes the [pointLimit], [bonusPoints] and the list of [bonusPlayers] /// and returns a formatted string. - String _getPopupString( + String _getBonusPopupMessageString( int pointLimit, int bonusPoints, List bonusPlayers) { List nameList = bonusPlayers.map((i) => widget.gameSession.players[i]).toList(); String resultText = ''; if (nameList.length == 1) { - resultText = - '${nameList.first} hat exakt das Punktelimit von $pointLimit Punkten erreicht und bekommt deshalb $bonusPoints Punkte abgezogen!'; + resultText = AppLocalizations.of(context).bonus_points_message( + nameList.first, pointLimit, bonusPoints, nameList.length); } else { resultText = nameList.length == 2 ? '${nameList[0]} & ${nameList[1]}' : '${nameList.sublist(0, nameList.length - 1).join(', ')} & ${nameList.last}'; - resultText += - ' haben exakt das Punktelimit von $pointLimit Punkten erreicht und bekommen deshalb $bonusPoints Punkte abgezogen!'; + resultText = AppLocalizations.of(context).bonus_points_message( + resultText, pointLimit, bonusPoints, nameList.length); } return resultText; } diff --git a/pubspec.yaml b/pubspec.yaml index f2b4949..4444d73 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.4.4+488 +version: 0.4.5+492 environment: sdk: ^3.5.4 From db6d4690cba5f863d8c0b0f3f1fa0b978407d53c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:09:12 +0200 Subject: [PATCH 109/182] Implemented mounted checks & changed return type --- lib/presentation/views/round_view.dart | 9 +++++---- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index fdf2b13..2d1f785 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -293,6 +293,7 @@ class _RoundViewState extends State { await _showBonusPopup(context, boni); } LocalStorageService.saveGameSessions(); + if (!context.mounted) return; Navigator.pop(context); } : null, @@ -307,9 +308,10 @@ class _RoundViewState extends State { await _showBonusPopup(context, boni); } LocalStorageService.saveGameSessions(); - if (widget.gameSession.isGameFinished) { + if (widget.gameSession.isGameFinished && + context.mounted) { Navigator.pop(context); - } else { + } else if (context.mounted) { Navigator.pop( context, widget.roundNumber + 1); } @@ -397,7 +399,7 @@ class _RoundViewState extends State { } /// Shows a popup dialog with the bonus information. - Future _showBonusPopup( + Future _showBonusPopup( BuildContext context, List bonusPlayers) async { print('Bonus Popup wird angezeigt'); int pointLimit = widget.gameSession.pointLimit; @@ -419,7 +421,6 @@ class _RoundViewState extends State { ], ), ); - return true; } /// Generates the message string for the bonus popup. diff --git a/pubspec.yaml b/pubspec.yaml index 4444d73..4d0b0f6 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.4.5+492 +version: 0.4.5+493 environment: sdk: ^3.5.4 From c24c271c8220b5fccb47daee2786d07a43c8c2a9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:14:53 +0200 Subject: [PATCH 110/182] Updated comment --- lib/data/game_session.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index 54c095b..d1402e5 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -235,7 +235,7 @@ class GameSession extends ChangeNotifier { /// This method updates the points of each player after a round. /// It first uses the _sumPoints() method to calculate the total points of each player. - /// Then, it checks if any player has reached 100 points. If so, it marks + /// Then, it checks if any player has reached 100 points. If so, saves their indices and marks /// that player as having reached 100 points in that corresponding [Round] object. /// If the game has the point limit activated, it first applies the /// _subtractPointsForReachingHundred() method to subtract 50 points @@ -243,6 +243,8 @@ class GameSession extends ChangeNotifier { /// It then checks if any player has exceeded 100 points. If so, it sets /// isGameFinished to true and calls the _setWinner() method to determine /// the winner. + /// It returns a list of players indices who reached 100 points in the current + /// round for the [RoundView] to show a popup List updatePoints() { List bonusPlayers = []; _sumPoints(); From a8298dfa21d3d6df8f06f7812d0bce340971a536 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:16:42 +0200 Subject: [PATCH 111/182] Updated variable name --- lib/presentation/views/round_view.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 2d1f785..45ce0ef 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -288,9 +288,10 @@ class _RoundViewState extends State { CupertinoButton( onPressed: _areRoundInputsValid() ? () async { - List boni = _finishRound(); - if (boni.isNotEmpty) { - await _showBonusPopup(context, boni); + List bonusPlayersIndices = _finishRound(); + if (bonusPlayersIndices.isNotEmpty) { + await _showBonusPopup( + context, bonusPlayersIndices); } LocalStorageService.saveGameSessions(); if (!context.mounted) return; @@ -303,9 +304,11 @@ class _RoundViewState extends State { CupertinoButton( onPressed: _areRoundInputsValid() ? () async { - List boni = _finishRound(); - if (boni.isNotEmpty) { - await _showBonusPopup(context, boni); + List bonusPlayersIndices = + _finishRound(); + if (bonusPlayersIndices.isNotEmpty) { + await _showBonusPopup( + context, bonusPlayersIndices); } LocalStorageService.saveGameSessions(); if (widget.gameSession.isGameFinished && From 047acfecd8df9123676bc73053cb42f51e3fea6a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:17:51 +0200 Subject: [PATCH 112/182] Updated string placeholders --- lib/l10n/arb/app_de.arb | 3 +++ lib/l10n/arb/app_en.arb | 3 +++ lib/presentation/views/round_view.dart | 8 ++++++-- pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 355ab43..93215ed 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -77,6 +77,9 @@ "bonus_points_message": "{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}", "@bonus_points_message": { "placeholders": { + "playerCount": { + "type": "int" + }, "names": { "type": "String" }, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 08072f1..19695a5 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -77,6 +77,9 @@ "bonus_points_message": "{playerCount, plural, =1{{names} has reached exactly the point limit of {pointLimit} points and therefore gets {bonusPoints} points deducted!} other{{names} have reached exactly the point limit of {pointLimit} points and therefore get {bonusPoints} points deducted!}}", "@bonus_points_message": { "placeholders": { + "playerCount": { + "type": "int" + }, "names": { "type": "String" }, diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 45ce0ef..a821fb5 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -436,13 +436,17 @@ class _RoundViewState extends State { String resultText = ''; if (nameList.length == 1) { resultText = AppLocalizations.of(context).bonus_points_message( - nameList.first, pointLimit, bonusPoints, nameList.length); + nameList.length, nameList.first, pointLimit, bonusPoints); } else { resultText = nameList.length == 2 ? '${nameList[0]} & ${nameList[1]}' : '${nameList.sublist(0, nameList.length - 1).join(', ')} & ${nameList.last}'; resultText = AppLocalizations.of(context).bonus_points_message( - resultText, pointLimit, bonusPoints, nameList.length); + nameList.length, + resultText, + pointLimit, + bonusPoints, + ); } return resultText; } diff --git a/pubspec.yaml b/pubspec.yaml index 4d0b0f6..9db5d4b 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.4.5+493 +version: 0.4.5+494 environment: sdk: ^3.5.4 From 79d0bdd19b7341a3a33dc7552f1f4d5e102a19cb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 10:19:51 +0200 Subject: [PATCH 113/182] Generated files update --- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5c91414..0a902f6 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -427,7 +427,7 @@ abstract class AppLocalizations { /// In de, this message translates to: /// **'{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}'** String bonus_points_message( - String names, int pointLimit, int bonusPoints, num playerCount); + int playerCount, String names, int pointLimit, int bonusPoints); /// No description provided for @end_game. /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 2007e48..7a71d00 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -183,7 +183,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String bonus_points_message( - String names, int pointLimit, int bonusPoints, num playerCount) { + int playerCount, String names, int pointLimit, int bonusPoints) { String _temp0 = intl.Intl.pluralLogic( playerCount, locale: localeName, diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 0830f23..4d4d663 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -180,7 +180,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String bonus_points_message( - String names, int pointLimit, int bonusPoints, num playerCount) { + int playerCount, String names, int pointLimit, int bonusPoints) { String _temp0 = intl.Intl.pluralLogic( playerCount, locale: localeName, From 16d1c017afbf2df8b3cc0a2f0e64fc8ee4bacc81 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:06:02 +0200 Subject: [PATCH 114/182] Updated _getPlacementPrefix method for dense ranks --- lib/presentation/views/active_game_view.dart | 65 ++++++++++++-------- pubspec.yaml | 2 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 704952a..c280e59 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -58,7 +58,8 @@ class _ActiveGameViewState extends State { return CupertinoListTile( title: Row( children: [ - _getPlacementPrefix(index), + _getPlacementPrefix( + index, gameSession.playerScores), const SizedBox(width: 5), Text( gameSession.players[playerIndex], @@ -266,36 +267,52 @@ class _ActiveGameViewState extends State { playerIndices.sort((a, b) { int scoreA = gameSession.playerScores[a]; int scoreB = gameSession.playerScores[b]; - return scoreA.compareTo(scoreB); + if (scoreA != scoreB) { + return scoreA.compareTo(scoreB); + } + return a.compareTo(b); }); return playerIndices; } - /// Returns a widget that displays the placement prefix based on the index. - /// First three places are represented by medals, and the rest are numbered. - /// [index] is the index of the player in the descending sorted list. - Widget _getPlacementPrefix(int index) { - switch (index) { - case 0: - return const Text( - '\u{1F947}', - style: TextStyle(fontSize: 22), - ); + /// Returns a widget representing the placement prefix for a player based on their index. + /// [index] is the index of the player in [players] list, + /// [playerScores] is a list of the players scores. + Widget _getPlacementPrefix(int index, playerScores) { + int placement = _calculateDenseRank(index, playerScores); + return _getPlacementTextWidget(placement); + } + + /// Calculates the dense rank for a player based on their index in the sorted list of players. + int _calculateDenseRank(int index, List playerScores) { + List sortedIndices = _getSortedPlayerIndices(); + List denseRanks = []; + int rank = 1; + for (int i = 0; i < sortedIndices.length; i++) { + if (i > 0) { + int prevScore = playerScores[sortedIndices[i - 1]]; + int currScore = playerScores[sortedIndices[i]]; + if (currScore != prevScore) { + rank++; + } + } + denseRanks.add(rank); + } + return denseRanks[index]; + } + + /// Returns a text widget representing the placement text based on the given placement number. + Text _getPlacementTextWidget(int placement) { + switch (placement) { case 1: - return const Text( - '\u{1F948}', - style: TextStyle(fontSize: 22), - ); + return const Text('\u{1F947}', style: TextStyle(fontSize: 22)); // 🥇 case 2: - return const Text( - '\u{1F949}', - style: TextStyle(fontSize: 22), - ); + return const Text('\u{1F948}', style: TextStyle(fontSize: 22)); // 🥈 + case 3: + return const Text('\u{1F949}', style: TextStyle(fontSize: 22)); // 🥉 default: - return Text( - ' ${index + 1}.', - style: const TextStyle(fontWeight: FontWeight.bold), - ); + return Text('$placement.', + style: const TextStyle(fontWeight: FontWeight.bold)); } } diff --git a/pubspec.yaml b/pubspec.yaml index 9db5d4b..d50b43a 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.4.5+494 +version: 0.4.6+497 environment: sdk: ^3.5.4 From 48784fd29048c4b8bd246c95aa1b56600e1673fe Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:07:14 +0200 Subject: [PATCH 115/182] Updated comments --- lib/presentation/views/active_game_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index c280e59..427fdc8 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -316,6 +316,7 @@ class _ActiveGameViewState extends State { } } + /// Shows a dialog to confirm deleting the game session. Future _showDeleteGameDialog() async { return await showCupertinoDialog( context: context, @@ -348,6 +349,8 @@ class _ActiveGameViewState extends State { false; } + /// Removes the game session in the game manager and navigates back to the previous screen. + /// If the game session does not exist in the game list, it shows an error dialog. Future _removeGameSession(GameSession gameSession) async { if (gameManager.gameExistsInGameList(gameSession.id)) { Navigator.pop(context); From f8bbbb04f3af4f08e571612bd1e8ca2061464e3a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:12:32 +0200 Subject: [PATCH 116/182] Added type annotation --- lib/presentation/views/active_game_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 427fdc8..8698c62 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -278,7 +278,7 @@ class _ActiveGameViewState extends State { /// Returns a widget representing the placement prefix for a player based on their index. /// [index] is the index of the player in [players] list, /// [playerScores] is a list of the players scores. - Widget _getPlacementPrefix(int index, playerScores) { + Widget _getPlacementPrefix(int index, List playerScores) { int placement = _calculateDenseRank(index, playerScores); return _getPlacementTextWidget(placement); } From 4a0ce067b5f3a91056baf9cf61fe0ffe2a554b67 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:14:22 +0200 Subject: [PATCH 117/182] Updated function _getSortedPlayerIndices() --- lib/presentation/views/active_game_view.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 8698c62..6eaee45 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -32,7 +32,10 @@ class _ActiveGameViewState extends State { return ListenableBuilder( listenable: gameSession, builder: (context, _) { - List sortedPlayerIndices = _getSortedPlayerIndices(); + List playerIndices = + List.generate(gameSession.players.length, (index) => index); + List sortedPlayerIndices = + _getSortedPlayerIndices(playerIndices); return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text(gameSession.gameTitle), @@ -260,9 +263,7 @@ class _ActiveGameViewState extends State { /// Returns a list of player indices sorted by their scores in /// ascending order. - List _getSortedPlayerIndices() { - List playerIndices = - List.generate(gameSession.players.length, (index) => index); + List _getSortedPlayerIndices(List playerIndices) { // Sort the indices based on the summed points playerIndices.sort((a, b) { int scoreA = gameSession.playerScores[a]; @@ -285,7 +286,7 @@ class _ActiveGameViewState extends State { /// Calculates the dense rank for a player based on their index in the sorted list of players. int _calculateDenseRank(int index, List playerScores) { - List sortedIndices = _getSortedPlayerIndices(); + List sortedIndices = _getSortedPlayerIndices(playerScores); List denseRanks = []; int rank = 1; for (int i = 0; i < sortedIndices.length; i++) { From 84e6f3f28653e95bbd7486f27c89d0f867b4454e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:14:44 +0200 Subject: [PATCH 118/182] Updated build method --- lib/presentation/views/active_game_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 6eaee45..2153d1d 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -29,13 +29,13 @@ class _ActiveGameViewState extends State { @override Widget build(BuildContext context) { + List playerIndices = + List.generate(gameSession.players.length, (index) => index); + List sortedPlayerIndices = _getSortedPlayerIndices(playerIndices); + return ListenableBuilder( listenable: gameSession, builder: (context, _) { - List playerIndices = - List.generate(gameSession.players.length, (index) => index); - List sortedPlayerIndices = - _getSortedPlayerIndices(playerIndices); return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text(gameSession.gameTitle), From 8b4a8bb86be4523807cff3ce428fbf30258e0926 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:30:55 +0200 Subject: [PATCH 119/182] Refactored and simplified --- lib/presentation/views/active_game_view.dart | 33 +++++++++----------- pubspec.yaml | 2 +- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 2153d1d..a4c52ee 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -20,6 +20,8 @@ class ActiveGameView extends StatefulWidget { class _ActiveGameViewState extends State { late final GameSession gameSession; + late final List denseRanks; + late List sortedPlayerIndices; @override void initState() { @@ -29,13 +31,11 @@ class _ActiveGameViewState extends State { @override Widget build(BuildContext context) { - List playerIndices = - List.generate(gameSession.players.length, (index) => index); - List sortedPlayerIndices = _getSortedPlayerIndices(playerIndices); - return ListenableBuilder( listenable: gameSession, builder: (context, _) { + sortedPlayerIndices = _getSortedPlayerIndices(); + denseRanks = _calculateDenseRank(gameSession.playerScores); return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text(gameSession.gameTitle), @@ -61,8 +61,7 @@ class _ActiveGameViewState extends State { return CupertinoListTile( title: Row( children: [ - _getPlacementPrefix( - index, gameSession.playerScores), + _getPlacementTextWidget(index), const SizedBox(width: 5), Text( gameSession.players[playerIndex], @@ -263,7 +262,9 @@ class _ActiveGameViewState extends State { /// Returns a list of player indices sorted by their scores in /// ascending order. - List _getSortedPlayerIndices(List playerIndices) { + List _getSortedPlayerIndices() { + List playerIndices = + List.generate(gameSession.players.length, (index) => index); // Sort the indices based on the summed points playerIndices.sort((a, b) { int scoreA = gameSession.playerScores[a]; @@ -276,17 +277,9 @@ class _ActiveGameViewState extends State { return playerIndices; } - /// Returns a widget representing the placement prefix for a player based on their index. - /// [index] is the index of the player in [players] list, - /// [playerScores] is a list of the players scores. - Widget _getPlacementPrefix(int index, List playerScores) { - int placement = _calculateDenseRank(index, playerScores); - return _getPlacementTextWidget(placement); - } - /// Calculates the dense rank for a player based on their index in the sorted list of players. - int _calculateDenseRank(int index, List playerScores) { - List sortedIndices = _getSortedPlayerIndices(playerScores); + List _calculateDenseRank(List playerScores) { + List sortedIndices = _getSortedPlayerIndices(); List denseRanks = []; int rank = 1; for (int i = 0; i < sortedIndices.length; i++) { @@ -299,11 +292,13 @@ class _ActiveGameViewState extends State { } denseRanks.add(rank); } - return denseRanks[index]; + return denseRanks; } /// Returns a text widget representing the placement text based on the given placement number. - Text _getPlacementTextWidget(int placement) { + /// [index] is the index of the player in [players] list, + Text _getPlacementTextWidget(int index) { + int placement = denseRanks[index]; switch (placement) { case 1: return const Text('\u{1F947}', style: TextStyle(fontSize: 22)); // 🥇 diff --git a/pubspec.yaml b/pubspec.yaml index d50b43a..95fea1d 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.4.6+497 +version: 0.4.6+504 environment: sdk: ^3.5.4 From f4120d28a9948c20214d167fc367ef763bd5d5b1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:38:28 +0200 Subject: [PATCH 120/182] Removed late --- lib/presentation/views/active_game_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index a4c52ee..1ea0a0d 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -20,7 +20,7 @@ class ActiveGameView extends StatefulWidget { class _ActiveGameViewState extends State { late final GameSession gameSession; - late final List denseRanks; + late List denseRanks; late List sortedPlayerIndices; @override From 5a939f4447e7bfb3d8ef651a5c04b372a7c8bf4e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:39:32 +0200 Subject: [PATCH 121/182] Unnessecary code --- lib/presentation/views/active_game_view.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 1ea0a0d..9715f77 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -35,7 +35,8 @@ class _ActiveGameViewState extends State { listenable: gameSession, builder: (context, _) { sortedPlayerIndices = _getSortedPlayerIndices(); - denseRanks = _calculateDenseRank(gameSession.playerScores); + denseRanks = _calculateDenseRank( + gameSession.playerScores, sortedPlayerIndices); return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text(gameSession.gameTitle), @@ -278,8 +279,8 @@ class _ActiveGameViewState extends State { } /// Calculates the dense rank for a player based on their index in the sorted list of players. - List _calculateDenseRank(List playerScores) { - List sortedIndices = _getSortedPlayerIndices(); + List _calculateDenseRank( + List playerScores, List sortedIndices) { List denseRanks = []; int rank = 1; for (int i = 0; i < sortedIndices.length; i++) { From f7676da88d15f0d49d559645c488aff7e3deab0c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 11:40:03 +0200 Subject: [PATCH 122/182] Added white space --- lib/presentation/views/active_game_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 9715f77..ab07804 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -308,7 +308,7 @@ class _ActiveGameViewState extends State { case 3: return const Text('\u{1F949}', style: TextStyle(fontSize: 22)); // 🥉 default: - return Text('$placement.', + return Text(' $placement.', style: const TextStyle(fontWeight: FontWeight.bold)); } } From 4cfbaa00c0280f95dc93e804dcb419fd0cdd9a3b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 12:20:47 +0200 Subject: [PATCH 123/182] Implemented reordering --- lib/presentation/views/round_view.dart | 96 +++++++++++++++++++++----- pubspec.yaml | 2 +- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index a821fb5..0c5b0e4 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -5,6 +5,7 @@ import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class RoundView extends StatefulWidget { final GameSession gameSession; @@ -67,6 +68,8 @@ class _RoundViewState extends State { @override Widget build(BuildContext context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; + final rotatedPlayers = _getRotatedPlayers(); + final originalIndices = _getOriginalIndices(); return CupertinoPageScaffold( resizeToAvoidBottomInset: false, @@ -175,9 +178,10 @@ class _RoundViewState extends State { ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: widget.gameSession.players.length, + itemCount: rotatedPlayers.length, itemBuilder: (context, index) { - final name = widget.gameSession.players[index]; + final originalIndex = originalIndices[index]; + final name = rotatedPlayers[index]; return Padding( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 20), @@ -187,13 +191,23 @@ class _RoundViewState extends State { backgroundColor: CupertinoColors.secondaryLabel, title: Row(children: [ Expanded( - child: Text( - name, - overflow: TextOverflow.ellipsis, - )) + child: Row(children: [ + Text( + name, + overflow: TextOverflow.ellipsis, + ), + Visibility( + visible: index == 0, + child: const SizedBox(width: 10), + ), + Visibility( + visible: index == 0, + child: const Icon(FontAwesomeIcons.medal, + size: 15)) + ])) ]), subtitle: Text( - '${widget.gameSession.playerScores[index]}' + '${widget.gameSession.playerScores[originalIndex]}' ' ${AppLocalizations.of(context).points}'), trailing: Row( children: [ @@ -201,7 +215,7 @@ class _RoundViewState extends State { width: 100, child: CupertinoTextField( maxLength: 3, - focusNode: _focusNodeList[index], + focusNode: _focusNodeList[originalIndex], keyboardType: const TextInputType.numberWithOptions( signed: true, @@ -216,12 +230,13 @@ class _RoundViewState extends State { 1 ? TextInputAction.done : TextInputAction.next, - controller: _scoreControllerList[index], + controller: + _scoreControllerList[originalIndex], placeholder: AppLocalizations.of(context).points, textAlign: TextAlign.center, onSubmitted: (_) => - _focusNextTextfield(index), + _focusNextTextfield(originalIndex), onChanged: (_) => setState(() {}), ), ), @@ -230,7 +245,8 @@ class _RoundViewState extends State { onTap: () { setState(() { _kamikazePlayerIndex = - (_kamikazePlayerIndex == index) + (_kamikazePlayerIndex == + originalIndex) ? null : index; }); @@ -240,17 +256,20 @@ class _RoundViewState extends State { height: 24, decoration: BoxDecoration( shape: BoxShape.circle, - color: _kamikazePlayerIndex == index + color: _kamikazePlayerIndex == + originalIndex ? CupertinoColors.systemRed : CupertinoColors .tertiarySystemFill, border: Border.all( - color: _kamikazePlayerIndex == index + color: _kamikazePlayerIndex == + originalIndex ? CupertinoColors.systemRed : CupertinoColors.systemGrey, ), ), - child: _kamikazePlayerIndex == index + child: _kamikazePlayerIndex == + originalIndex ? const Icon( CupertinoIcons.exclamationmark, size: 16, @@ -338,8 +357,12 @@ class _RoundViewState extends State { /// Focuses the next text field in the list of text fields. /// [index] is the index of the current text field. void _focusNextTextfield(int index) { - if (index < widget.gameSession.players.length - 1) { - FocusScope.of(context).requestFocus(_focusNodeList[index + 1]); + final originalIndices = _getOriginalIndices(); + final currentPos = originalIndices.indexOf(index); + + if (currentPos < originalIndices.length - 1) { + FocusScope.of(context) + .requestFocus(_focusNodeList[originalIndices[currentPos + 1]]); } else { _focusNodeList[index].unfocus(); } @@ -451,6 +474,47 @@ class _RoundViewState extends State { return resultText; } + List _getRotatedPlayers() { + final winnerIndex = _getPreviousRoundWinnerIndex(); + return [ + widget.gameSession.players[winnerIndex], + ...widget.gameSession.players.sublist(winnerIndex + 1), + ...widget.gameSession.players.sublist(0, winnerIndex) + ]; + } + + List _getOriginalIndices() { + final winnerIndex = _getPreviousRoundWinnerIndex(); + return [ + winnerIndex, + ...List.generate(widget.gameSession.players.length - winnerIndex - 1, + (i) => winnerIndex + i + 1), + ...List.generate(winnerIndex, (i) => i) + ]; + } + + int _getPreviousRoundWinnerIndex() { + if (widget.roundNumber == 1) { + return 0; // In der ersten Runde einfach den ersten Spieler nehmen + } + + final previousRound = widget.gameSession.roundList[widget.roundNumber - 2]; + final scores = previousRound.scores; + + // Finde den niedrigsten Score (Gewinner) + int minScore = scores[0]; + int winnerIndex = 0; + + for (int i = 1; i < scores.length; i++) { + if (scores[i] < minScore) { + minScore = scores[i]; + winnerIndex = i; + } + } + + return winnerIndex; + } + @override void dispose() { for (final controller in _scoreControllerList) { diff --git a/pubspec.yaml b/pubspec.yaml index 95fea1d..ba369cf 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.4.6+504 +version: 0.4.6+505 environment: sdk: ^3.5.4 From 37338dca766f2ef9efa467c1529b5c7f38ab58b6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 12:34:57 +0200 Subject: [PATCH 124/182] Refactoring --- lib/presentation/views/round_view.dart | 86 ++++++++++++++------------ 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 0c5b0e4..113791c 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -354,6 +354,51 @@ class _RoundViewState extends State { ); } + /// Gets the index of the player who won the previous round. + int _getPreviousRoundWinnerIndex() { + if (widget.roundNumber == 1) { + return 0; // If it's the first round, there's no previous round, so return 0. + } + + final previousRound = widget.gameSession.roundList[widget.roundNumber - 2]; + final scores = previousRound.scoreUpdates; + + // Find the index of the player with the minimum score + int minScore = scores[0]; + int winnerIndex = 0; + + // Iterate through the scores to find the player with the minimum score + for (int i = 1; i < scores.length; i++) { + if (scores[i] < minScore) { + minScore = scores[i]; + winnerIndex = i; + } + } + + return winnerIndex; + } + + /// Rotates the players list based on the previous round's winner. + List _getRotatedPlayers() { + final winnerIndex = _getPreviousRoundWinnerIndex(); + return [ + widget.gameSession.players[winnerIndex], + ...widget.gameSession.players.sublist(winnerIndex + 1), + ...widget.gameSession.players.sublist(0, winnerIndex) + ]; + } + + /// Gets the original indices of the players by recalculating it from the rotated list. + List _getOriginalIndices() { + final winnerIndex = _getPreviousRoundWinnerIndex(); + return [ + winnerIndex, + ...List.generate(widget.gameSession.players.length - winnerIndex - 1, + (i) => winnerIndex + i + 1), + ...List.generate(winnerIndex, (i) => i) + ]; + } + /// Focuses the next text field in the list of text fields. /// [index] is the index of the current text field. void _focusNextTextfield(int index) { @@ -474,47 +519,6 @@ class _RoundViewState extends State { return resultText; } - List _getRotatedPlayers() { - final winnerIndex = _getPreviousRoundWinnerIndex(); - return [ - widget.gameSession.players[winnerIndex], - ...widget.gameSession.players.sublist(winnerIndex + 1), - ...widget.gameSession.players.sublist(0, winnerIndex) - ]; - } - - List _getOriginalIndices() { - final winnerIndex = _getPreviousRoundWinnerIndex(); - return [ - winnerIndex, - ...List.generate(widget.gameSession.players.length - winnerIndex - 1, - (i) => winnerIndex + i + 1), - ...List.generate(winnerIndex, (i) => i) - ]; - } - - int _getPreviousRoundWinnerIndex() { - if (widget.roundNumber == 1) { - return 0; // In der ersten Runde einfach den ersten Spieler nehmen - } - - final previousRound = widget.gameSession.roundList[widget.roundNumber - 2]; - final scores = previousRound.scores; - - // Finde den niedrigsten Score (Gewinner) - int minScore = scores[0]; - int winnerIndex = 0; - - for (int i = 1; i < scores.length; i++) { - if (scores[i] < minScore) { - minScore = scores[i]; - winnerIndex = i; - } - } - - return winnerIndex; - } - @override void dispose() { for (final controller in _scoreControllerList) { From b225b28e32bb815f4c47babe4184a6d38bb214cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 12:35:13 +0200 Subject: [PATCH 125/182] Updated version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index ba369cf..5a10eca 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.4.6+505 +version: 0.4.7+505 environment: sdk: ^3.5.4 From 4478c00b9d770fb5810d4de97665f56fd8d1e29f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 12:42:30 +0200 Subject: [PATCH 126/182] Corrected kamikaze index --- lib/presentation/views/round_view.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 113791c..7c62120 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -248,7 +248,7 @@ class _RoundViewState extends State { (_kamikazePlayerIndex == originalIndex) ? null - : index; + : originalIndex; }); }, child: Container( diff --git a/pubspec.yaml b/pubspec.yaml index 5a10eca..6773a53 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.4.7+505 +version: 0.4.7+506 environment: sdk: ^3.5.4 From 287d57d973d04825412549525961a0a693f3a5a6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 14 Jul 2025 14:19:25 +0200 Subject: [PATCH 127/182] Update Version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23ec7d3..66eab9f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CABO Counter -![Version](https://img.shields.io/badge/Version-0.4.4-orange) +![Version](https://img.shields.io/badge/Version-0.4.7-orange) ![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) From cc6a7fee2543d3c4b6382816984c2e5866478dec Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 16 Jul 2025 14:27:38 +0200 Subject: [PATCH 128/182] Fixed bug with wrong medal icon --- lib/presentation/views/round_view.dart | 6 ++++-- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 7c62120..2506cde 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -197,11 +197,13 @@ class _RoundViewState extends State { overflow: TextOverflow.ellipsis, ), Visibility( - visible: index == 0, + visible: + index == 0 && widget.roundNumber != 1, child: const SizedBox(width: 10), ), Visibility( - visible: index == 0, + visible: + index == 0 && widget.roundNumber != 1, child: const Icon(FontAwesomeIcons.medal, size: 15)) ])) diff --git a/pubspec.yaml b/pubspec.yaml index 6773a53..6b71a0f 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.4.7+506 +version: 0.4.7+507 environment: sdk: ^3.5.4 From 77b551715fe6abca3b1bdc2481a0f215a87f206a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 16 Jul 2025 14:28:49 +0200 Subject: [PATCH 129/182] change not equal to greater than --- lib/presentation/views/round_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 2506cde..a7ddde1 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -198,12 +198,12 @@ class _RoundViewState extends State { ), Visibility( visible: - index == 0 && widget.roundNumber != 1, + index == 0 && widget.roundNumber > 1, child: const SizedBox(width: 10), ), Visibility( visible: - index == 0 && widget.roundNumber != 1, + index == 0 && widget.roundNumber > 1, child: const Icon(FontAwesomeIcons.medal, size: 15)) ])) From 2033f43843450ae1aa4fa7e96d1b1fd8c976afe5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 16 Jul 2025 14:32:40 +0200 Subject: [PATCH 130/182] Updated bool var --- lib/presentation/views/round_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index a7ddde1..9e2b40f 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -182,6 +182,8 @@ class _RoundViewState extends State { itemBuilder: (context, index) { final originalIndex = originalIndices[index]; final name = rotatedPlayers[index]; + bool shouldShowMedal = + index == 0 && widget.roundNumber > 1; return Padding( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 20), @@ -197,13 +199,11 @@ class _RoundViewState extends State { overflow: TextOverflow.ellipsis, ), Visibility( - visible: - index == 0 && widget.roundNumber > 1, + visible: shouldShowMedal, child: const SizedBox(width: 10), ), Visibility( - visible: - index == 0 && widget.roundNumber > 1, + visible: shouldShowMedal, child: const Icon(FontAwesomeIcons.medal, size: 15)) ])) From 1b4c377a13fa67ecfdb1b4ad46b611f2d4824741 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 18 Jul 2025 22:36:59 +0200 Subject: [PATCH 131/182] Fixed deletion error --- lib/presentation/views/main_menu_view.dart | 8 +++----- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 86ff208..6715ba6 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -126,7 +126,7 @@ class _MainMenuViewState extends State { listenable: session, builder: (context, _) { return Dismissible( - key: Key(session.gameTitle), + key: Key(session.id), background: Container( color: CupertinoColors.destructiveRed, alignment: Alignment.centerRight, @@ -139,14 +139,12 @@ class _MainMenuViewState extends State { ), direction: DismissDirection.endToStart, confirmDismiss: (direction) async { - final String gameTitle = gameManager - .gameList[index].gameTitle; return await _showDeleteGamePopup( - context, gameTitle); + context, session.gameTitle); }, onDismissed: (direction) { gameManager - .removeGameSessionByIndex(index); + .removeGameSessionById(session.id); }, dismissThresholds: const { DismissDirection.startToEnd: 0.6 diff --git a/pubspec.yaml b/pubspec.yaml index 6b71a0f..08e9ac7 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.4.7+507 +version: 0.4.7+509 environment: sdk: ^3.5.4 From 25552d7037d6df4e8d817e8d623447481db546bb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 18 Jul 2025 22:51:59 +0200 Subject: [PATCH 132/182] Small translation improvements --- lib/l10n/arb/app_en.arb | 32 ++++++++++---------- lib/l10n/generated/app_localizations_en.dart | 32 +++++++++++--------- lib/presentation/views/main_menu_view.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 19695a5..8a327a5 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -14,13 +14,13 @@ "player": "Player", "players": "Players", "name": "Name", - "back": "Back", + "back": "Back", "home": "Home", "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": "Create a new game using the button in the top right.", "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": { @@ -46,19 +46,19 @@ "select_mode": "Select a mode", "add_player": "Add Player", "create_game": "Create Game", - "max_players_title": "Maximum reached", - "max_players_message": "A maximum of 5 players can be added.", - "no_gameTitle_title": "No Title", - "no_gameTitle_message": "You must enter a title for the game.", - "no_mode_title": "No Mode", - "no_mode_message": "You must select a game mode.", - "min_players_title": "Too few players", - "min_players_message": "At least 2 players must be added.", - "no_name_title": "No Name", + "max_players_title": "Player Limit Reached", + "max_players_message": "You can add a maximum of 5 players.", + "no_gameTitle_title": "Missing Game Title", + "no_gameTitle_message": "Please enter a title for your game.", + "no_mode_title": "Game Mode Required", + "no_mode_message": "Please select a game mode to continue", + "min_players_title": "Too Few Players", + "min_players_message": "At least 2 players are required to start the game.", + "no_name_title": "Missing Player Names", "no_name_message": "Each player must have a name.", "select_game_mode": "Select game mode", - "point_limit_description": "The game ends when a player reaches more than {pointLimit} points.", + "point_limit_description": "The game ends when a player scores more than {pointLimit} points.", "@point_limit_description": { "placeholders": { "pointLimit": { @@ -66,10 +66,10 @@ } } }, - "unlimited_description": "There is no limit. The game continues until you decide to stop.", + "unlimited_description": "The game continues until you decide to stop playing", "results": "Results", - "who_said_cabo": "Who said CABO?", + "who_said_cabo": "Who called Cabo?", "kamikaze": "Kamikaze", "done": "Done", "next_round": "Next Round", @@ -107,9 +107,9 @@ "settings": "Settings", "cabo_penalty": "Cabo Penalty", - "cabo_penalty_subtitle": "... for falsely calling Cabo.", + "cabo_penalty_subtitle": "A point penalty for incorrectly calling Cabo.", "point_limit": "Point Limit", - "point_limit_subtitle": "... the game ends here.", + "point_limit_subtitle": "The required score to win the game.", "reset_to_default": "Reset to Default", "game_data": "Game Data", "import_data": "Import Data", diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 4d4d663..440d9cd 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -61,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.'; + 'Create a new game using the button in the top right.'; @override String get delete_game_title => 'Delete game?'; @@ -119,31 +119,32 @@ class AppLocalizationsEn extends AppLocalizations { String get create_game => 'Create Game'; @override - String get max_players_title => 'Maximum reached'; + String get max_players_title => 'Player Limit Reached'; @override - String get max_players_message => 'A maximum of 5 players can be added.'; + String get max_players_message => 'You can add a maximum of 5 players.'; @override - String get no_gameTitle_title => 'No Title'; + String get no_gameTitle_title => 'Missing Game Title'; @override - String get no_gameTitle_message => 'You must enter a title for the game.'; + String get no_gameTitle_message => 'Please enter a title for your game.'; @override - String get no_mode_title => 'No Mode'; + String get no_mode_title => 'Game Mode Required'; @override - String get no_mode_message => 'You must select a game mode.'; + String get no_mode_message => 'Please select a game mode to continue'; @override - String get min_players_title => 'Too few players'; + String get min_players_title => 'Too Few Players'; @override - String get min_players_message => 'At least 2 players must be added.'; + String get min_players_message => + 'At least 2 players are required to start the game.'; @override - String get no_name_title => 'No Name'; + String get no_name_title => 'Missing Player Names'; @override String get no_name_message => 'Each player must have a name.'; @@ -153,18 +154,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String point_limit_description(int pointLimit) { - return 'The game ends when a player reaches more than $pointLimit points.'; + return 'The game ends when a player scores more than $pointLimit points.'; } @override String get unlimited_description => - 'There is no limit. The game continues until you decide to stop.'; + 'The game continues until you decide to stop playing'; @override String get results => 'Results'; @override - String get who_said_cabo => 'Who said CABO?'; + String get who_said_cabo => 'Who called Cabo?'; @override String get kamikaze => 'Kamikaze'; @@ -232,13 +233,14 @@ 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 => + 'A point penalty for incorrectly 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 required score to win the game.'; @override String get reset_to_default => 'Reset to Default'; diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 6715ba6..1a818b6 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -72,7 +72,7 @@ class _MainMenuViewState extends State { }); }, icon: const Icon(CupertinoIcons.settings, size: 30)), - middle: const Text('Cabo Counter'), + middle: Text(AppLocalizations.of(context).app_name), trailing: IconButton( onPressed: () => Navigator.push( context, diff --git a/pubspec.yaml b/pubspec.yaml index 08e9ac7..a29cba1 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.4.7+509 +version: 0.4.7+512 environment: sdk: ^3.5.4 From c8de78ee77759bae62dbb23011f9d3b3f795d8f4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 18 Jul 2025 23:25:20 +0200 Subject: [PATCH 133/182] Implemented first version of point overview --- lib/presentation/views/active_game_view.dart | 13 +++ .../views/point_overview_view.dart | 93 +++++++++++++++++++ pubspec.yaml | 2 +- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 lib/presentation/views/point_overview_view.dart diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index ab07804..eeae49d 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -4,6 +4,7 @@ import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart'; +import 'package:cabo_counter/presentation/views/point_overview_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; @@ -135,6 +136,18 @@ class _ActiveGameViewState extends State { builder: (_) => GraphView( gameSession: gameSession, )))), + CupertinoListTile( + title: Text( + 'Übersicht', + ), + backgroundColorActivated: + CustomTheme.backgroundColor, + onTap: () => Navigator.push( + context, + CupertinoPageRoute( + builder: (_) => PointOverviewView( + gameSession: gameSession, + )))), Visibility( visible: !gameSession.isPointsLimitEnabled, child: CupertinoListTile( diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart new file mode 100644 index 0000000..f8e15ea --- /dev/null +++ b/lib/presentation/views/point_overview_view.dart @@ -0,0 +1,93 @@ +import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PointOverviewView extends StatefulWidget { + final GameSession gameSession; + + const PointOverviewView({super.key, required this.gameSession}); + + @override + State createState() => _PointOverviewViewState(); +} + +class _PointOverviewViewState extends State { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + resizeToAvoidBottomInset: true, + navigationBar: CupertinoNavigationBar( + middle: const Text('Punkte-Übersicht'), + previousPageTitle: AppLocalizations.of(context).back, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: DataTable( + dataRowMinHeight: 60, + dataRowMaxHeight: 60, + dividerThickness: 0.5, + columnSpacing: 20, + columns: [ + const DataColumn(label: Text('#')), + ...widget.gameSession.players.map( + (player) => DataColumn(label: Text(player)), + ), + ], + rows: List.generate( + widget.gameSession.roundList.length, + (roundIndex) { + final round = widget.gameSession.roundList[roundIndex]; + return DataRow( + cells: [ + DataCell(Align( + alignment: Alignment.center, + child: Text( + '$roundIndex', + style: const TextStyle( + fontWeight: FontWeight.bold, fontSize: 20), + ), + )), + ...List.generate(widget.gameSession.players.length, + (playerIndex) { + final score = round.scores[playerIndex]; + final update = round.scoreUpdates[playerIndex]; + return DataCell( + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: update <= 0 + ? Colors.green[200] + : Colors.red[200], + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '${update >= 0 ? '+' : '-'}$update', + style: TextStyle( + color: update <= 0 + ? Colors.green[900] + : Colors.red[900], + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 4), + Text('$score'), + ], + ), + ); + }), + ], + ); + }, + ), + ), + ))); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index a29cba1..07b2551 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.4.7+512 +version: 0.4.7+513 environment: sdk: ^3.5.4 From 5b418817b20c9078fc95c501bded0c38f9d66f25 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 18 Jul 2025 23:41:09 +0200 Subject: [PATCH 134/182] Visual improvements on table --- .../views/point_overview_view.dart | 78 ++++++++++++------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index f8e15ea..38d74c6 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; @@ -24,16 +25,32 @@ class _PointOverviewViewState extends State { child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: DataTable( dataRowMinHeight: 60, dataRowMaxHeight: 60, dividerThickness: 0.5, columnSpacing: 20, columns: [ - const DataColumn(label: Text('#')), + const DataColumn( + numeric: true, + headingRowAlignment: MainAxisAlignment.center, + label: Text( + '#', + style: TextStyle(fontWeight: FontWeight.bold), + ), + columnWidth: IntrinsicColumnWidth(flex: 0.5)), ...widget.gameSession.players.map( - (player) => DataColumn(label: Text(player)), + (player) => DataColumn( + label: FittedBox( + fit: BoxFit.fill, + child: Text( + player, + style: + const TextStyle(fontWeight: FontWeight.bold), + )), + headingRowAlignment: MainAxisAlignment.center, + columnWidth: const IntrinsicColumnWidth(flex: 1)), ), ], rows: List.generate( @@ -46,40 +63,47 @@ class _PointOverviewViewState extends State { alignment: Alignment.center, child: Text( '$roundIndex', - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 20), + style: const TextStyle(fontSize: 20), ), )), ...List.generate(widget.gameSession.players.length, (playerIndex) { final score = round.scores[playerIndex]; final update = round.scoreUpdates[playerIndex]; + final saidCabo = round.caboPlayerIndex == playerIndex + ? true + : false; return DataCell( - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: update <= 0 - ? Colors.green[200] - : Colors.red[200], - borderRadius: BorderRadius.circular(8), - ), - child: Text( - '${update >= 0 ? '+' : '-'}$update', - style: TextStyle( + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( color: update <= 0 - ? Colors.green[900] - : Colors.red[900], - fontWeight: FontWeight.bold, + ? CustomTheme.primaryColor + : CupertinoColors.destructiveRed, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '${update >= 0 ? '+' : '-'}$update', + style: const TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), ), ), - ), - const SizedBox(height: 4), - Text('$score'), - ], + const SizedBox(height: 4), + Text('$score', + style: TextStyle( + fontWeight: saidCabo + ? FontWeight.bold + : FontWeight.normal, + )), + ], + ), ), ); }), From 0d9c8a99cd3647931b059afd37d210258dfafeb0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 00:05:44 +0200 Subject: [PATCH 135/182] Added details and sum row --- .../views/point_overview_view.dart | 205 ++++++++++-------- pubspec.yaml | 2 +- 2 files changed, 116 insertions(+), 91 deletions(-) diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index 38d74c6..fe6373a 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -17,101 +17,126 @@ class _PointOverviewViewState extends State { @override Widget build(BuildContext context) { return CupertinoPageScaffold( - resizeToAvoidBottomInset: true, - navigationBar: CupertinoNavigationBar( - middle: const Text('Punkte-Übersicht'), - previousPageTitle: AppLocalizations.of(context).back, - ), - child: SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: DataTable( - dataRowMinHeight: 60, - dataRowMaxHeight: 60, - dividerThickness: 0.5, - columnSpacing: 20, - columns: [ - const DataColumn( - numeric: true, - headingRowAlignment: MainAxisAlignment.center, - label: Text( - '#', - style: TextStyle(fontWeight: FontWeight.bold), - ), - columnWidth: IntrinsicColumnWidth(flex: 0.5)), - ...widget.gameSession.players.map( - (player) => DataColumn( - label: FittedBox( - fit: BoxFit.fill, - child: Text( - player, - style: - const TextStyle(fontWeight: FontWeight.bold), - )), - headingRowAlignment: MainAxisAlignment.center, - columnWidth: const IntrinsicColumnWidth(flex: 1)), + resizeToAvoidBottomInset: true, + navigationBar: CupertinoNavigationBar( + middle: const Text('Punkte-Übersicht'), + previousPageTitle: AppLocalizations.of(context).back, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: DataTable( + dataRowMinHeight: 60, + dataRowMaxHeight: 60, + dividerThickness: 0.5, + columnSpacing: 20, + columns: [ + const DataColumn( + numeric: true, + headingRowAlignment: MainAxisAlignment.center, + label: Text( + '#', + style: TextStyle(fontWeight: FontWeight.bold), ), - ], - rows: List.generate( - widget.gameSession.roundList.length, - (roundIndex) { - final round = widget.gameSession.roundList[roundIndex]; - return DataRow( - cells: [ - DataCell(Align( - alignment: Alignment.center, - child: Text( - '$roundIndex', - style: const TextStyle(fontSize: 20), - ), + columnWidth: IntrinsicColumnWidth(flex: 0.5)), + ...widget.gameSession.players.map( + (player) => DataColumn( + label: FittedBox( + fit: BoxFit.fill, + child: Text( + player, + style: const TextStyle(fontWeight: FontWeight.bold), )), - ...List.generate(widget.gameSession.players.length, - (playerIndex) { - final score = round.scores[playerIndex]; - final update = round.scoreUpdates[playerIndex]; - final saidCabo = round.caboPlayerIndex == playerIndex - ? true - : false; - return DataCell( - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: update <= 0 - ? CustomTheme.primaryColor - : CupertinoColors.destructiveRed, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - '${update >= 0 ? '+' : '-'}$update', - style: const TextStyle( - color: CupertinoColors.white, - fontWeight: FontWeight.bold, - ), + headingRowAlignment: MainAxisAlignment.center, + columnWidth: const IntrinsicColumnWidth(flex: 1)), + ), + ], + rows: [ + ...List.generate( + widget.gameSession.roundList.length, + (roundIndex) { + final round = widget.gameSession.roundList[roundIndex]; + return DataRow( + cells: [ + DataCell(Align( + alignment: Alignment.center, + child: Text( + '$roundIndex', + style: const TextStyle(fontSize: 20), + ), + )), + ...List.generate(widget.gameSession.players.length, + (playerIndex) { + final score = round.scores[playerIndex]; + final update = round.scoreUpdates[playerIndex]; + final saidCabo = + round.caboPlayerIndex == playerIndex ? true : false; + return DataCell( + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: update <= 0 + ? CustomTheme.primaryColor + : CupertinoColors.destructiveRed, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '${update >= 0 ? '+' : '-'}$update', + style: const TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 4), - Text('$score', - style: TextStyle( - fontWeight: saidCabo - ? FontWeight.bold - : FontWeight.normal, - )), - ], - ), + ), + const SizedBox(height: 4), + Text('$score', + style: TextStyle( + fontWeight: saidCabo + ? FontWeight.bold + : FontWeight.normal, + )), + ], ), - ); - }), - ], - ); - }, - ), + ), + ); + }), + ], + ); + }, ), - ))); + DataRow( + cells: [ + const DataCell(Align( + alignment: Alignment.center, + child: Text( + 'Σ', + style: + TextStyle(fontSize: 25, fontWeight: FontWeight.bold), + ), + )), + ...widget.gameSession.playerScores.map( + (score) => DataCell( + Center( + child: Text( + '$score', + style: const TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 07b2551..817731d 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.4.7+513 +version: 0.4.7+515 environment: sdk: ^3.5.4 From c461cd0b2a33f90cdbbcca7312162ab5a09671b1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:06:02 +0200 Subject: [PATCH 136/182] Updated strings --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/arb/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 3 +++ lib/l10n/generated/app_localizations_en.dart | 3 +++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 93215ed..534a35c 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -92,7 +92,6 @@ } }, - "end_game": "Spiel beenden", "delete_game": "Spiel löschen", "new_game_same_settings": "Neues Spiel mit gleichen Einstellungen", @@ -102,6 +101,7 @@ "end_game_title": "Spiel beenden?", "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", + "table": "Punkteübersicht", "game_process": "Spielverlauf", "empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8a327a5..d0a19c5 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -102,6 +102,7 @@ "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.", + "table": "Point Overview", "game_process": "Scoring History", "empty_graph_text": "You must play at least one round for the game progress graph to be displayed.", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 0a902f6..ab51d4f 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -477,6 +477,12 @@ abstract class AppLocalizations { /// **'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 @table. + /// + /// In de, this message translates to: + /// **'Punkteübersicht'** + String get table; + /// No description provided for @game_process. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 7a71d00..9e25d98 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -221,6 +221,9 @@ class AppLocalizationsDe extends AppLocalizations { String get end_game_message => 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'; + @override + String get table => 'Punkteübersicht'; + @override String get game_process => 'Spielverlauf'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 440d9cd..eb29acb 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -219,6 +219,9 @@ class AppLocalizationsEn extends AppLocalizations { 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 table => 'Point Overview'; + @override String get game_process => 'Scoring History'; From 4bc3f65c712f2eb4f17fb3a5669556c2bb63605a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:06:15 +0200 Subject: [PATCH 137/182] Implemented new strings --- lib/presentation/views/active_game_view.dart | 2 +- lib/presentation/views/point_overview_view.dart | 3 +-- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index eeae49d..6bbf6bc 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -138,7 +138,7 @@ class _ActiveGameViewState extends State { )))), CupertinoListTile( title: Text( - 'Übersicht', + AppLocalizations.of(context).table, ), backgroundColorActivated: CustomTheme.backgroundColor, diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index fe6373a..31ea295 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -17,9 +17,8 @@ class _PointOverviewViewState extends State { @override Widget build(BuildContext context) { return CupertinoPageScaffold( - resizeToAvoidBottomInset: true, navigationBar: CupertinoNavigationBar( - middle: const Text('Punkte-Übersicht'), + middle: Text(AppLocalizations.of(context).table), previousPageTitle: AppLocalizations.of(context).back, ), child: SingleChildScrollView( diff --git a/pubspec.yaml b/pubspec.yaml index 817731d..e800775 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.4.7+515 +version: 0.4.7+519 environment: sdk: ^3.5.4 From 61d10d7164f2cb56180e07c379f88493dbc79947 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:07:09 +0200 Subject: [PATCH 138/182] Refactoring --- lib/l10n/arb/app_de.arb | 4 ++-- lib/l10n/arb/app_en.arb | 4 ++-- lib/l10n/generated/app_localizations.dart | 8 ++++---- lib/l10n/generated/app_localizations_de.dart | 4 ++-- lib/l10n/generated/app_localizations_en.dart | 4 ++-- lib/presentation/views/active_game_view.dart | 4 ++-- lib/presentation/views/graph_view.dart | 2 +- lib/presentation/views/point_overview_view.dart | 2 +- pubspec.yaml | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 534a35c..037050a 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -101,8 +101,8 @@ "end_game_title": "Spiel beenden?", "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", - "table": "Punkteübersicht", - "game_process": "Spielverlauf", + "point_overview": "Punkteübersicht", + "scoring_history": "Spielverlauf", "empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", "settings": "Einstellungen", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d0a19c5..d35978c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -102,8 +102,8 @@ "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.", - "table": "Point Overview", - "game_process": "Scoring History", + "point_overview": "Point Overview", + "scoring_history": "Scoring History", "empty_graph_text": "You must play at least one round for the game progress graph to be displayed.", "settings": "Settings", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index ab51d4f..97f8ebb 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -477,17 +477,17 @@ abstract class AppLocalizations { /// **'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 @table. + /// No description provided for @point_overview. /// /// In de, this message translates to: /// **'Punkteübersicht'** - String get table; + String get point_overview; - /// No description provided for @game_process. + /// No description provided for @scoring_history. /// /// In de, this message translates to: /// **'Spielverlauf'** - String get game_process; + String get scoring_history; /// No description provided for @empty_graph_text. /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 9e25d98..6f45f13 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -222,10 +222,10 @@ class AppLocalizationsDe extends AppLocalizations { 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'; @override - String get table => 'Punkteübersicht'; + String get point_overview => 'Punkteübersicht'; @override - String get game_process => 'Spielverlauf'; + String get scoring_history => 'Spielverlauf'; @override String get empty_graph_text => diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index eb29acb..5bdc175 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -220,10 +220,10 @@ class AppLocalizationsEn extends AppLocalizations { 'Do you want to end the game? The game gets marked as finished and cannot be continued.'; @override - String get table => 'Point Overview'; + String get point_overview => 'Point Overview'; @override - String get game_process => 'Scoring History'; + String get scoring_history => 'Scoring History'; @override String get empty_graph_text => diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 6bbf6bc..280ae2c 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -126,7 +126,7 @@ class _ActiveGameViewState extends State { children: [ CupertinoListTile( title: Text( - AppLocalizations.of(context).game_process, + AppLocalizations.of(context).scoring_history, ), backgroundColorActivated: CustomTheme.backgroundColor, @@ -138,7 +138,7 @@ class _ActiveGameViewState extends State { )))), CupertinoListTile( title: Text( - AppLocalizations.of(context).table, + AppLocalizations.of(context).point_overview, ), backgroundColorActivated: CustomTheme.backgroundColor, diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index d322bd0..043eaa3 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -27,7 +27,7 @@ class _GraphViewState extends State { Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text(AppLocalizations.of(context).game_process), + middle: Text(AppLocalizations.of(context).scoring_history), previousPageTitle: AppLocalizations.of(context).back, ), child: widget.gameSession.roundNumber > 1 diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index 31ea295..3f1f5d0 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -18,7 +18,7 @@ class _PointOverviewViewState extends State { Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text(AppLocalizations.of(context).table), + middle: Text(AppLocalizations.of(context).point_overview), previousPageTitle: AppLocalizations.of(context).back, ), child: SingleChildScrollView( diff --git a/pubspec.yaml b/pubspec.yaml index e800775..a00e5a1 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.4.7+519 +version: 0.4.7+520 environment: sdk: ^3.5.4 From d3374b8b8cf9daa44de800060c546ba5389f668b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:15:53 +0200 Subject: [PATCH 139/182] Updated graph displayment --- lib/presentation/views/graph_view.dart | 14 +++++++++++++- pubspec.yaml | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/graph_view.dart b/lib/presentation/views/graph_view.dart index 043eaa3..23137cd 100644 --- a/lib/presentation/views/graph_view.dart +++ b/lib/presentation/views/graph_view.dart @@ -34,17 +34,29 @@ class _GraphViewState extends State { ? Padding( padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), child: SfCartesianChart( + enableAxisAnimation: true, legend: const Legend( overflowMode: LegendItemOverflowMode.wrap, isVisible: true, position: LegendPosition.bottom), primaryXAxis: const NumericAxis( + labelStyle: TextStyle(fontWeight: FontWeight.bold), interval: 1, decimalPlaces: 0, ), - primaryYAxis: const NumericAxis( + primaryYAxis: NumericAxis( + labelStyle: const TextStyle(fontWeight: FontWeight.bold), + labelAlignment: LabelAlignment.center, + labelPosition: ChartDataLabelPosition.inside, interval: 1, decimalPlaces: 0, + axisLabelFormatter: (AxisLabelRenderDetails details) { + if (details.value == 0) { + return ChartAxisLabel('', const TextStyle()); + } + return ChartAxisLabel( + '${details.value.toInt()}', const TextStyle()); + }, ), series: getCumulativeScores(), ), diff --git a/pubspec.yaml b/pubspec.yaml index a00e5a1..e00e46a 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.4.7+520 +version: 0.4.7+521 environment: sdk: ^3.5.4 From 3b29014d290a4f5e1dd776075d8c1fa7abd87719 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:33:17 +0200 Subject: [PATCH 140/182] Moved new views to statistics section --- lib/l10n/arb/app_de.arb | 1 + lib/l10n/arb/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 3 +++ lib/l10n/generated/app_localizations_en.dart | 3 +++ lib/presentation/views/active_game_view.dart | 13 ++++++++++++- 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 037050a..399aefd 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -101,6 +101,7 @@ "end_game_title": "Spiel beenden?", "end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.", + "statistics": "Statistiken", "point_overview": "Punkteübersicht", "scoring_history": "Spielverlauf", "empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d35978c..4d067ad 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -102,6 +102,7 @@ "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.", + "statistics": "Statistics", "point_overview": "Point Overview", "scoring_history": "Scoring History", "empty_graph_text": "You must play at least one round for the game progress graph to be displayed.", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 97f8ebb..7ce3c52 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -477,6 +477,12 @@ abstract class AppLocalizations { /// **'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 @statistics. + /// + /// In de, this message translates to: + /// **'Statistiken'** + String get statistics; + /// No description provided for @point_overview. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 6f45f13..6539c20 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -221,6 +221,9 @@ class AppLocalizationsDe extends AppLocalizations { String get end_game_message => 'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'; + @override + String get statistics => 'Statistiken'; + @override String get point_overview => 'Punkteübersicht'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 5bdc175..7e026f6 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -219,6 +219,9 @@ class AppLocalizationsEn extends AppLocalizations { 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 statistics => 'Statistics'; + @override String get point_overview => 'Point Overview'; diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 280ae2c..7b88608 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -118,7 +118,7 @@ class _ActiveGameViewState extends State { Padding( padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( - AppLocalizations.of(context).game, + AppLocalizations.of(context).statistics, style: CustomTheme.rowTitle, ), ), @@ -148,6 +148,17 @@ class _ActiveGameViewState extends State { builder: (_) => PointOverviewView( gameSession: gameSession, )))), + ], + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).game, + style: CustomTheme.rowTitle, + ), + ), + Column( + children: [ Visibility( visible: !gameSession.isPointsLimitEnabled, child: CupertinoListTile( From 039dfffb5449c00414bbc5a4172a939849204661 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:33:31 +0200 Subject: [PATCH 141/182] Added seperator in main menu --- lib/presentation/views/main_menu_view.dart | 9 ++++++++- pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index 1a818b6..a80664d 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -118,8 +118,15 @@ class _MainMenuViewState extends State { ), ], ) - : ListView.builder( + : ListView.separated( itemCount: gameManager.gameList.length, + separatorBuilder: (context, index) => Divider( + height: 1, + thickness: 0.5, + color: CustomTheme.white.withAlpha(50), + indent: 50, + endIndent: 50, + ), itemBuilder: (context, index) { final session = gameManager.gameList[index]; return ListenableBuilder( diff --git a/pubspec.yaml b/pubspec.yaml index e00e46a..9f58ea5 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.4.7+521 +version: 0.4.8+522 environment: sdk: ^3.5.4 From 1cf7fdbac39867424107cc3cbe0c44fea8e8ef31 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:44:54 +0200 Subject: [PATCH 142/182] Renaming --- lib/presentation/views/point_overview_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index 3f1f5d0..f80b9b7 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -54,14 +54,14 @@ class _PointOverviewViewState extends State { rows: [ ...List.generate( widget.gameSession.roundList.length, - (roundIndex) { - final round = widget.gameSession.roundList[roundIndex]; + (roundNumber) { + final round = widget.gameSession.roundList[roundNumber]; return DataRow( cells: [ DataCell(Align( alignment: Alignment.center, child: Text( - '$roundIndex', + '$roundNumber', style: const TextStyle(fontSize: 20), ), )), From d9c0d40ff2286e88a446765996004e49e4d742aa Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:46:25 +0200 Subject: [PATCH 143/182] Updated sign --- lib/presentation/views/point_overview_view.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/point_overview_view.dart index f80b9b7..0abdaac 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/point_overview_view.dart @@ -86,7 +86,7 @@ class _PointOverviewViewState extends State { borderRadius: BorderRadius.circular(8), ), child: Text( - '${update >= 0 ? '+' : '-'}$update', + '${update >= 0 ? '+' : ''}$update', style: const TextStyle( color: CupertinoColors.white, fontWeight: FontWeight.bold, diff --git a/pubspec.yaml b/pubspec.yaml index 9f58ea5..63ab04c 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.4.8+522 +version: 0.4.8+523 environment: sdk: ^3.5.4 From 5a775dafd946b1d001515f4e24f704e22c0e35d5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:51:28 +0200 Subject: [PATCH 144/182] Updated colors & class name --- lib/core/custom_theme.dart | 4 ++++ lib/presentation/views/active_game_view.dart | 4 ++-- .../{point_overview_view.dart => points_view.dart} | 12 ++++++------ pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) rename lib/presentation/views/{point_overview_view.dart => points_view.dart} (93%) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index a00340b..fa78cb6 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -13,6 +13,10 @@ class CustomTheme { static const Color graphColor4 = Color(0xFF9C27B0); static final Color graphColor5 = primaryColor; + // Colors for PointsView + static Color pointLossColor = primaryColor; + static const Color pointGainColor = Color(0xFFF44336); + static TextStyle modeTitle = TextStyle( color: primaryColor, fontSize: 20, diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index 7b88608..defe5fc 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -4,7 +4,7 @@ import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart'; -import 'package:cabo_counter/presentation/views/point_overview_view.dart'; +import 'package:cabo_counter/presentation/views/points_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; @@ -145,7 +145,7 @@ class _ActiveGameViewState extends State { onTap: () => Navigator.push( context, CupertinoPageRoute( - builder: (_) => PointOverviewView( + builder: (_) => PointsView( gameSession: gameSession, )))), ], diff --git a/lib/presentation/views/point_overview_view.dart b/lib/presentation/views/points_view.dart similarity index 93% rename from lib/presentation/views/point_overview_view.dart rename to lib/presentation/views/points_view.dart index 0abdaac..555d5bd 100644 --- a/lib/presentation/views/point_overview_view.dart +++ b/lib/presentation/views/points_view.dart @@ -4,16 +4,16 @@ import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class PointOverviewView extends StatefulWidget { +class PointsView extends StatefulWidget { final GameSession gameSession; - const PointOverviewView({super.key, required this.gameSession}); + const PointsView({super.key, required this.gameSession}); @override - State createState() => _PointOverviewViewState(); + State createState() => _PointsViewState(); } -class _PointOverviewViewState extends State { +class _PointsViewState extends State { @override Widget build(BuildContext context) { return CupertinoPageScaffold( @@ -81,8 +81,8 @@ class _PointOverviewViewState extends State { horizontal: 6, vertical: 2), decoration: BoxDecoration( color: update <= 0 - ? CustomTheme.primaryColor - : CupertinoColors.destructiveRed, + ? CustomTheme.pointLossColor + : CustomTheme.pointGainColor, borderRadius: BorderRadius.circular(8), ), child: Text( diff --git a/pubspec.yaml b/pubspec.yaml index 63ab04c..7f8483c 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.4.8+523 +version: 0.4.8+525 environment: sdk: ^3.5.4 From 12b4d3d74102f01497af0e1c1ebeb428924848b9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:52:09 +0200 Subject: [PATCH 145/182] Removed empty line --- lib/l10n/arb/app_en.arb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 4d067ad..3b6150a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -92,7 +92,6 @@ } }, - "end_game": "End Game", "delete_game": "Delete Game", "new_game_same_settings": "New Game with same Settings", From 65d44ac7b13f3149ff7a504544e908abd5b303d1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:56:48 +0200 Subject: [PATCH 146/182] Updated round index --- lib/presentation/views/points_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/points_view.dart b/lib/presentation/views/points_view.dart index 555d5bd..4e0cded 100644 --- a/lib/presentation/views/points_view.dart +++ b/lib/presentation/views/points_view.dart @@ -54,14 +54,14 @@ class _PointsViewState extends State { rows: [ ...List.generate( widget.gameSession.roundList.length, - (roundNumber) { - final round = widget.gameSession.roundList[roundNumber]; + (roundIndex) { + final round = widget.gameSession.roundList[roundIndex]; return DataRow( cells: [ DataCell(Align( alignment: Alignment.center, child: Text( - '$roundNumber', + '${roundIndex + 1}', style: const TextStyle(fontSize: 20), ), )), From 91ba666521dcd07e2bb7115687a41864f8b951c2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 15:57:32 +0200 Subject: [PATCH 147/182] Updated types --- lib/presentation/views/points_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/points_view.dart b/lib/presentation/views/points_view.dart index 4e0cded..1379785 100644 --- a/lib/presentation/views/points_view.dart +++ b/lib/presentation/views/points_view.dart @@ -67,10 +67,10 @@ class _PointsViewState extends State { )), ...List.generate(widget.gameSession.players.length, (playerIndex) { - final score = round.scores[playerIndex]; - final update = round.scoreUpdates[playerIndex]; - final saidCabo = - round.caboPlayerIndex == playerIndex ? true : false; + final int score = round.scores[playerIndex]; + final int update = round.scoreUpdates[playerIndex]; + final bool saidCabo = + round.caboPlayerIndex == playerIndex; return DataCell( Center( child: Column( From 0ac446b0329265bdf0cc6c3a701ddd9f3c1dc77c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 16:46:20 +0200 Subject: [PATCH 148/182] Added new kamikaze button and bundles navigation functionality --- lib/l10n/arb/app_de.arb | 1 + lib/l10n/arb/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 ++ lib/l10n/generated/app_localizations_de.dart | 3 + lib/l10n/generated/app_localizations_en.dart | 3 + lib/presentation/views/round_view.dart | 93 +++++++++++++++----- pubspec.yaml | 2 +- 7 files changed, 84 insertions(+), 25 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 399aefd..dceb8fc 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -71,6 +71,7 @@ "results": "Ergebnisse", "who_said_cabo": "Wer hat CABO gesagt?", "kamikaze": "Kamikaze", + "who_has_kamikaze": "Wer hat Kamikaze?", "done": "Fertig", "next_round": "Nächste Runde", "bonus_points_title": "Bonus-Punkte!", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 3b6150a..3009d6e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -71,6 +71,7 @@ "results": "Results", "who_said_cabo": "Who called Cabo?", "kamikaze": "Kamikaze", + "who_has_kamikaze": "Who has Kamikaze?", "done": "Done", "next_round": "Next Round", "bonus_points_title": "Bonus-Points!", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 7ce3c52..695fb57 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -404,6 +404,12 @@ abstract class AppLocalizations { /// **'Kamikaze'** String get kamikaze; + /// No description provided for @who_has_kamikaze. + /// + /// In de, this message translates to: + /// **'Wer hat Kamikaze?'** + String get who_has_kamikaze; + /// No description provided for @done. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 6539c20..3ef54b8 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -172,6 +172,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get kamikaze => 'Kamikaze'; + @override + String get who_has_kamikaze => 'Wer hat Kamikaze?'; + @override String get done => 'Fertig'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7e026f6..8a86b95 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -170,6 +170,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get kamikaze => 'Kamikaze'; + @override + String get who_has_kamikaze => 'Who has Kamikaze?'; + @override String get done => 'Done'; diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 9e2b40f..45834e4 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -154,6 +154,17 @@ class _RoundViewState extends State { ), ), ), + Center( + child: CupertinoButton( + onPressed: () async { + if (await _showKamikazeSheet(context)) { + if (!context.mounted) return; + _endOfRoundNavigation(context, true); + } + }, + child: Text(AppLocalizations.of(context).kamikaze), + ), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: CupertinoListTile( @@ -308,15 +319,8 @@ class _RoundViewState extends State { children: [ CupertinoButton( onPressed: _areRoundInputsValid() - ? () async { - List bonusPlayersIndices = _finishRound(); - if (bonusPlayersIndices.isNotEmpty) { - await _showBonusPopup( - context, bonusPlayersIndices); - } - LocalStorageService.saveGameSessions(); - if (!context.mounted) return; - Navigator.pop(context); + ? () { + _endOfRoundNavigation(context, false); } : null, child: Text(AppLocalizations.of(context).done), @@ -324,21 +328,8 @@ class _RoundViewState extends State { if (!widget.gameSession.isGameFinished) CupertinoButton( onPressed: _areRoundInputsValid() - ? () async { - List bonusPlayersIndices = - _finishRound(); - if (bonusPlayersIndices.isNotEmpty) { - await _showBonusPopup( - context, bonusPlayersIndices); - } - LocalStorageService.saveGameSessions(); - if (widget.gameSession.isGameFinished && - context.mounted) { - Navigator.pop(context); - } else if (context.mounted) { - Navigator.pop( - context, widget.roundNumber + 1); - } + ? () { + _endOfRoundNavigation(context, true); } : null, child: Text(AppLocalizations.of(context).next_round), @@ -401,6 +392,36 @@ class _RoundViewState extends State { ]; } + Future _showKamikazeSheet(BuildContext context) async { + return await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return CupertinoActionSheet( + title: Text(AppLocalizations.of(context).kamikaze), + message: Text(AppLocalizations.of(context).who_has_kamikaze), + actions: widget.gameSession.players.asMap().entries.map((entry) { + final index = entry.key; + final name = entry.value; + return CupertinoActionSheetAction( + onPressed: () { + _kamikazePlayerIndex = + _kamikazePlayerIndex == index ? null : index; + Navigator.pop(context, true); + }, + child: Text(name), + ); + }).toList(), + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.pop(context, false), + isDestructiveAction: true, + child: Text(AppLocalizations.of(context).cancel), + ), + ); + }, + ) ?? + false; + } + /// Focuses the next text field in the list of text fields. /// [index] is the index of the current text field. void _focusNextTextfield(int index) { @@ -521,6 +542,30 @@ class _RoundViewState extends State { return resultText; } + /// Handles the navigation for the end of the round. + /// It checks for bonus players and shows a popup, saves the game session, + /// and navigates to the next round or back to the previous screen. + /// It takes the BuildContext [context] and a boolean [navigateToNextRound] to determine + /// if it should navigate to the next round or not. + Future _endOfRoundNavigation( + BuildContext context, bool navigateToNextRound) async { + List bonusPlayersIndices = _finishRound(); + if (bonusPlayersIndices.isNotEmpty) { + await _showBonusPopup(context, bonusPlayersIndices); + } + + LocalStorageService.saveGameSessions(); + + if (context.mounted) { + if (!navigateToNextRound || widget.gameSession.isGameFinished) { + Navigator.pop(context); + return; + } else { + Navigator.pop(context, widget.roundNumber + 1); + } + } + } + @override void dispose() { for (final controller in _scoreControllerList) { diff --git a/pubspec.yaml b/pubspec.yaml index 7f8483c..4a2e99d 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.4.8+525 +version: 0.4.8+526 environment: sdk: ^3.5.4 From 29a042ff16fc7c5a156b48cf6a26d244a51b0904 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:06:07 +0200 Subject: [PATCH 149/182] Updated lock icon --- lib/presentation/views/round_view.dart | 40 +++++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 45834e4..2e0edd9 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -74,21 +74,22 @@ class _RoundViewState extends State { return CupertinoPageScaffold( resizeToAvoidBottomInset: false, navigationBar: CupertinoNavigationBar( - transitionBetweenRoutes: true, - leading: CupertinoButton( - padding: EdgeInsets.zero, - onPressed: () => - {LocalStorageService.saveGameSessions(), Navigator.pop(context)}, - child: Text(AppLocalizations.of(context).cancel), - ), - middle: Text(AppLocalizations.of(context).results), - trailing: widget.gameSession.isGameFinished - ? const Icon( + transitionBetweenRoutes: true, + leading: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => { + LocalStorageService.saveGameSessions(), + Navigator.pop(context) + }, + child: Text(AppLocalizations.of(context).cancel), + ), + middle: Text(AppLocalizations.of(context).results), + trailing: Visibility( + visible: widget.gameSession.isGameFinished, + child: const Icon( CupertinoIcons.lock, size: 25, - ) - : null, - ), + ))), child: Stack( children: [ Positioned.fill( @@ -155,14 +156,25 @@ class _RoundViewState extends State { ), ), Center( + heightFactor: 1, child: CupertinoButton( + padding: const EdgeInsets.symmetric( + horizontal: 15, vertical: 0), + borderRadius: BorderRadius.circular(12), + color: CupertinoColors.systemRed, onPressed: () async { if (await _showKamikazeSheet(context)) { if (!context.mounted) return; _endOfRoundNavigation(context, true); } }, - child: Text(AppLocalizations.of(context).kamikaze), + child: Text( + AppLocalizations.of(context).kamikaze, + style: const TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), + ), ), ), Padding( From 81ada1c8103f9cb72fdcfd185ab3c1de9994a9da Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:18:52 +0200 Subject: [PATCH 150/182] Updated button position and design --- lib/presentation/views/round_view.dart | 45 +++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 2e0edd9..b0c24c8 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -155,28 +155,6 @@ class _RoundViewState extends State { ), ), ), - Center( - heightFactor: 1, - child: CupertinoButton( - padding: const EdgeInsets.symmetric( - horizontal: 15, vertical: 0), - borderRadius: BorderRadius.circular(12), - color: CupertinoColors.systemRed, - onPressed: () async { - if (await _showKamikazeSheet(context)) { - if (!context.mounted) return; - _endOfRoundNavigation(context, true); - } - }, - child: Text( - AppLocalizations.of(context).kamikaze, - style: const TextStyle( - color: CupertinoColors.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: CupertinoListTile( @@ -311,6 +289,29 @@ class _RoundViewState extends State { ); }, ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Center( + heightFactor: 1, + child: CupertinoButton( + sizeStyle: CupertinoButtonSize.medium, + borderRadius: BorderRadius.circular(15), + color: const Color(0xFF202020), + onPressed: () async { + if (await _showKamikazeSheet(context)) { + if (!context.mounted) return; + _endOfRoundNavigation(context, true); + } + }, + child: Text( + AppLocalizations.of(context).kamikaze, + style: const TextStyle( + color: CupertinoColors.destructiveRed, + ), + ), + ), + ), + ), ], ), ), From 5b704d39376e228671ba3aee0a2b39723cd82606 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:28:19 +0200 Subject: [PATCH 151/182] Removed title row and changed segmendetControl Padding --- lib/presentation/views/round_view.dart | 120 ++++++------------------- pubspec.yaml | 2 +- 2 files changed, 28 insertions(+), 94 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index b0c24c8..7d5739a 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -115,7 +115,7 @@ class _RoundViewState extends State { vertical: 10, ), child: SizedBox( - height: 40, + height: 60, child: CupertinoSegmentedControl( unselectedColor: CustomTheme.backgroundTintColor, selectedColor: CustomTheme.primaryColor, @@ -131,7 +131,7 @@ class _RoundViewState extends State { Padding( padding: const EdgeInsets.symmetric( horizontal: 6, - vertical: 6, + vertical: 8, ), child: FittedBox( fit: BoxFit.scaleDown, @@ -155,27 +155,6 @@ class _RoundViewState extends State { ), ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: CupertinoListTile( - title: Text(AppLocalizations.of(context).player), - trailing: Row( - children: [ - SizedBox( - width: 100, - child: Center( - child: Text( - AppLocalizations.of(context).points))), - const SizedBox(width: 20), - SizedBox( - width: 80, - child: Center( - child: Text(AppLocalizations.of(context) - .kamikaze))), - ], - ), - ), - ), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -212,77 +191,32 @@ class _RoundViewState extends State { subtitle: Text( '${widget.gameSession.playerScores[originalIndex]}' ' ${AppLocalizations.of(context).points}'), - trailing: Row( - children: [ - SizedBox( - width: 100, - child: CupertinoTextField( - maxLength: 3, - focusNode: _focusNodeList[originalIndex], - keyboardType: - const TextInputType.numberWithOptions( - signed: true, - decimal: false, - ), - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - textInputAction: index == - widget.gameSession.players - .length - - 1 - ? TextInputAction.done - : TextInputAction.next, - controller: - _scoreControllerList[originalIndex], - placeholder: - AppLocalizations.of(context).points, - textAlign: TextAlign.center, - onSubmitted: (_) => - _focusNextTextfield(originalIndex), - onChanged: (_) => setState(() {}), - ), + trailing: SizedBox( + width: 100, + child: CupertinoTextField( + maxLength: 3, + focusNode: _focusNodeList[originalIndex], + keyboardType: + const TextInputType.numberWithOptions( + signed: true, + decimal: false, ), - const SizedBox(width: 50), - GestureDetector( - onTap: () { - setState(() { - _kamikazePlayerIndex = - (_kamikazePlayerIndex == - originalIndex) - ? null - : originalIndex; - }); - }, - child: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _kamikazePlayerIndex == - originalIndex - ? CupertinoColors.systemRed - : CupertinoColors - .tertiarySystemFill, - border: Border.all( - color: _kamikazePlayerIndex == - originalIndex - ? CupertinoColors.systemRed - : CupertinoColors.systemGrey, - ), - ), - child: _kamikazePlayerIndex == - originalIndex - ? const Icon( - CupertinoIcons.exclamationmark, - size: 16, - color: CupertinoColors.white, - ) - : null, - ), - ), - const SizedBox(width: 22), - ], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + textInputAction: index == + widget.gameSession.players.length - 1 + ? TextInputAction.done + : TextInputAction.next, + controller: + _scoreControllerList[originalIndex], + placeholder: + AppLocalizations.of(context).points, + textAlign: TextAlign.center, + onSubmitted: (_) => + _focusNextTextfield(originalIndex), + onChanged: (_) => setState(() {}), + ), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 4a2e99d..f1bf78c 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.4.8+526 +version: 0.4.8+529 environment: sdk: ^3.5.4 From f0cfafd5e3678d71c1125a970742d1ff4e42bc3e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:40:34 +0200 Subject: [PATCH 152/182] Refactored logic and added comments --- lib/presentation/views/round_view.dart | 16 +++++++++++----- pubspec.yaml | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 7d5739a..3d65d17 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -351,8 +351,7 @@ class _RoundViewState extends State { final name = entry.value; return CupertinoActionSheetAction( onPressed: () { - _kamikazePlayerIndex = - _kamikazePlayerIndex == index ? null : index; + _kamikazePlayerIndex = index; Navigator.pop(context, true); }, child: Text(name), @@ -504,12 +503,19 @@ class _RoundViewState extends State { LocalStorageService.saveGameSessions(); if (context.mounted) { - if (!navigateToNextRound || widget.gameSession.isGameFinished) { + // If the game is finished, pop the context and return to the previous screen. + if (widget.gameSession.isGameFinished) { Navigator.pop(context); return; - } else { - Navigator.pop(context, widget.roundNumber + 1); } + // If navigateToNextRound is false, pop the context and return to the previous screen. + if (!navigateToNextRound) { + Navigator.pop(context); + return; + } + // If navigateToNextRound is true and the game isn't finished yet, + // pop the context and navigate to the next round. + Navigator.pop(context, widget.roundNumber + 1); } } diff --git a/pubspec.yaml b/pubspec.yaml index f1bf78c..d9cb654 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.4.8+529 +version: 0.4.9+529 environment: sdk: ^3.5.4 From cdcdd0b78ada780d6ef6255fd1e8435aaef88b7a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:41:37 +0200 Subject: [PATCH 153/182] Updated comment --- lib/presentation/views/round_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 3d65d17..45c6376 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -438,7 +438,7 @@ class _RoundViewState extends State { return bonusPlayers; } - /// Shows a popup dialog with the bonus information. + /// Shows a popup dialog with the information which player received the bonus points. Future _showBonusPopup( BuildContext context, List bonusPlayers) async { print('Bonus Popup wird angezeigt'); From d10fabb85a117cd4c04f96914f0c2a60133902b7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:47:51 +0200 Subject: [PATCH 154/182] Chaned icon --- lib/presentation/views/round_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 45c6376..050c2f2 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -184,7 +184,7 @@ class _RoundViewState extends State { ), Visibility( visible: shouldShowMedal, - child: const Icon(FontAwesomeIcons.medal, + child: const Icon(FontAwesomeIcons.crown, size: 15)) ])) ]), From 610cdc10e4bc2751a60c4cc2473146f376575303 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:51:08 +0200 Subject: [PATCH 155/182] Added comment --- lib/presentation/views/round_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 050c2f2..f9ef0ee 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -339,6 +339,8 @@ class _RoundViewState extends State { ]; } + /// Shows a Cupertino action sheet to select the player who has Kamikaze. + /// It returns true if a player was selected, false if the action was cancelled. Future _showKamikazeSheet(BuildContext context) async { return await showCupertinoModalPopup( context: context, From b7f6bf93484adfabd336312450b0ef3ec4e537ed Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 17:54:25 +0200 Subject: [PATCH 156/182] Removed print --- lib/presentation/views/round_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index f9ef0ee..690e256 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -443,7 +443,6 @@ class _RoundViewState extends State { /// Shows a popup dialog with the information which player received the bonus points. Future _showBonusPopup( BuildContext context, List bonusPlayers) async { - print('Bonus Popup wird angezeigt'); int pointLimit = widget.gameSession.pointLimit; int bonusPoints = (pointLimit / 2).round(); From a7521a3168859820091f8b4e51f2c3da297b0218 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 18:01:39 +0200 Subject: [PATCH 157/182] Updated colors --- lib/core/custom_theme.dart | 4 +++- lib/presentation/views/round_view.dart | 11 ++++++----- lib/presentation/views/tab_view.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index fa78cb6..e123779 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -4,7 +4,9 @@ class CustomTheme { static Color white = CupertinoColors.white; static Color primaryColor = CupertinoColors.systemGreen; static Color backgroundColor = const Color(0xFF101010); - static Color backgroundTintColor = CupertinoColors.darkBackgroundGray; + static Color mainElementbackgroundColor = CupertinoColors.darkBackgroundGray; + static Color playerTileColor = CupertinoColors.secondaryLabel; + static Color buttonBackgroundColor = const Color(0xFF202020); // Line Colors for GraphView static const Color graphColor1 = Color(0xFFF44336); diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 690e256..82af6dc 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -117,7 +117,8 @@ class _RoundViewState extends State { child: SizedBox( height: 60, child: CupertinoSegmentedControl( - unselectedColor: CustomTheme.backgroundTintColor, + unselectedColor: + CustomTheme.mainElementbackgroundColor, selectedColor: CustomTheme.primaryColor, groupValue: _caboPlayerIndex, children: Map.fromEntries(widget.gameSession.players @@ -170,7 +171,7 @@ class _RoundViewState extends State { child: ClipRRect( borderRadius: BorderRadius.circular(12), child: CupertinoListTile( - backgroundColor: CupertinoColors.secondaryLabel, + backgroundColor: CustomTheme.playerTileColor, title: Row(children: [ Expanded( child: Row(children: [ @@ -229,8 +230,8 @@ class _RoundViewState extends State { heightFactor: 1, child: CupertinoButton( sizeStyle: CupertinoButtonSize.medium, - borderRadius: BorderRadius.circular(15), - color: const Color(0xFF202020), + borderRadius: BorderRadius.circular(12), + color: CustomTheme.buttonBackgroundColor, onPressed: () async { if (await _showKamikazeSheet(context)) { if (!context.mounted) return; @@ -260,7 +261,7 @@ class _RoundViewState extends State { return Container( height: 80, padding: const EdgeInsets.only(bottom: 20), - color: CustomTheme.backgroundTintColor, + color: CustomTheme.mainElementbackgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index 0c98cc7..360737c 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -17,7 +17,7 @@ class _TabViewState extends State { Widget build(BuildContext context) { return CupertinoTabScaffold( tabBar: CupertinoTabBar( - backgroundColor: CustomTheme.backgroundTintColor, + backgroundColor: CustomTheme.mainElementbackgroundColor, iconSize: 27, height: 55, items: [ diff --git a/pubspec.yaml b/pubspec.yaml index d9cb654..37af518 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.4.9+529 +version: 0.4.9+533 environment: sdk: ^3.5.4 From f17eac14b9bcd47caa2daf2323ea949e3121bea2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 18:06:38 +0200 Subject: [PATCH 158/182] Changed var name --- lib/core/custom_theme.dart | 2 +- lib/presentation/views/round_view.dart | 4 ++-- lib/presentation/views/tab_view.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index e123779..bfc4f3c 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -4,7 +4,7 @@ class CustomTheme { static Color white = CupertinoColors.white; static Color primaryColor = CupertinoColors.systemGreen; static Color backgroundColor = const Color(0xFF101010); - static Color mainElementbackgroundColor = CupertinoColors.darkBackgroundGray; + static Color mainElementBackgroundColor = CupertinoColors.darkBackgroundGray; static Color playerTileColor = CupertinoColors.secondaryLabel; static Color buttonBackgroundColor = const Color(0xFF202020); diff --git a/lib/presentation/views/round_view.dart b/lib/presentation/views/round_view.dart index 82af6dc..f99380e 100644 --- a/lib/presentation/views/round_view.dart +++ b/lib/presentation/views/round_view.dart @@ -118,7 +118,7 @@ class _RoundViewState extends State { height: 60, child: CupertinoSegmentedControl( unselectedColor: - CustomTheme.mainElementbackgroundColor, + CustomTheme.mainElementBackgroundColor, selectedColor: CustomTheme.primaryColor, groupValue: _caboPlayerIndex, children: Map.fromEntries(widget.gameSession.players @@ -261,7 +261,7 @@ class _RoundViewState extends State { return Container( height: 80, padding: const EdgeInsets.only(bottom: 20), - color: CustomTheme.mainElementbackgroundColor, + color: CustomTheme.mainElementBackgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index 360737c..4b757fa 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -17,7 +17,7 @@ class _TabViewState extends State { Widget build(BuildContext context) { return CupertinoTabScaffold( tabBar: CupertinoTabBar( - backgroundColor: CustomTheme.mainElementbackgroundColor, + backgroundColor: CustomTheme.mainElementBackgroundColor, iconSize: 27, height: 55, items: [ From 21437e6b0ea345278d88a31f9341c603cc0c969c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 21:52:29 +0200 Subject: [PATCH 159/182] Removed unused strings --- lib/l10n/arb/app_de.arb | 3 +-- lib/l10n/arb/app_en.arb | 3 +-- lib/l10n/generated/app_localizations.dart | 12 +++--------- lib/l10n/generated/app_localizations_de.dart | 5 +---- lib/l10n/generated/app_localizations_en.dart | 6 +----- 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index dceb8fc..8fb9c31 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -109,9 +109,8 @@ "settings": "Einstellungen", "cabo_penalty": "Cabo-Strafe", - "cabo_penalty_subtitle": "... für falsches Cabo sagen", "point_limit": "Punkte-Limit", - "point_limit_subtitle": "... hier ist Schluss", + "standard_mode": "Standard-Modus", "reset_to_default": "Auf Standard zurücksetzen", "game_data": "Spieldaten", "import_data": "Spieldaten importieren", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 3009d6e..47893bc 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -109,9 +109,8 @@ "settings": "Settings", "cabo_penalty": "Cabo Penalty", - "cabo_penalty_subtitle": "A point penalty for incorrectly calling Cabo.", "point_limit": "Point Limit", - "point_limit_subtitle": "The required score to win the game.", + "standard_mode": "Default Mode", "reset_to_default": "Reset to Default", "game_data": "Game Data", "import_data": "Import Data", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 695fb57..2e1b880 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -519,23 +519,17 @@ abstract class AppLocalizations { /// **'Cabo-Strafe'** String get cabo_penalty; - /// No description provided for @cabo_penalty_subtitle. - /// - /// In de, this message translates to: - /// **'... für falsches Cabo sagen'** - String get cabo_penalty_subtitle; - /// No description provided for @point_limit. /// /// In de, this message translates to: /// **'Punkte-Limit'** String get point_limit; - /// No description provided for @point_limit_subtitle. + /// No description provided for @standard_mode. /// /// In de, this message translates to: - /// **'... hier ist Schluss'** - String get point_limit_subtitle; + /// **'Standard-Modus'** + String get standard_mode; /// No description provided for @reset_to_default. /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3ef54b8..43b68a7 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -243,14 +243,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get cabo_penalty => 'Cabo-Strafe'; - @override - String get cabo_penalty_subtitle => '... für falsches Cabo sagen'; - @override String get point_limit => 'Punkte-Limit'; @override - String get point_limit_subtitle => '... hier ist Schluss'; + String get standard_mode => 'Standard-Modus'; @override String get reset_to_default => 'Auf Standard zurücksetzen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 8a86b95..b54cf3d 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -241,15 +241,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get cabo_penalty => 'Cabo Penalty'; - @override - String get cabo_penalty_subtitle => - 'A point penalty for incorrectly calling Cabo.'; - @override String get point_limit => 'Point Limit'; @override - String get point_limit_subtitle => 'The required score to win the game.'; + String get standard_mode => 'Default Mode'; @override String get reset_to_default => 'Reset to Default'; From 97b32b5173ff03b2407887966400bbcdfaff4f8f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 21:52:37 +0200 Subject: [PATCH 160/182] Added gameMode --- lib/services/config_service.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 70f6133..02edc7d 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -6,12 +6,15 @@ import 'package:shared_preferences/shared_preferences.dart'; class ConfigService { static const String _keyPointLimit = 'pointLimit'; static const String _keyCaboPenalty = 'caboPenalty'; + static const String _keyGameMode = 'gameMode'; // Actual values used in the app static int pointLimit = 100; static int caboPenalty = 5; + static int gameMode = -1; // Default values static const int _defaultPointLimit = 100; static const int _defaultCaboPenalty = 5; + static const int _defaultGameMode = -1; static Future initConfig() async { final prefs = await SharedPreferences.getInstance(); @@ -21,6 +24,13 @@ class ConfigService { _keyPointLimit, prefs.getInt(_keyPointLimit) ?? _defaultPointLimit); prefs.setInt( _keyCaboPenalty, prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty); + prefs.setInt(_keyGameMode, prefs.getInt(_keyGameMode) ?? _defaultGameMode); + } + + static Future setGameMode(int newGameMode) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_keyGameMode, newGameMode); + gameMode = newGameMode; } /// Getter for the point limit. @@ -34,6 +44,7 @@ class ConfigService { static Future setPointLimit(int newPointLimit) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, newPointLimit); + pointLimit = newPointLimit; } /// Getter for the cabo penalty. @@ -47,6 +58,7 @@ class ConfigService { static Future setCaboPenalty(int newCaboPenalty) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyCaboPenalty, newCaboPenalty); + caboPenalty = newCaboPenalty; } /// Resets the configuration to default values. From 2690eac3eb2eca6493b850ba272e46cf0753f451 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 21:52:47 +0200 Subject: [PATCH 161/182] Changed creation variable --- lib/presentation/views/create_game_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 0d23494..fe3173d 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -117,6 +117,7 @@ class _CreateGameViewState extends State { CupertinoPageRoute( builder: (context) => ModeSelectionMenu( pointLimit: ConfigService.pointLimit, + showDeselection: false, ), ), ); From 482180d88e9d3a814987a7f503d56e3a40a3843e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 21:52:56 +0200 Subject: [PATCH 162/182] Updated mode selection --- .../views/mode_selection_view.dart | 21 ++++++++++++++++++- pubspec.yaml | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart index a7d3ce7..8ce0119 100644 --- a/lib/presentation/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -4,7 +4,9 @@ import 'package:flutter/cupertino.dart'; class ModeSelectionMenu extends StatelessWidget { final int pointLimit; - const ModeSelectionMenu({super.key, required this.pointLimit}); + final bool showDeselection; + const ModeSelectionMenu( + {super.key, required this.pointLimit, required this.showDeselection}); @override Widget build(BuildContext context) { @@ -14,6 +16,23 @@ class ModeSelectionMenu extends StatelessWidget { ), child: ListView( children: [ + Visibility( + visible: showDeselection, + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: CupertinoListTile( + title: + Text('Kein Standardmodus', style: CustomTheme.modeTitle), + subtitle: const Text( + 'Dein Standardmodus wird zurückgesetzt.', + style: CustomTheme.modeDescription, + maxLines: 3, + ), + onTap: () { + Navigator.pop(context); + }, + ), + )), Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: CupertinoListTile( diff --git a/pubspec.yaml b/pubspec.yaml index 37af518..623b5b9 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.4.9+533 +version: 0.4.9+537 environment: sdk: ^3.5.4 From 499c0c6dfd542b350af9b94b98320fee958e6ee7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:17:11 +0200 Subject: [PATCH 163/182] Updated strings --- lib/l10n/arb/app_de.arb | 3 +++ lib/l10n/arb/app_en.arb | 3 +++ lib/l10n/generated/app_localizations.dart | 18 ++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 9 +++++++++ lib/l10n/generated/app_localizations_en.dart | 9 +++++++++ 5 files changed, 42 insertions(+) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 8fb9c31..c68424c 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -58,6 +58,9 @@ "no_name_message": "Jeder Spieler muss einen Namen haben.", "select_game_mode": "Spielmodus auswählen", + "no_mode_selected": "Kein Modus ausgewählt", + "no_default_mode": "Kein Standard-Modus", + "no_default_description" : "Der Standard-Modus wird zurückgesetzt.", "point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht", "@point_limit_description": { "placeholders": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 47893bc..946ac74 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -58,6 +58,9 @@ "no_name_message": "Each player must have a name.", "select_game_mode": "Select game mode", + "no_mode_selected": "No mode selected", + "no_default_mode": "No default mode", + "no_default_description" : "The default mode gets resetted.", "point_limit_description": "The game ends when a player scores more than {pointLimit} points.", "@point_limit_description": { "placeholders": { diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 2e1b880..cc4eb12 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -374,6 +374,24 @@ abstract class AppLocalizations { /// **'Spielmodus auswählen'** String get select_game_mode; + /// No description provided for @no_mode_selected. + /// + /// In de, this message translates to: + /// **'Kein Modus ausgewählt'** + String get no_mode_selected; + + /// No description provided for @no_default_mode. + /// + /// In de, this message translates to: + /// **'Kein Standard-Modus'** + String get no_default_mode; + + /// No description provided for @no_default_description. + /// + /// In de, this message translates to: + /// **'Der Standard-Modus wird zurückgesetzt.'** + String get no_default_description; + /// No description provided for @point_limit_description. /// /// In de, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 43b68a7..3820070 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -154,6 +154,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get select_game_mode => 'Spielmodus auswählen'; + @override + String get no_mode_selected => 'Kein Modus ausgewählt'; + + @override + String get no_default_mode => 'Kein Standard-Modus'; + + @override + String get no_default_description => 'Der Standard-Modus wird zurückgesetzt.'; + @override String point_limit_description(int pointLimit) { return 'Es wird so lange gespielt, bis ein:e Spieler:in mehr als $pointLimit Punkte erreicht'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index b54cf3d..1344f96 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -152,6 +152,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_game_mode => 'Select game mode'; + @override + String get no_mode_selected => 'No mode selected'; + + @override + String get no_default_mode => 'No default mode'; + + @override + String get no_default_description => 'The default mode gets resetted.'; + @override String point_limit_description(int pointLimit) { return 'The game ends when a player scores more than $pointLimit points.'; From 98912d516427f8b2ad7d9ee9046068af06cb450a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:17:28 +0200 Subject: [PATCH 164/182] Changed mode order --- .../views/mode_selection_view.dart | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/mode_selection_view.dart b/lib/presentation/views/mode_selection_view.dart index 8ce0119..0424dab 100644 --- a/lib/presentation/views/mode_selection_view.dart +++ b/lib/presentation/views/mode_selection_view.dart @@ -2,6 +2,12 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; +enum GameMode { + none, + pointLimit, + unlimited, +} + class ModeSelectionMenu extends StatelessWidget { final int pointLimit; final bool showDeselection; @@ -16,23 +22,6 @@ class ModeSelectionMenu extends StatelessWidget { ), child: ListView( children: [ - Visibility( - visible: showDeselection, - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), - child: CupertinoListTile( - title: - Text('Kein Standardmodus', style: CustomTheme.modeTitle), - subtitle: const Text( - 'Dein Standardmodus wird zurückgesetzt.', - style: CustomTheme.modeDescription, - maxLines: 3, - ), - onTap: () { - Navigator.pop(context); - }, - ), - )), Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: CupertinoListTile( @@ -45,12 +34,12 @@ class ModeSelectionMenu extends StatelessWidget { maxLines: 3, ), onTap: () { - Navigator.pop(context, true); + Navigator.pop(context, GameMode.pointLimit); }, ), ), Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: CupertinoListTile( title: Text(AppLocalizations.of(context).unlimited, style: CustomTheme.modeTitle), @@ -60,10 +49,27 @@ class ModeSelectionMenu extends StatelessWidget { maxLines: 3, ), onTap: () { - Navigator.pop(context, false); + Navigator.pop(context, GameMode.unlimited); }, ), ), + Visibility( + visible: showDeselection, + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: CupertinoListTile( + title: Text(AppLocalizations.of(context).no_default_mode, + style: CustomTheme.modeTitle), + subtitle: Text( + AppLocalizations.of(context).no_default_description, + style: CustomTheme.modeDescription, + maxLines: 3, + ), + onTap: () { + Navigator.pop(context, GameMode.none); + }, + ), + )), ], ), ); From 2e1c5392a09845a66ea72b484697a5281a10ac27 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:17:41 +0200 Subject: [PATCH 165/182] Implemented default mode selection --- lib/presentation/views/settings_view.dart | 52 +++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index d6f0833..7247073 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -1,6 +1,7 @@ import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; +import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; import 'package:cabo_counter/services/config_service.dart'; @@ -20,6 +21,7 @@ class SettingsView extends StatefulWidget { class _SettingsViewState extends State { UniqueKey _stepperKey1 = UniqueKey(); UniqueKey _stepperKey2 = UniqueKey(); + int defaultMode = ConfigService.gameMode; @override void initState() { super.initState(); @@ -84,6 +86,56 @@ class _SettingsViewState extends State { }, ), ), + CustomFormRow( + prefixText: AppLocalizations.of(context).standard_mode, + prefixIcon: CupertinoIcons.airplane, + suffixWidget: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + defaultMode == -1 + ? AppLocalizations.of(context) + .no_mode_selected + : (defaultMode == 1 + ? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}' + : AppLocalizations.of(context).unlimited), + ), + const SizedBox(width: 10), + const CupertinoListTileChevron() + ], + ), + onPressed: () async { + final selectedMode = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ModeSelectionMenu( + pointLimit: ConfigService.pointLimit, + showDeselection: true, + ), + ), + ); + print('Selected mode: $selectedMode'); + + switch (selectedMode) { + case GameMode.pointLimit: + setState(() { + defaultMode = 1; + }); + break; + case GameMode.unlimited: + setState(() { + defaultMode = 0; + }); + break; + case GameMode.none: + default: + setState(() { + defaultMode = -1; + }); + } + ConfigService.setGameMode(defaultMode); + }, + ), CustomFormRow( prefixText: AppLocalizations.of(context).reset_to_default, diff --git a/pubspec.yaml b/pubspec.yaml index 623b5b9..13a8959 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.4.9+537 +version: 0.4.9+539 environment: sdk: ^3.5.4 From 193b66b175490f91ed798c7902a047139d10d22b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:17:48 +0200 Subject: [PATCH 166/182] Updated initState --- lib/presentation/views/create_game_view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index fe3173d..ab9aabb 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -48,8 +48,15 @@ class _CreateGameViewState extends State { @override void initState() { super.initState(); + print('pointLimit: $_isPointsLimitEnabled'); + + if (widget.isPointsLimitEnabled == null) { + _isPointsLimitEnabled = + ConfigService.gameMode == -1 ? null : ConfigService.gameMode == 1; + } else { + _isPointsLimitEnabled = widget.isPointsLimitEnabled; + } - _isPointsLimitEnabled = widget.isPointsLimitEnabled; _gameTitleTextController.text = widget.gameTitle ?? ''; if (widget.players != null) { @@ -91,7 +98,6 @@ class _CreateGameViewState extends State { controller: _gameTitleTextController, ), ), - // Spielmodus-Auswahl mit Chevron Padding( padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), child: CupertinoTextField( From 1a2a9b5df1a345a0ebb83ba5686db356efde16f8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:20:59 +0200 Subject: [PATCH 167/182] Removed print --- lib/presentation/views/create_game_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index ab9aabb..7928c4d 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -48,7 +48,6 @@ class _CreateGameViewState extends State { @override void initState() { super.initState(); - print('pointLimit: $_isPointsLimitEnabled'); if (widget.isPointsLimitEnabled == null) { _isPointsLimitEnabled = From a6ab7f76b0443b0487efba27a9476573670eb057 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:21:20 +0200 Subject: [PATCH 168/182] Removed print --- lib/presentation/views/settings_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 7247073..b08abf6 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -114,7 +114,6 @@ class _SettingsViewState extends State { ), ), ); - print('Selected mode: $selectedMode'); switch (selectedMode) { case GameMode.pointLimit: From 1935dc488cc49103570d53eb8f589f60cc2dcc5b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:25:30 +0200 Subject: [PATCH 169/182] Removed comments --- lib/presentation/views/create_game_view.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 7928c4d..563eb45 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -144,11 +144,9 @@ class _CreateGameViewState extends State { ), Expanded( child: ListView.builder( - itemCount: _playerNameTextControllers.length + - 1, // +1 für den + Button + itemCount: _playerNameTextControllers.length + 1, itemBuilder: (context, index) { if (index == _playerNameTextControllers.length) { - // + Button als letztes Element return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: CupertinoButton( From e9fe7262ab0c4a01f0cf3db72a03775eddb18292 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:30:16 +0200 Subject: [PATCH 170/182] Updated config service --- lib/services/config_service.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 02edc7d..2b463f5 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -25,6 +25,10 @@ class ConfigService { prefs.setInt( _keyCaboPenalty, prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty); prefs.setInt(_keyGameMode, prefs.getInt(_keyGameMode) ?? _defaultGameMode); + + pointLimit = prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; + caboPenalty = prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; + gameMode = prefs.getInt(_keyGameMode) ?? _defaultGameMode; } static Future setGameMode(int newGameMode) async { From cfa93b6917501625a2fbd532f531f450ffe2fa06 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:30:29 +0200 Subject: [PATCH 171/182] Changed create game view --- lib/presentation/views/create_game_view.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 563eb45..cdda7f9 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -127,10 +127,22 @@ class _CreateGameViewState extends State { ), ); - if (selectedMode != null) { - setState(() { - _isPointsLimitEnabled = selectedMode; - }); + switch (selectedMode) { + case GameMode.pointLimit: + setState(() { + _isPointsLimitEnabled = null; + }); + break; + case GameMode.unlimited: + setState(() { + _isPointsLimitEnabled = false; + }); + break; + case GameMode.none: + default: + setState(() { + _isPointsLimitEnabled = true; + }); } }, ), From aec3905c551cca62219f888d75b84ee0915b3973 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:34:43 +0200 Subject: [PATCH 172/182] Changed icon --- lib/presentation/views/settings_view.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index b08abf6..67b2fa0 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -64,7 +64,6 @@ class _SettingsViewState extends State { onChanged: (newCaboPenalty) { setState(() { ConfigService.setCaboPenalty(newCaboPenalty); - ConfigService.caboPenalty = newCaboPenalty; }); }, ), @@ -81,14 +80,13 @@ class _SettingsViewState extends State { onChanged: (newPointLimit) { setState(() { ConfigService.setPointLimit(newPointLimit); - ConfigService.pointLimit = newPointLimit; }); }, ), ), CustomFormRow( prefixText: AppLocalizations.of(context).standard_mode, - prefixIcon: CupertinoIcons.airplane, + prefixIcon: CupertinoIcons.square_stack, suffixWidget: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ From 7b8f49c1a486e2256a69831982eaad79fa0b8534 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:36:14 +0200 Subject: [PATCH 173/182] Updated strings --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c68424c..3c157ad 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -60,7 +60,7 @@ "select_game_mode": "Spielmodus auswählen", "no_mode_selected": "Kein Modus ausgewählt", "no_default_mode": "Kein Standard-Modus", - "no_default_description" : "Der Standard-Modus wird zurückgesetzt.", + "no_default_description": "Der Standard-Modus wird zurückgesetzt.", "point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht", "@point_limit_description": { "placeholders": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 946ac74..ea169e9 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -60,7 +60,7 @@ "select_game_mode": "Select game mode", "no_mode_selected": "No mode selected", "no_default_mode": "No default mode", - "no_default_description" : "The default mode gets resetted.", + "no_default_description": "The default mode gets reset.", "point_limit_description": "The game ends when a player scores more than {pointLimit} points.", "@point_limit_description": { "placeholders": { diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 1344f96..6b2689e 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -159,7 +159,7 @@ class AppLocalizationsEn extends AppLocalizations { String get no_default_mode => 'No default mode'; @override - String get no_default_description => 'The default mode gets resetted.'; + String get no_default_description => 'The default mode gets reset.'; @override String point_limit_description(int pointLimit) { diff --git a/pubspec.yaml b/pubspec.yaml index 13a8959..dca4d3a 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.4.9+539 +version: 0.4.9+540 environment: sdk: ^3.5.4 From 38213921afcc87129f97ac4353fa62f223f41db5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:44:47 +0200 Subject: [PATCH 174/182] Updated config --- lib/services/config_service.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 2b463f5..108b210 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -19,16 +19,16 @@ class ConfigService { static Future initConfig() async { final prefs = await SharedPreferences.getInstance(); - // Default values only set if they are not already set - prefs.setInt( - _keyPointLimit, prefs.getInt(_keyPointLimit) ?? _defaultPointLimit); - prefs.setInt( - _keyCaboPenalty, prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty); - prefs.setInt(_keyGameMode, prefs.getInt(_keyGameMode) ?? _defaultGameMode); - + // Initialize pointLimit, caboPenalty, and gameMode from SharedPreferences + // If they are not set, use the default values pointLimit = prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; caboPenalty = prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; gameMode = prefs.getInt(_keyGameMode) ?? _defaultGameMode; + + // Save the initial values to SharedPreferences + prefs.setInt(_keyPointLimit, pointLimit); + prefs.setInt(_keyCaboPenalty, caboPenalty); + prefs.setInt(_keyGameMode, gameMode); } static Future setGameMode(int newGameMode) async { From c31a3200e9905ec5a2edbbf80d10b262514194c8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:45:52 +0200 Subject: [PATCH 175/182] Updated mode selection logic --- lib/presentation/views/create_game_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index cdda7f9..2634788 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -130,7 +130,7 @@ class _CreateGameViewState extends State { switch (selectedMode) { case GameMode.pointLimit: setState(() { - _isPointsLimitEnabled = null; + _isPointsLimitEnabled = true; }); break; case GameMode.unlimited: @@ -141,7 +141,7 @@ class _CreateGameViewState extends State { case GameMode.none: default: setState(() { - _isPointsLimitEnabled = true; + _isPointsLimitEnabled = null; }); } }, From ac1851d501666dc2317e4894e21b49639c332836 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:53:13 +0200 Subject: [PATCH 176/182] Deleted getter --- lib/services/config_service.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 108b210..a360fe4 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -37,12 +37,6 @@ class ConfigService { gameMode = newGameMode; } - /// Getter for the point limit. - static Future getPointLimit() async { - final prefs = await SharedPreferences.getInstance(); - return prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; - } - /// Setter for the point limit. /// [newPointLimit] is the new point limit to be set. static Future setPointLimit(int newPointLimit) async { @@ -51,12 +45,6 @@ class ConfigService { pointLimit = newPointLimit; } - /// Getter for the cabo penalty. - static Future getCaboPenalty() async { - final prefs = await SharedPreferences.getInstance(); - return prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; - } - /// Setter for the cabo penalty. /// [newCaboPenalty] is the new cabo penalty to be set. static Future setCaboPenalty(int newCaboPenalty) async { @@ -69,6 +57,7 @@ class ConfigService { static Future resetConfig() async { ConfigService.pointLimit = _defaultPointLimit; ConfigService.caboPenalty = _defaultCaboPenalty; + ConfigService.gameMode = _defaultGameMode; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, _defaultPointLimit); await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty); From 7789c05174e5030eda05f35f9c41dd9864053717 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:53:22 +0200 Subject: [PATCH 177/182] Removed not used code --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 9279426..8b45434 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,8 +12,6 @@ Future main() async { await SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); await ConfigService.initConfig(); - ConfigService.pointLimit = await ConfigService.getPointLimit(); - ConfigService.caboPenalty = await ConfigService.getCaboPenalty(); await VersionService.init(); runApp(const App()); } From 9c797e1e5eba42e1135a1f99e460341b5464211d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 22:53:32 +0200 Subject: [PATCH 178/182] Implemented reset logic for default game mode --- lib/presentation/views/settings_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 67b2fa0..c649a20 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -142,6 +142,7 @@ class _SettingsViewState extends State { setState(() { _stepperKey1 = UniqueKey(); _stepperKey2 = UniqueKey(); + defaultMode = ConfigService.gameMode; }); }, ) From d660fb06276f98d6bab960e8c4251183dee235a1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 23:00:02 +0200 Subject: [PATCH 179/182] Updated to 0.5.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index dca4d3a..600700e 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.4.9+540 +version: 0.5.0+540 environment: sdk: ^3.5.4 From 96471a5764b773d33d639f1ea9c33b952f45ee89 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 19 Jul 2025 23:06:38 +0200 Subject: [PATCH 180/182] Hotfix: Pixel Overflow --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- lib/presentation/views/settings_view.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3c157ad..5032baf 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -58,7 +58,7 @@ "no_name_message": "Jeder Spieler muss einen Namen haben.", "select_game_mode": "Spielmodus auswählen", - "no_mode_selected": "Kein Modus ausgewählt", + "no_mode_selected": "Kein Modus", "no_default_mode": "Kein Standard-Modus", "no_default_description": "Der Standard-Modus wird zurückgesetzt.", "point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index cc4eb12..40d8c65 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -377,7 +377,7 @@ abstract class AppLocalizations { /// No description provided for @no_mode_selected. /// /// In de, this message translates to: - /// **'Kein Modus ausgewählt'** + /// **'Kein Modus'** String get no_mode_selected; /// No description provided for @no_default_mode. diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3820070..b73ccba 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -155,7 +155,7 @@ class AppLocalizationsDe extends AppLocalizations { String get select_game_mode => 'Spielmodus auswählen'; @override - String get no_mode_selected => 'Kein Modus ausgewählt'; + String get no_mode_selected => 'Kein Modus'; @override String get no_default_mode => 'Kein Standard-Modus'; diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index c649a20..596866d 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -98,7 +98,7 @@ class _SettingsViewState extends State { ? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}' : AppLocalizations.of(context).unlimited), ), - const SizedBox(width: 10), + const SizedBox(width: 5), const CupertinoListTileChevron() ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 600700e..3861d64 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.5.0+540 +version: 0.5.0+542 environment: sdk: ^3.5.4 From 261e8f80c11c582301ee0fe7ed88a53c4f54bca8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 20 Jul 2025 16:34:00 +0200 Subject: [PATCH 181/182] Changed the overall return type for gamemodes --- lib/l10n/arb/app_de.arb | 6 +- lib/l10n/generated/app_localizations.dart | 6 +- lib/l10n/generated/app_localizations_de.dart | 7 ++- lib/presentation/views/active_game_view.dart | 9 ++- lib/presentation/views/create_game_view.dart | 57 +++++++---------- lib/presentation/views/main_menu_view.dart | 13 ++-- lib/presentation/views/settings_view.dart | 39 ++++-------- lib/services/config_service.dart | 64 ++++++++++++++------ pubspec.yaml | 2 +- 9 files changed, 103 insertions(+), 100 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 5032baf..c5fae28 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -58,9 +58,9 @@ "no_name_message": "Jeder Spieler muss einen Namen haben.", "select_game_mode": "Spielmodus auswählen", - "no_mode_selected": "Kein Modus", - "no_default_mode": "Kein Standard-Modus", - "no_default_description": "Der Standard-Modus wird zurückgesetzt.", + "no_mode_selected": "Wähle einen Spielmodus", + "no_default_mode": "Kein Modus", + "no_default_description": "Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.", "point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht", "@point_limit_description": { "placeholders": { diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 40d8c65..d821b28 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -377,19 +377,19 @@ abstract class AppLocalizations { /// No description provided for @no_mode_selected. /// /// In de, this message translates to: - /// **'Kein Modus'** + /// **'Wähle einen Spielmodus'** String get no_mode_selected; /// No description provided for @no_default_mode. /// /// In de, this message translates to: - /// **'Kein Standard-Modus'** + /// **'Kein Modus'** String get no_default_mode; /// No description provided for @no_default_description. /// /// In de, this message translates to: - /// **'Der Standard-Modus wird zurückgesetzt.'** + /// **'Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.'** String get no_default_description; /// No description provided for @point_limit_description. diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index b73ccba..13dcf2a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -155,13 +155,14 @@ class AppLocalizationsDe extends AppLocalizations { String get select_game_mode => 'Spielmodus auswählen'; @override - String get no_mode_selected => 'Kein Modus'; + String get no_mode_selected => 'Wähle einen Spielmodus'; @override - String get no_default_mode => 'Kein Standard-Modus'; + String get no_default_mode => 'Kein Modus'; @override - String get no_default_description => 'Der Standard-Modus wird zurückgesetzt.'; + String get no_default_description => + 'Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.'; @override String point_limit_description(int pointLimit) { diff --git a/lib/presentation/views/active_game_view.dart b/lib/presentation/views/active_game_view.dart index defe5fc..dece0a6 100644 --- a/lib/presentation/views/active_game_view.dart +++ b/lib/presentation/views/active_game_view.dart @@ -4,6 +4,7 @@ import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart'; +import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/points_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/services/local_storage_service.dart'; @@ -205,9 +206,11 @@ class _ActiveGameViewState extends State { CupertinoPageRoute( builder: (_) => CreateGameView( gameTitle: gameSession.gameTitle, - isPointsLimitEnabled: widget - .gameSession - .isPointsLimitEnabled, + gameMode: widget.gameSession + .isPointsLimitEnabled == + true + ? GameMode.pointLimit + : GameMode.unlimited, players: gameSession.players, ))); }, diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index 2634788..bf6d1fb 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -16,15 +16,15 @@ enum CreateStatus { } class CreateGameView extends StatefulWidget { + final GameMode gameMode; final String? gameTitle; - final bool? isPointsLimitEnabled; final List? players; const CreateGameView({ super.key, this.gameTitle, - this.isPointsLimitEnabled, this.players, + required this.gameMode, }); @override @@ -42,19 +42,15 @@ class _CreateGameViewState extends State { /// Maximum number of players allowed in the game. final int maxPlayers = 5; - /// Variable to store whether the points limit feature is enabled. - bool? _isPointsLimitEnabled; + /// Variable to hold the selected game mode. + late GameMode gameMode; @override void initState() { super.initState(); - if (widget.isPointsLimitEnabled == null) { - _isPointsLimitEnabled = - ConfigService.gameMode == -1 ? null : ConfigService.gameMode == 1; - } else { - _isPointsLimitEnabled = widget.isPointsLimitEnabled; - } + gameMode = widget.gameMode; + print('Game mode: $gameMode'); _gameTitleTextController.text = widget.gameTitle ?? ''; @@ -106,10 +102,10 @@ class _CreateGameViewState extends State { suffix: Row( children: [ Text( - _isPointsLimitEnabled == null - ? AppLocalizations.of(context).select_mode - : (_isPointsLimitEnabled! - ? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}' + gameMode == GameMode.none + ? AppLocalizations.of(context).no_mode_selected + : (gameMode == GameMode.pointLimit + ? '${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}' : AppLocalizations.of(context).unlimited), ), const SizedBox(width: 3), @@ -121,29 +117,15 @@ class _CreateGameViewState extends State { context, CupertinoPageRoute( builder: (context) => ModeSelectionMenu( - pointLimit: ConfigService.pointLimit, + pointLimit: ConfigService.getPointLimit(), showDeselection: false, ), ), ); - switch (selectedMode) { - case GameMode.pointLimit: - setState(() { - _isPointsLimitEnabled = true; - }); - break; - case GameMode.unlimited: - setState(() { - _isPointsLimitEnabled = false; - }); - break; - case GameMode.none: - default: - setState(() { - _isPointsLimitEnabled = null; - }); - } + setState(() { + gameMode = selectedMode ?? gameMode; + }); }, ), ), @@ -249,7 +231,7 @@ class _CreateGameViewState extends State { showFeedbackDialog(CreateStatus.noGameTitle); return; } - if (_isPointsLimitEnabled == null) { + if (gameMode == GameMode.none) { showFeedbackDialog(CreateStatus.noModeSelected); return; } @@ -266,13 +248,16 @@ class _CreateGameViewState extends State { for (var controller in _playerNameTextControllers) { players.add(controller.text); } + + bool isPointsLimitEnabled = gameMode == GameMode.pointLimit; + GameSession gameSession = GameSession( createdAt: DateTime.now(), gameTitle: _gameTitleTextController.text, players: players, - pointLimit: ConfigService.pointLimit, - caboPenalty: ConfigService.caboPenalty, - isPointsLimitEnabled: _isPointsLimitEnabled!, + pointLimit: ConfigService.getPointLimit(), + caboPenalty: ConfigService.getCaboPenalty(), + isPointsLimitEnabled: isPointsLimitEnabled, ); final index = await gameManager.addGameSession(gameSession); final session = gameManager.gameList[index]; diff --git a/lib/presentation/views/main_menu_view.dart b/lib/presentation/views/main_menu_view.dart index a80664d..f8817a0 100644 --- a/lib/presentation/views/main_menu_view.dart +++ b/lib/presentation/views/main_menu_view.dart @@ -77,7 +77,8 @@ class _MainMenuViewState extends State { onPressed: () => Navigator.push( context, CupertinoPageRoute( - builder: (context) => const CreateGameView(), + builder: (context) => CreateGameView( + gameMode: ConfigService.getGameMode()), ), ), icon: const Icon(CupertinoIcons.add)), @@ -96,8 +97,8 @@ class _MainMenuViewState extends State { onTap: () => Navigator.push( context, CupertinoPageRoute( - builder: (context) => - const CreateGameView(), + builder: (context) => CreateGameView( + gameMode: ConfigService.getGameMode()), ), ), child: Icon( @@ -216,9 +217,9 @@ class _MainMenuViewState extends State { /// Translates the game mode boolean into the corresponding String. /// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'. - String _translateGameMode(bool pointLimit) { - if (pointLimit) { - return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}'; + String _translateGameMode(bool isPointLimitEnabled) { + if (isPointLimitEnabled) { + return '${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}'; } return AppLocalizations.of(context).unlimited; } diff --git a/lib/presentation/views/settings_view.dart b/lib/presentation/views/settings_view.dart index 596866d..aa49872 100644 --- a/lib/presentation/views/settings_view.dart +++ b/lib/presentation/views/settings_view.dart @@ -21,7 +21,7 @@ class SettingsView extends StatefulWidget { class _SettingsViewState extends State { UniqueKey _stepperKey1 = UniqueKey(); UniqueKey _stepperKey2 = UniqueKey(); - int defaultMode = ConfigService.gameMode; + GameMode defaultMode = ConfigService.getGameMode(); @override void initState() { super.initState(); @@ -57,7 +57,7 @@ class _SettingsViewState extends State { prefixIcon: CupertinoIcons.bolt_fill, suffixWidget: CustomStepper( key: _stepperKey1, - initialValue: ConfigService.caboPenalty, + initialValue: ConfigService.getCaboPenalty(), minValue: 0, maxValue: 50, step: 1, @@ -73,7 +73,7 @@ class _SettingsViewState extends State { prefixIcon: FontAwesomeIcons.bullseye, suffixWidget: CustomStepper( key: _stepperKey2, - initialValue: ConfigService.pointLimit, + initialValue: ConfigService.getPointLimit(), minValue: 30, maxValue: 1000, step: 10, @@ -91,11 +91,10 @@ class _SettingsViewState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ Text( - defaultMode == -1 - ? AppLocalizations.of(context) - .no_mode_selected - : (defaultMode == 1 - ? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}' + defaultMode == GameMode.none + ? AppLocalizations.of(context).no_default_mode + : (defaultMode == GameMode.pointLimit + ? '${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}' : AppLocalizations.of(context).unlimited), ), const SizedBox(width: 5), @@ -107,29 +106,15 @@ class _SettingsViewState extends State { context, CupertinoPageRoute( builder: (context) => ModeSelectionMenu( - pointLimit: ConfigService.pointLimit, + pointLimit: ConfigService.getPointLimit(), showDeselection: true, ), ), ); - switch (selectedMode) { - case GameMode.pointLimit: - setState(() { - defaultMode = 1; - }); - break; - case GameMode.unlimited: - setState(() { - defaultMode = 0; - }); - break; - case GameMode.none: - default: - setState(() { - defaultMode = -1; - }); - } + setState(() { + defaultMode = selectedMode ?? GameMode.none; + }); ConfigService.setGameMode(defaultMode); }, ), @@ -142,7 +127,7 @@ class _SettingsViewState extends State { setState(() { _stepperKey1 = UniqueKey(); _stepperKey2 = UniqueKey(); - defaultMode = ConfigService.gameMode; + defaultMode = ConfigService.getGameMode(); }); }, ) diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index a360fe4..d4ac105 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -1,3 +1,4 @@ +import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// This class handles the configuration settings for the app. @@ -8,9 +9,9 @@ class ConfigService { static const String _keyCaboPenalty = 'caboPenalty'; static const String _keyGameMode = 'gameMode'; // Actual values used in the app - static int pointLimit = 100; - static int caboPenalty = 5; - static int gameMode = -1; + static int _pointLimit = 100; + static int _caboPenalty = 5; + static int _gameMode = -1; // Default values static const int _defaultPointLimit = 100; static const int _defaultCaboPenalty = 5; @@ -21,43 +22,70 @@ class ConfigService { // Initialize pointLimit, caboPenalty, and gameMode from SharedPreferences // If they are not set, use the default values - pointLimit = prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; - caboPenalty = prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; - gameMode = prefs.getInt(_keyGameMode) ?? _defaultGameMode; + _pointLimit = prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; + _caboPenalty = prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; + _gameMode = prefs.getInt(_keyGameMode) ?? _defaultGameMode; // Save the initial values to SharedPreferences - prefs.setInt(_keyPointLimit, pointLimit); - prefs.setInt(_keyCaboPenalty, caboPenalty); - prefs.setInt(_keyGameMode, gameMode); + prefs.setInt(_keyPointLimit, _pointLimit); + prefs.setInt(_keyCaboPenalty, _caboPenalty); + prefs.setInt(_keyGameMode, _gameMode); } - static Future setGameMode(int newGameMode) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setInt(_keyGameMode, newGameMode); - gameMode = newGameMode; + static GameMode getGameMode() { + switch (_gameMode) { + case 0: + return GameMode.pointLimit; + case 1: + return GameMode.unlimited; + default: + return GameMode.none; + } } + static Future setGameMode(GameMode newGameMode) async { + int gameMode; + switch (newGameMode) { + case GameMode.pointLimit: + gameMode = 0; + break; + case GameMode.unlimited: + gameMode = 1; + break; + default: + gameMode = -1; + } + + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_keyGameMode, gameMode); + _gameMode = gameMode; + } + + static int getPointLimit() => _pointLimit; + /// Setter for the point limit. /// [newPointLimit] is the new point limit to be set. static Future setPointLimit(int newPointLimit) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, newPointLimit); - pointLimit = newPointLimit; + _pointLimit = newPointLimit; } + static int getCaboPenalty() => _caboPenalty; + /// Setter for the cabo penalty. /// [newCaboPenalty] is the new cabo penalty to be set. static Future setCaboPenalty(int newCaboPenalty) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyCaboPenalty, newCaboPenalty); - caboPenalty = newCaboPenalty; + _caboPenalty = newCaboPenalty; } /// Resets the configuration to default values. static Future resetConfig() async { - ConfigService.pointLimit = _defaultPointLimit; - ConfigService.caboPenalty = _defaultCaboPenalty; - ConfigService.gameMode = _defaultGameMode; + ConfigService._pointLimit = _defaultPointLimit; + ConfigService._caboPenalty = _defaultCaboPenalty; + ConfigService._gameMode = _defaultGameMode; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, _defaultPointLimit); await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty); diff --git a/pubspec.yaml b/pubspec.yaml index 3861d64..e445bd6 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.5.0+542 +version: 0.5.0+544 environment: sdk: ^3.5.4 From 5d362a77cffb96fa2e864c0aee69a8c33c234be6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 20 Jul 2025 16:41:36 +0200 Subject: [PATCH 182/182] Updated documentation --- lib/presentation/views/create_game_view.dart | 1 - lib/services/config_service.dart | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/create_game_view.dart b/lib/presentation/views/create_game_view.dart index bf6d1fb..e0b4198 100644 --- a/lib/presentation/views/create_game_view.dart +++ b/lib/presentation/views/create_game_view.dart @@ -50,7 +50,6 @@ class _CreateGameViewState extends State { super.initState(); gameMode = widget.gameMode; - print('Game mode: $gameMode'); _gameTitleTextController.text = widget.gameTitle ?? ''; diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index d4ac105..1b20ad5 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -32,6 +32,14 @@ class ConfigService { prefs.setInt(_keyGameMode, _gameMode); } + /// Retrieves the current game mode. + /// + /// The game mode is determined based on the stored integer value: + /// - `0`: [GameMode.pointLimit] + /// - `1`: [GameMode.unlimited] + /// - Any other value: [GameMode.none] (-1 is used as a default for no mode) + /// + /// Returns the corresponding [GameMode] enum value. static GameMode getGameMode() { switch (_gameMode) { case 0: @@ -43,6 +51,14 @@ class ConfigService { } } + /// Sets the game mode for the application. + /// + /// [newGameMode] is the new game mode to be set. It can be one of the following: + /// - `GameMode.pointLimit`: The game ends when a pleayer reaches the point limit. + /// - `GameMode.unlimited`: Every game goes for infinity until you end it. + /// - `GameMode.none`: No default mode set. + /// + /// This method updates the `_gameMode` field and persists the value in `SharedPreferences`. static Future setGameMode(GameMode newGameMode) async { int gameMode; switch (newGameMode) {