Merge pull request #25 from flixcoo/feature/4-fix-widget-state-update

feature/4-fix-widget-state-update
This commit is contained in:
2025-06-08 19:30:48 +02:00
committed by GitHub
7 changed files with 184 additions and 149 deletions

View File

@@ -0,0 +1,30 @@
import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:flutter/foundation.dart';
class GameManager extends ChangeNotifier {
List<GameSession> gameList = [];
/// Adds a new game session to the list and sorts it by creation date.
/// Takes a [GameSession] object as input. It then adds the session to the `gameList`,
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
/// It also saves the updated game sessions to local storage.
void addGameSession(GameSession session) {
gameList.add(session);
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
notifyListeners();
LocalStorageService.saveGameSessions();
}
/// Removes a game session from the list and sorts it by creation date.
/// Takes a [index] as input. It then removes the session at the specified index from the `gameList`,
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
/// It also saves the updated game sessions to local storage.
void removeGameSession(int index) {
gameList.removeAt(index);
notifyListeners();
LocalStorageService.saveGameSessions();
}
}
final globals = GameManager();

View File

@@ -1,8 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/utility/globals.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:file_saver/file_saver.dart'; import 'package:file_saver/file_saver.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -19,7 +19,7 @@ class LocalStorageService {
/// Writes the game session list to a JSON file and returns it as string. /// Writes the game session list to a JSON file and returns it as string.
static String getJsonFile() { static String getJsonFile() {
final jsonFile = final jsonFile =
Globals.gameList.map((session) => session.toJson()).toList(); globals.gameList.map((session) => session.toJson()).toList();
return json.encode(jsonFile); return json.encode(jsonFile);
} }
@@ -63,14 +63,14 @@ class LocalStorageService {
if (!await validateJsonSchema(jsonString)) { if (!await validateJsonSchema(jsonString)) {
logger.w('Die Datei konnte nicht validiert werden'); logger.w('Die Datei konnte nicht validiert werden');
Globals.gameList = []; globals.gameList = [];
return false; return false;
} }
logger.d('Die gefundene Datei hat Inhalt'); logger.d('Die gefundene Datei hat Inhalt');
logger.d('Die gefundene Datei wurde erfolgreich validiert'); logger.d('Die gefundene Datei wurde erfolgreich validiert');
final jsonList = json.decode(jsonString) as List<dynamic>; final jsonList = json.decode(jsonString) as List<dynamic>;
Globals.gameList = jsonList globals.gameList = jsonList
.map((jsonItem) => .map((jsonItem) =>
GameSession.fromJson(jsonItem as Map<String, dynamic>)) GameSession.fromJson(jsonItem as Map<String, dynamic>))
.toList(); .toList();
@@ -80,7 +80,7 @@ class LocalStorageService {
} catch (e) { } catch (e) {
logger.e('Fehler beim Laden der Spieldaten:\n$e', logger.e('Fehler beim Laden der Spieldaten:\n$e',
error: 'JSON nicht geladen'); error: 'JSON nicht geladen');
Globals.gameList = []; globals.gameList = [];
return false; return false;
} }
} }
@@ -125,7 +125,7 @@ class LocalStorageService {
return false; return false;
} }
final jsonData = json.decode(jsonString) as List<dynamic>; final jsonData = json.decode(jsonString) as List<dynamic>;
Globals.gameList = jsonData globals.gameList = jsonData
.map((jsonItem) => .map((jsonItem) =>
GameSession.fromJson(jsonItem as Map<String, dynamic>)) GameSession.fromJson(jsonItem as Map<String, dynamic>))
.toList(); .toList();
@@ -172,7 +172,7 @@ class LocalStorageService {
static Future<bool> deleteAllGames() async { static Future<bool> deleteAllGames() async {
try { try {
Globals.gameList.clear(); globals.gameList.clear();
await saveGameSessions(); await saveGameSessions();
logger.i('Alle Runden wurden erfolgreich gelöscht.'); logger.i('Alle Runden wurden erfolgreich gelöscht.');
return true; return true;

View File

@@ -1,17 +1,5 @@
import 'package:cabo_counter/data/game_session.dart';
class Globals { class Globals {
/// The [gameList] contains all active game sessions.
static List<GameSession> gameList = [];
static void addGameSession(GameSession session) {
gameList.add(session);
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
}
static int pointLimit = 100; static int pointLimit = 100;
static int caboPenalty = 5; static int caboPenalty = 5;
static String appDevPhase = 'Alpha'; static String appDevPhase = 'Alpha';
} }

View File

@@ -1,3 +1,4 @@
import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.dart';
@@ -289,9 +290,7 @@ class _CreateGameState extends State<CreateGame> {
caboPenalty: Globals.caboPenalty, caboPenalty: Globals.caboPenalty,
isPointsLimitEnabled: selectedMode!, isPointsLimitEnabled: selectedMode!,
); );
setState(() { globals.addGameSession(gameSession);
Globals.addGameSession(gameSession);
});
LocalStorageService.saveGameSessions(); LocalStorageService.saveGameSessions();
if (context.mounted) { if (context.mounted) {
Navigator.pushReplacement( Navigator.pushReplacement(

View File

@@ -1,6 +1,6 @@
import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.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/active_game_view.dart';
import 'package:cabo_counter/views/create_game_view.dart'; import 'package:cabo_counter/views/create_game_view.dart';
import 'package:cabo_counter/views/settings_view.dart'; import 'package:cabo_counter/views/settings_view.dart';
@@ -26,6 +26,11 @@ class _MainMenuViewState extends State<MainMenuView> {
_isLoading = false; _isLoading = false;
}); });
}); });
globals.addListener(_updateView);
}
void _updateView() {
if (mounted) setState(() {});
} }
@override @override
@@ -33,132 +38,143 @@ class _MainMenuViewState extends State<MainMenuView> {
print('MainMenuView build'); print('MainMenuView build');
LocalStorageService.loadGameSessions(); LocalStorageService.loadGameSessions();
return CupertinoPageScaffold( return ListenableBuilder(
resizeToAvoidBottomInset: false, listenable: globals,
navigationBar: CupertinoNavigationBar( builder: (context, _) {
leading: IconButton( return CupertinoPageScaffold(
onPressed: () { resizeToAvoidBottomInset: false,
Navigator.push( navigationBar: CupertinoNavigationBar(
context, leading: IconButton(
CupertinoPageRoute( onPressed: () {
builder: (context) => const SettingsView(), Navigator.push(
), context,
); CupertinoPageRoute(
}, builder: (context) => const SettingsView(),
icon: const Icon(CupertinoIcons.settings, size: 30)), ),
middle: const Text('Cabo Counter'), );
trailing: IconButton( },
onPressed: () => { icon: const Icon(CupertinoIcons.settings, size: 30)),
Navigator.push( middle: const Text('Cabo Counter'),
context, trailing: IconButton(
CupertinoPageRoute( onPressed: () => {
builder: (context) => const CreateGame(), 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),
),
),
],
)
: 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. /// Translates the game mode boolean into the corresponding String.
@@ -204,7 +220,7 @@ class _MainMenuViewState extends State<MainMenuView> {
/// This function takes an [index] as parameter and removes the game session at /// This function takes an [index] as parameter and removes the game session at
/// that index from the global game list, /// that index from the global game list,
void _deleteSpecificGame(int index) { void _deleteSpecificGame(int index) {
Globals.gameList.removeAt(index); globals.gameList.removeAt(index);
LocalStorageService.saveGameSessions(); LocalStorageService.saveGameSessions();
} }
} }

View File

@@ -58,6 +58,7 @@ class _RoundViewState extends State<RoundView> {
_kamikazePlayerIndex = _kamikazePlayerIndex =
gameSession.roundList[widget.roundNumber - 1].kamikazePlayerIndex; gameSession.roundList[widget.roundNumber - 1].kamikazePlayerIndex;
} }
super.initState(); super.initState();
} }
@@ -215,6 +216,7 @@ class _RoundViewState extends State<RoundView> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
onSubmitted: (_) => onSubmitted: (_) =>
_focusNextTextfield(index), _focusNextTextfield(index),
onChanged: (_) => setState(() {}),
), ),
), ),
const SizedBox(width: 50), const SizedBox(width: 50),

View File

@@ -2,7 +2,7 @@ name: cabo_counter
description: "Mobile app for the card game Cabo" description: "Mobile app for the card game Cabo"
publish_to: 'none' publish_to: 'none'
version: 0.1.6+145 version: 0.1.6+149
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4