From 845528e36249502cb4b8e29fb99cdfbd72d4a957 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Jun 2025 19:11:57 +0200 Subject: [PATCH] Fixed state update bug --- lib/main.dart | 4 +- lib/services/config_service.dart | 4 +- lib/services/local_storage_service.dart | 12 +- lib/utility/globals.dart | 26 ++- lib/views/create_game_view.dart | 10 +- lib/views/main_menu_view.dart | 266 +++++++++++++----------- lib/views/settings_view.dart | 10 +- pubspec.yaml | 2 +- 8 files changed, 178 insertions(+), 156 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index edd7d3c..26ff4eb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,8 +8,8 @@ import 'package:flutter/cupertino.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await ConfigService.initConfig(); - Globals.pointLimit = await ConfigService.getPointLimit(); - Globals.caboPenalty = await ConfigService.getCaboPenalty(); + globals.pointLimit = await ConfigService.getPointLimit(); + globals.caboPenalty = await ConfigService.getCaboPenalty(); runApp(const App()); } diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 1c8275a..a4336e0 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -48,8 +48,8 @@ class ConfigService { /// Resets the configuration to default values. static Future resetConfig() async { - Globals.pointLimit = _defaultPointLimit; - Globals.caboPenalty = _defaultCaboPenalty; + globals.pointLimit = _defaultPointLimit; + globals.caboPenalty = _defaultCaboPenalty; final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_keyPointLimit, _defaultPointLimit); await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty); diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index a68f3e4..5ca438e 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -19,7 +19,7 @@ class LocalStorageService { /// Writes the game session list to a JSON file and returns it as string. static String getJsonFile() { final jsonFile = - Globals.gameList.map((session) => session.toJson()).toList(); + globals.gameList.map((session) => session.toJson()).toList(); return json.encode(jsonFile); } @@ -63,14 +63,14 @@ class LocalStorageService { if (!await validateJsonSchema(jsonString)) { logger.w('Die Datei konnte nicht validiert werden'); - Globals.gameList = []; + globals.gameList = []; return false; } logger.d('Die gefundene Datei hat Inhalt'); logger.d('Die gefundene Datei wurde erfolgreich validiert'); final jsonList = json.decode(jsonString) as List; - Globals.gameList = jsonList + globals.gameList = jsonList .map((jsonItem) => GameSession.fromJson(jsonItem as Map)) .toList(); @@ -80,7 +80,7 @@ class LocalStorageService { } catch (e) { logger.e('Fehler beim Laden der Spieldaten:\n$e', error: 'JSON nicht geladen'); - Globals.gameList = []; + globals.gameList = []; return false; } } @@ -125,7 +125,7 @@ class LocalStorageService { return false; } final jsonData = json.decode(jsonString) as List; - Globals.gameList = jsonData + globals.gameList = jsonData .map((jsonItem) => GameSession.fromJson(jsonItem as Map)) .toList(); @@ -172,7 +172,7 @@ class LocalStorageService { static Future deleteAllGames() async { try { - Globals.gameList.clear(); + globals.gameList.clear(); await saveGameSessions(); logger.i('Alle Runden wurden erfolgreich gelöscht.'); return true; diff --git a/lib/utility/globals.dart b/lib/utility/globals.dart index 89d8559..7b4d7e0 100644 --- a/lib/utility/globals.dart +++ b/lib/utility/globals.dart @@ -1,17 +1,25 @@ import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; +import 'package:flutter/foundation.dart'; -class Globals { - /// The [gameList] contains all active game sessions. - static List gameList = []; +class Globals extends ChangeNotifier { + List gameList = []; + int pointLimit = 100; + int caboPenalty = 5; + String appDevPhase = 'Alpha'; - static void addGameSession(GameSession session) { + void addGameSession(GameSession session) { gameList.add(session); gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + notifyListeners(); // Wichtig! + LocalStorageService.saveGameSessions(); } - static int pointLimit = 100; - - static int caboPenalty = 5; - - static String appDevPhase = 'Alpha'; + void removeGameSession(int index) { + gameList.removeAt(index); + notifyListeners(); // Wichtig! + LocalStorageService.saveGameSessions(); + } } + +final globals = Globals(); // Globale Instanz diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 7bbeefa..aa0d40f 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -81,7 +81,7 @@ class _CreateGameState extends State { context, CupertinoPageRoute( builder: (context) => ModeSelectionMenu( - pointLimit: Globals.pointLimit, + pointLimit: globals.pointLimit, ), ), ); @@ -285,13 +285,11 @@ class _CreateGameState extends State { createdAt: DateTime.now(), gameTitle: _gameTitleTextController.text, players: players, - pointLimit: Globals.pointLimit, - caboPenalty: Globals.caboPenalty, + pointLimit: globals.pointLimit, + caboPenalty: globals.caboPenalty, isPointsLimitEnabled: selectedMode!, ); - setState(() { - Globals.addGameSession(gameSession); - }); + 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 fabd02d..9572c82 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -26,6 +26,11 @@ class _MainMenuViewState extends State { _isLoading = false; }); }); + globals.addListener(_updateView); + } + + void _updateView() { + if (mounted) setState(() {}); } @override @@ -33,132 +38,143 @@ class _MainMenuViewState extends State { print('MainMenuView build'); 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, + return ListenableBuilder( + listenable: globals, + builder: (context, _) { + 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(), ), - )), - 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, - ), - ), - 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(() {}); - }, - ), - ), - ); + ) }, - ), - ), - ), - ); + 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), + ), + ), + ], + ) + : 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 { + 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( + backgroundColorActivated: + CustomTheme.backgroundColor, + 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. @@ -204,7 +220,7 @@ class _MainMenuViewState extends State { /// 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); + globals.gameList.removeAt(index); LocalStorageService.saveGameSessions(); } } diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 8f0dcf4..36c7099 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -50,14 +50,14 @@ class _SettingsViewState extends State { subtitle: const Text('... für falsches Cabo sagen'), trailing: Stepper( key: _stepperKey1, - initialValue: Globals.caboPenalty, + initialValue: globals.caboPenalty, minValue: 0, maxValue: 50, step: 1, onChanged: (newCaboPenalty) { setState(() { ConfigService.setCaboPenalty(newCaboPenalty); - Globals.caboPenalty = newCaboPenalty; + globals.caboPenalty = newCaboPenalty; }); }, ), @@ -70,14 +70,14 @@ class _SettingsViewState extends State { subtitle: const Text('... hier ist Schluss'), trailing: Stepper( key: _stepperKey2, - initialValue: Globals.pointLimit, + initialValue: globals.pointLimit, minValue: 30, maxValue: 1000, step: 10, onChanged: (newPointLimit) { setState(() { ConfigService.setPointLimit(newPointLimit); - Globals.pointLimit = newPointLimit; + globals.pointLimit = newPointLimit; }); }, ), @@ -201,7 +201,7 @@ class _SettingsViewState extends State { builder: (context, snapshot) { if (snapshot.hasData) { return Text( - '${Globals.appDevPhase} ${snapshot.data!.version} ' + '${globals.appDevPhase} ${snapshot.data!.version} ' '(Build ${snapshot.data!.buildNumber})', textAlign: TextAlign.center, ); diff --git a/pubspec.yaml b/pubspec.yaml index 85cf31a..990882b 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+146 +version: 0.1.6+148 environment: sdk: ^3.5.4