From d71585b58598b17122aa5dfffe76d0fcf76160a9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Jun 2025 00:48:30 +0200 Subject: [PATCH 1/4] Implemented 2 possibilites of deleting game --- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/services/local_storage_service.dart | 13 + lib/views/create_game_view.dart | 4 +- lib/views/main_menu_view.dart | 328 ++++++++++++------ 5 files changed, 246 insertions(+), 103 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 138b281..804f441 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -215,7 +215,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 51c28c5..fa4cdb6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ deleteAllGames() async { + try { + Globals.gameList.clear(); + await saveGameSessions(); + logger.i('Alle Runden wurden erfolgreich gelöscht.'); + return true; + } catch (e) { + logger.e('Fehler beim Löschen aller Runden: $e', + error: 'Löschen fehlgeschlagen'); + return false; + } + } } diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 9150a22..7bbeefa 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -289,7 +289,9 @@ class _CreateGameState extends State { caboPenalty: Globals.caboPenalty, isPointsLimitEnabled: selectedMode!, ); - Globals.addGameSession(gameSession); + setState(() { + Globals.addGameSession(gameSession); + }); LocalStorageService.saveGameSessions(); if (context.mounted) { Navigator.pushReplacement( diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index e5a1039..58cb0f0 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -17,6 +17,7 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; + bool _isDeletionModeEnabled = false; @override initState() { @@ -34,112 +35,239 @@ class _MainMenuViewState extends State { LocalStorageService.loadGameSessions(); return CupertinoPageScaffold( - resizeToAvoidBottomInset: false, - navigationBar: CupertinoNavigationBar( - leading: IconButton( - onPressed: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const SettingsView(), - ), - ); - }, - icon: const Icon( - CupertinoIcons.settings, - size: 30, - )), - middle: const Text('Cabo Counter'), - trailing: IconButton( - onPressed: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const CreateGame(), - ), - ); - }, - icon: const Icon(CupertinoIcons.add)), - ), - child: CupertinoPageScaffold( - child: SafeArea( - child: _isLoading - ? const Center(child: CupertinoActivityIndicator()) - : Globals.gameList.isEmpty - ? Column( - mainAxisAlignment: - MainAxisAlignment.center, // Oben ausrichten - children: [ - const SizedBox(height: 30), // Abstand von oben - Center( - child: GestureDetector( - onTap: () => setState(() {}), - child: Icon( - CupertinoIcons.plus, - size: 60, - color: CustomTheme.primaryColor, - ), - )), - const SizedBox(height: 10), // Abstand von oben - const Padding( - padding: EdgeInsets.symmetric(horizontal: 70), - child: Text( - 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), - ), + resizeToAvoidBottomInset: false, + navigationBar: CupertinoNavigationBar( + leading: IconButton( + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const SettingsView(), + ), + ); + }, + icon: _isDeletionModeEnabled + ? const Icon(null) + : const Icon(CupertinoIcons.settings, size: 30)), + middle: const Text('Cabo Counter'), + trailing: IconButton( + onPressed: () => _isDeletionModeEnabled + ? _showDeleteAllGamesPopup() + : { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const CreateGame(), ), - ], - ) - : ListView.builder( - itemCount: Globals.gameList.length, - itemBuilder: (context, index) { - final session = Globals.gameList[index]; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: CupertinoListTile( - title: Text(session.gameTitle), - subtitle: session.isGameFinished == true - ? Text( - '\u{1F947} ${session.winner}', - style: const TextStyle(fontSize: 14), - ) - : Text( - 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', - style: const TextStyle(fontSize: 14), - ), - trailing: Row( - children: [ - Text('${session.roundNumber}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons - .arrow_2_circlepath_circle_fill), - const SizedBox(width: 15), - Text('${session.players.length}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons.person_2_fill), - ], - ), - onTap: () async { - //ignore: unused_local_variable - final val = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => ActiveGameView( - gameSession: Globals.gameList[index]), - ), - ); - setState(() {}); - }, - )); - }), + ) + }, + icon: _isDeletionModeEnabled + ? const Icon(CupertinoIcons.trash, size: 25) + : const Icon(CupertinoIcons.add)), ), - ), - ); + child: CupertinoPageScaffold( + child: SafeArea( + child: _isLoading + ? const Center(child: CupertinoActivityIndicator()) + : Globals.gameList.isEmpty + ? Column( + mainAxisAlignment: + MainAxisAlignment.center, // Oben ausrichten + children: [ + const SizedBox(height: 30), // Abstand von oben + Center( + child: GestureDetector( + onTap: () => setState(() {}), + child: Icon( + CupertinoIcons.plus, + size: 60, + color: CustomTheme.primaryColor, + ), + )), + const SizedBox(height: 10), // Abstand von oben + const Padding( + padding: EdgeInsets.symmetric(horizontal: 70), + child: Text( + 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + ], + ) + : GestureDetector( + onTap: () => { + if (_isDeletionModeEnabled) + { + setState(() { + _isDeletionModeEnabled = false; + }), + print('Deletion mode: $_isDeletionModeEnabled') + } + }, + onLongPress: () => { + setState(() { + _isDeletionModeEnabled = true; + }), + print('Deletion mode: $_isDeletionModeEnabled') + }, + child: ListView.builder( + itemCount: Globals.gameList.length, + itemBuilder: (context, index) { + final session = Globals.gameList[index]; + return Dismissible( + key: Key(session.gameTitle), + background: Container( + color: CupertinoColors.destructiveRed, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20.0), + child: const Icon( + CupertinoIcons.delete, + color: CupertinoColors.white, + ), + ), + direction: DismissDirection.startToEnd, + confirmDismiss: (direction) async { + if (_isDeletionModeEnabled) return false; + return await _showDeleteSingleGamePopup(index); + }, + onDismissed: (direction) { + _deleteSpecificGame(index); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 10.0), + child: CupertinoListTile( + title: Text(session.gameTitle), + subtitle: session.isGameFinished == true + ? Text( + '\u{1F947} ${session.winner}', + style: const TextStyle(fontSize: 14), + ) + : Text( + 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', + style: const TextStyle(fontSize: 14), + ), + trailing: Row( + children: [ + Text('${session.roundNumber}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons + .arrow_2_circlepath_circle_fill), + const SizedBox(width: 15), + Text('${session.players.length}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons.person_2_fill), + ], + ), + onTap: () => _isDeletionModeEnabled + ? _showDeleteSingleGamePopup(index) + : () async { + //ignore: unused_local_variable + final val = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => + ActiveGameView( + gameSession: Globals + .gameList[index]), + ), + ); + setState(() {}); + }, + ), + ), + ); + }, + ), + ), + ), + )); } + /// 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 '101 Punkte'; return 'Unbegrenzt'; } + + /// Shows a confirmation dialog to delete all game sessions. + void _showDeleteAllGamesPopup() { + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: const Text('Alle Spiele löschen?'), + content: const Text( + 'Bist du sicher, dass du alle Spiele löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'), + actions: [ + CupertinoDialogAction( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Abbrechen'), + ), + CupertinoDialogAction( + onPressed: () { + setState(() { + _deleteAllGames(); + _isDeletionModeEnabled = false; + }); + Navigator.pop(context); + }, + child: const Text('Löschen'), + ), + ], + ); + }, + ); + } + + /// Shows a confirmation dialog to delete all game sessions. + Future _showDeleteSingleGamePopup(int gameIndex) async { + final String title = Globals.gameList[gameIndex].gameTitle; + bool? shouldDelete = await showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: const Text('Spiel löschen?'), + content: Text( + 'Bist du sicher, dass du die Runde "$title" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'), + actions: [ + CupertinoDialogAction( + onPressed: () { + Navigator.pop(context, false); + }, + child: const Text('Abbrechen'), + ), + CupertinoDialogAction( + onPressed: () { + Navigator.pop(context, true); + }, + child: const Text('Löschen'), + ), + ], + ); + }, + ); + return shouldDelete ?? false; + } // + + /// Deletes all game sessions. + /// This functions clears the global games lists and triggers a save to the + /// local storage. This overwrites the existing game data so that both the + /// local json file and the global variable are empty. + void _deleteAllGames() { + Globals.gameList.clear(); + LocalStorageService.saveGameSessions(); + } + + /// Deletes a specific game session by its index. + /// This function takes an [index] as parameter and removes the game session at + /// that index from the global game list, + void _deleteSpecificGame(int index) { + Globals.gameList.removeAt(index); + LocalStorageService.saveGameSessions(); + } } From 16e8d6ad150a3859c9381588f0deabccbfccf240 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Jun 2025 01:12:12 +0200 Subject: [PATCH 2/4] Removed old deletion way --- lib/views/main_menu_view.dart | 361 ++++++++++++++-------------------- 1 file changed, 149 insertions(+), 212 deletions(-) diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 58cb0f0..fabd02d 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -17,7 +17,6 @@ class MainMenuView extends StatefulWidget { class _MainMenuViewState extends State { bool _isLoading = true; - bool _isDeletionModeEnabled = false; @override initState() { @@ -35,154 +34,131 @@ class _MainMenuViewState extends State { LocalStorageService.loadGameSessions(); return CupertinoPageScaffold( - resizeToAvoidBottomInset: false, - navigationBar: CupertinoNavigationBar( - leading: IconButton( - onPressed: () { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const SettingsView(), - ), - ); - }, - icon: _isDeletionModeEnabled - ? const Icon(null) - : const Icon(CupertinoIcons.settings, size: 30)), - middle: const Text('Cabo Counter'), - trailing: IconButton( - onPressed: () => _isDeletionModeEnabled - ? _showDeleteAllGamesPopup() - : { - Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => const CreateGame(), + resizeToAvoidBottomInset: false, + navigationBar: CupertinoNavigationBar( + leading: IconButton( + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const SettingsView(), + ), + ); + }, + icon: const Icon(CupertinoIcons.settings, size: 30)), + middle: const Text('Cabo Counter'), + trailing: IconButton( + onPressed: () => { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => const CreateGame(), + ), + ) + }, + icon: const Icon(CupertinoIcons.add)), + ), + child: CupertinoPageScaffold( + child: SafeArea( + child: _isLoading + ? const Center(child: CupertinoActivityIndicator()) + : Globals.gameList.isEmpty + ? Column( + mainAxisAlignment: + MainAxisAlignment.center, // Oben ausrichten + children: [ + const SizedBox(height: 30), // Abstand von oben + Center( + child: GestureDetector( + onTap: () => setState(() {}), + child: Icon( + CupertinoIcons.plus, + size: 60, + color: CustomTheme.primaryColor, + ), + )), + const SizedBox(height: 10), // Abstand von oben + const Padding( + padding: EdgeInsets.symmetric(horizontal: 70), + child: Text( + 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), ), - ) - }, - icon: _isDeletionModeEnabled - ? const Icon(CupertinoIcons.trash, size: 25) - : const Icon(CupertinoIcons.add)), - ), - child: CupertinoPageScaffold( - child: SafeArea( - child: _isLoading - ? const Center(child: CupertinoActivityIndicator()) - : Globals.gameList.isEmpty - ? Column( - mainAxisAlignment: - MainAxisAlignment.center, // Oben ausrichten - children: [ - const SizedBox(height: 30), // Abstand von oben - Center( - child: GestureDetector( - onTap: () => setState(() {}), - child: Icon( - CupertinoIcons.plus, - size: 60, - color: CustomTheme.primaryColor, - ), - )), - const SizedBox(height: 10), // Abstand von oben - const Padding( - padding: EdgeInsets.symmetric(horizontal: 70), - child: Text( - 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), + ], + ) + : ListView.builder( + itemCount: Globals.gameList.length, + itemBuilder: (context, index) { + final session = Globals.gameList[index]; + return Dismissible( + key: Key(session.gameTitle), + background: Container( + color: CupertinoColors.destructiveRed, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20.0), + child: const Icon( + CupertinoIcons.delete, + color: CupertinoColors.white, ), ), - ], - ) - : GestureDetector( - onTap: () => { - if (_isDeletionModeEnabled) - { - setState(() { - _isDeletionModeEnabled = false; - }), - print('Deletion mode: $_isDeletionModeEnabled') - } - }, - onLongPress: () => { - setState(() { - _isDeletionModeEnabled = true; - }), - print('Deletion mode: $_isDeletionModeEnabled') - }, - child: ListView.builder( - itemCount: Globals.gameList.length, - itemBuilder: (context, index) { - final session = Globals.gameList[index]; - return Dismissible( - key: Key(session.gameTitle), - background: Container( - color: CupertinoColors.destructiveRed, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20.0), - child: const Icon( - CupertinoIcons.delete, - color: CupertinoColors.white, - ), - ), - direction: DismissDirection.startToEnd, - confirmDismiss: (direction) async { - if (_isDeletionModeEnabled) return false; - return await _showDeleteSingleGamePopup(index); - }, - onDismissed: (direction) { - _deleteSpecificGame(index); - }, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 10.0), - child: CupertinoListTile( - title: Text(session.gameTitle), - subtitle: session.isGameFinished == true - ? Text( - '\u{1F947} ${session.winner}', - style: const TextStyle(fontSize: 14), - ) - : Text( - 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', - style: const TextStyle(fontSize: 14), - ), - trailing: Row( - children: [ - Text('${session.roundNumber}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons - .arrow_2_circlepath_circle_fill), - const SizedBox(width: 15), - Text('${session.players.length}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons.person_2_fill), - ], - ), - onTap: () => _isDeletionModeEnabled - ? _showDeleteSingleGamePopup(index) - : () async { - //ignore: unused_local_variable - final val = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => - ActiveGameView( - gameSession: Globals - .gameList[index]), - ), - ); - setState(() {}); - }, - ), - ), - ); + direction: DismissDirection.startToEnd, + confirmDismiss: (direction) async { + final String gameTitle = + Globals.gameList[index].gameTitle; + return await _showDeleteGamePopup(gameTitle); }, - ), - ), - ), - )); + onDismissed: (direction) { + _deleteSpecificGame(index); + }, + dismissThresholds: const { + DismissDirection.startToEnd: 0.6 + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: CupertinoListTile( + title: Text(session.gameTitle), + subtitle: session.isGameFinished == true + ? Text( + '\u{1F947} ${session.winner}', + style: const TextStyle(fontSize: 14), + ) + : Text( + 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', + style: const TextStyle(fontSize: 14), + ), + trailing: Row( + children: [ + Text('${session.roundNumber}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons + .arrow_2_circlepath_circle_fill), + const SizedBox(width: 15), + Text('${session.players.length}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons.person_2_fill), + ], + ), + onTap: () async { + //ignore: unused_local_variable + final val = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ActiveGameView( + gameSession: Globals.gameList[index]), + ), + ); + setState(() {}); + }, + ), + ), + ); + }, + ), + ), + ), + ); } /// Translates the game mode boolean into the corresponding String. @@ -193,74 +169,35 @@ class _MainMenuViewState extends State { } /// Shows a confirmation dialog to delete all game sessions. - void _showDeleteAllGamesPopup() { - showCupertinoDialog( - context: context, - builder: (context) { - return CupertinoAlertDialog( - title: const Text('Alle Spiele löschen?'), - content: const Text( - 'Bist du sicher, dass du alle Spiele löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'), - actions: [ - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Abbrechen'), - ), - CupertinoDialogAction( - onPressed: () { - setState(() { - _deleteAllGames(); - _isDeletionModeEnabled = false; - }); - Navigator.pop(context); - }, - child: const Text('Löschen'), - ), - ], - ); - }, - ); - } - - /// Shows a confirmation dialog to delete all game sessions. - Future _showDeleteSingleGamePopup(int gameIndex) async { - final String title = Globals.gameList[gameIndex].gameTitle; + /// Returns true if the user confirms the deletion, false otherwise. + /// + Future _showDeleteGamePopup(String gameTitle) async { bool? shouldDelete = await showCupertinoDialog( - context: context, - builder: (context) { - return CupertinoAlertDialog( - title: const Text('Spiel löschen?'), - content: Text( - 'Bist du sicher, dass du die Runde "$title" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'), - actions: [ - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, false); - }, - child: const Text('Abbrechen'), - ), - CupertinoDialogAction( - onPressed: () { - Navigator.pop(context, true); - }, - child: const Text('Löschen'), - ), - ], - ); - }, - ); - return shouldDelete ?? false; - } // - - /// Deletes all game sessions. - /// This functions clears the global games lists and triggers a save to the - /// local storage. This overwrites the existing game data so that both the - /// local json file and the global variable are empty. - void _deleteAllGames() { - Globals.gameList.clear(); - LocalStorageService.saveGameSessions(); + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: const Text('Spiel löschen?'), + content: Text( + 'Bist du sicher, dass du die Runde "$gameTitle" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'), + actions: [ + CupertinoDialogAction( + onPressed: () { + Navigator.pop(context, false); + }, + child: const Text('Abbrechen'), + ), + CupertinoDialogAction( + onPressed: () { + Navigator.pop(context, true); + }, + child: const Text('Löschen'), + ), + ], + ); + }, + ) ?? + false; + return shouldDelete; } /// Deletes a specific game session by its index. From 0256686efe56c802648a8aeb5d85db16f3af6b56 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Jun 2025 01:22:51 +0200 Subject: [PATCH 3/4] Editet version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1a829fa..9661bec 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.1.6-alpha+145 +version: 0.1.6+145 environment: sdk: ^3.5.4 From eddb009d70388432a14ce0d6459bc24bff920c0e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Jun 2025 01:29:05 +0200 Subject: [PATCH 4/4] Added script for auto incrementing build number --- .gitignore | 2 -- increment_build_number.sh | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 increment_build_number.sh diff --git a/.gitignore b/.gitignore index 50aec09..09b5d34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -increment_build_number.sh - # Do not remove or rename entries in this file, only add new ones # See https://github.com/flutter/flutter/issues/128635 for more context. diff --git a/increment_build_number.sh b/increment_build_number.sh new file mode 100644 index 0000000..7d209bc --- /dev/null +++ b/increment_build_number.sh @@ -0,0 +1,2 @@ +build_number=$(awk -F'+' '/version:/ {print $2}' pubspec.yaml); build_number=${build_number:-0}; +sed -i '' -E "s/(version: [0-9]+\.[0-9]+\.[0-9]\+)[0-9]*/\1$((build_number + 1))/" pubspec.yaml \ No newline at end of file