Merge pull request #32 from flixcoo/bug/30-bug-game-rounds-not-initially-safed

Bug/30-bug-game-rounds-not-initially-safed
This commit is contained in:
2025-06-11 15:38:38 +02:00
committed by GitHub
7 changed files with 200 additions and 163 deletions

View File

@@ -9,11 +9,21 @@ class GameManager extends ChangeNotifier {
/// Takes a [GameSession] object as input. It then adds the session to the `gameList`, /// 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. /// 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. /// It also saves the updated game sessions to local storage.
void addGameSession(GameSession session) { /// Returns the index of the newly added session in the sorted list.
Future<int> addGameSession(GameSession session) async {
session.addListener(() {
notifyListeners(); // Propagate session changes
});
gameList.add(session); gameList.add(session);
print(
'[game_manager.dart] Added game session: ${session.gameTitle} at ${session.createdAt}');
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
print(
'[game_manager.dart] Sorted game sessions by creation date. Total sessions: ${gameList.length}');
notifyListeners(); notifyListeners();
LocalStorageService.saveGameSessions(); await LocalStorageService.saveGameSessions();
print('[game_manager.dart] Saved game sessions to local storage.');
return gameList.indexOf(session);
} }
/// Removes a game session from the list and sorts it by creation date. /// Removes a game session from the list and sorts it by creation date.
@@ -21,6 +31,7 @@ class GameManager extends ChangeNotifier {
/// sorts the list in descending order based on the creation date, and notifies listeners of the change. /// 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. /// It also saves the updated game sessions to local storage.
void removeGameSession(int index) { void removeGameSession(int index) {
gameList[index].removeListener(notifyListeners);
gameList.removeAt(index); gameList.removeAt(index);
notifyListeners(); notifyListeners();
LocalStorageService.saveGameSessions(); LocalStorageService.saveGameSessions();

View File

@@ -11,7 +11,7 @@ import 'package:flutter/cupertino.dart';
/// [roundNumber] is the current round number. /// [roundNumber] is the current round number.
/// [isGameFinished] is a boolean indicating if the game has ended yet. /// [isGameFinished] is a boolean indicating if the game has ended yet.
/// [winner] is the name of the player who won the game. /// [winner] is the name of the player who won the game.
class GameSession { class GameSession extends ChangeNotifier {
final DateTime createdAt; final DateTime createdAt;
final String gameTitle; final String gameTitle;
final List<String> players; final List<String> players;
@@ -222,6 +222,7 @@ class GameSession {
} else { } else {
roundList[roundNum - 1] = newRound; roundList[roundNum - 1] = newRound;
} }
notifyListeners();
} }
/// This method updates the points of each player after a round. /// This method updates the points of each player after a round.
@@ -248,6 +249,7 @@ class GameSession {
} }
} }
} }
notifyListeners();
} }
@visibleForTesting @visibleForTesting
@@ -262,6 +264,7 @@ class GameSession {
playerScores[i] += roundList[j].scoreUpdates[i]; playerScores[i] += roundList[j].scoreUpdates[i];
} }
} }
notifyListeners();
} }
/// Checks if a player has reached 100 points in the current round. /// Checks if a player has reached 100 points in the current round.
@@ -291,10 +294,14 @@ class GameSession {
} }
} }
winner = lowestPlayer; winner = lowestPlayer;
notifyListeners();
} }
/// Increases the round number by 1. /// Increases the round number by 1.
void increaseRound() { void increaseRound() {
roundNumber++; roundNumber++;
print('roundNumber erhöht: $roundNumber — Hash: ${identityHashCode(this)}');
notifyListeners();
} }
} }

View File

@@ -28,6 +28,7 @@ class LocalStorageService {
/// Saves the game sessions to a local JSON file. /// Saves the game sessions to a local JSON file.
static Future<void> saveGameSessions() async { static Future<void> saveGameSessions() async {
print('[local_storage_service.dart] Versuche, Daten zu speichern...');
try { try {
final file = await _getFilePath(); final file = await _getFilePath();
final jsonFile = getJsonFile(); final jsonFile = getJsonFile();

View File

@@ -15,6 +15,9 @@ class ActiveGameView extends StatefulWidget {
class _ActiveGameViewState extends State<ActiveGameView> { class _ActiveGameViewState extends State<ActiveGameView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListenableBuilder(
listenable: widget.gameSession,
builder: (context, _) {
List<int> sortedPlayerIndices = _getSortedPlayerIndices(); List<int> sortedPlayerIndices = _getSortedPlayerIndices();
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
@@ -43,14 +46,16 @@ class _ActiveGameViewState extends State<ActiveGameView> {
const SizedBox(width: 5), const SizedBox(width: 5),
Text( Text(
widget.gameSession.players[playerIndex], widget.gameSession.players[playerIndex],
style: const TextStyle(fontWeight: FontWeight.bold), style:
const TextStyle(fontWeight: FontWeight.bold),
), ),
], ],
), ),
trailing: Row( trailing: Row(
children: [ children: [
const SizedBox(width: 5), const SizedBox(width: 5),
Text('${widget.gameSession.playerScores[playerIndex]} ' Text(
'${widget.gameSession.playerScores[playerIndex]} '
'Punkte') 'Punkte')
], ],
), ),
@@ -74,7 +79,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
title: Text( title: Text(
'Runde ${index + 1}', 'Runde ${index + 1}',
), ),
trailing: index + 1 != widget.gameSession.roundNumber || trailing: index + 1 !=
widget.gameSession.roundNumber ||
widget.gameSession.isGameFinished == true widget.gameSession.isGameFinished == true
? (const Text('\u{2705}', ? (const Text('\u{2705}',
style: TextStyle(fontSize: 22))) style: TextStyle(fontSize: 22)))
@@ -82,8 +88,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
style: TextStyle(fontSize: 22)), style: TextStyle(fontSize: 22)),
onTap: () async { onTap: () async {
// ignore: unused_local_variable // ignore: unused_local_variable
final val = final val = await Navigator.of(context,
await Navigator.of(context, rootNavigator: true) rootNavigator: true)
.push( .push(
CupertinoPageRoute( CupertinoPageRoute(
fullscreenDialog: true, fullscreenDialog: true,
@@ -92,7 +98,6 @@ class _ActiveGameViewState extends State<ActiveGameView> {
roundNumber: index + 1), roundNumber: index + 1),
), ),
); );
setState(() {});
}, },
)); ));
}, },
@@ -101,6 +106,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
), ),
), ),
); );
});
} }
/// Returns a list of player indices sorted by their scores in /// Returns a list of player indices sorted by their scores in

View File

@@ -206,7 +206,7 @@ class _CreateGameState extends State<CreateGame> {
), ),
], ],
), ),
onPressed: () { onPressed: () async {
if (_gameTitleTextController.text == '') { if (_gameTitleTextController.text == '') {
showCupertinoDialog( showCupertinoDialog(
context: context, context: context,
@@ -289,13 +289,14 @@ class _CreateGameState extends State<CreateGame> {
caboPenalty: Globals.caboPenalty, caboPenalty: Globals.caboPenalty,
isPointsLimitEnabled: selectedMode!, isPointsLimitEnabled: selectedMode!,
); );
gameManager.addGameSession(gameSession); final index = await gameManager.addGameSession(gameSession);
print('index des spiels: $index');
if (context.mounted) { if (context.mounted) {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
CupertinoPageRoute( CupertinoPageRoute(
builder: (context) => builder: (context) => ActiveGameView(
ActiveGameView(gameSession: gameSession))); gameSession: gameManager.gameList[index])));
} else { } else {
print('Context is not mounted'); print('Context is not mounted');
} }

View File

@@ -35,9 +35,6 @@ class _MainMenuViewState extends State<MainMenuView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print('MainMenuView build');
LocalStorageService.loadGameSessions();
return ListenableBuilder( return ListenableBuilder(
listenable: gameManager, listenable: gameManager,
builder: (context, _) { builder: (context, _) {
@@ -100,12 +97,16 @@ class _MainMenuViewState extends State<MainMenuView> {
itemCount: gameManager.gameList.length, itemCount: gameManager.gameList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final session = gameManager.gameList[index]; final session = gameManager.gameList[index];
return ListenableBuilder(
listenable: session,
builder: (context, _) {
return Dismissible( return Dismissible(
key: Key(session.gameTitle), key: Key(session.gameTitle),
background: Container( background: Container(
color: CupertinoColors.destructiveRed, color: CupertinoColors.destructiveRed,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 20.0), padding:
const EdgeInsets.only(left: 20.0),
child: const Icon( child: const Icon(
CupertinoIcons.delete, CupertinoIcons.delete,
color: CupertinoColors.white, color: CupertinoColors.white,
@@ -113,9 +114,10 @@ class _MainMenuViewState extends State<MainMenuView> {
), ),
direction: DismissDirection.startToEnd, direction: DismissDirection.startToEnd,
confirmDismiss: (direction) async { confirmDismiss: (direction) async {
final String gameTitle = final String gameTitle = gameManager
gameManager.gameList[index].gameTitle; .gameList[index].gameTitle;
return await _showDeleteGamePopup(gameTitle); return await _showDeleteGamePopup(
gameTitle);
}, },
onDismissed: (direction) { onDismissed: (direction) {
gameManager.removeGameSession(index); gameManager.removeGameSession(index);
@@ -130,16 +132,17 @@ class _MainMenuViewState extends State<MainMenuView> {
backgroundColorActivated: backgroundColorActivated:
CustomTheme.backgroundColor, CustomTheme.backgroundColor,
title: Text(session.gameTitle), title: Text(session.gameTitle),
subtitle: session.isGameFinished == true subtitle:
session.isGameFinished == true
? Text( ? Text(
'\u{1F947} ${session.winner}', '\u{1F947} ${session.winner}',
style: style: const TextStyle(
const TextStyle(fontSize: 14), fontSize: 14),
) )
: Text( : Text(
'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}',
style: style: const TextStyle(
const TextStyle(fontSize: 14), fontSize: 14),
), ),
trailing: Row( trailing: Row(
children: [ children: [
@@ -159,9 +162,10 @@ class _MainMenuViewState extends State<MainMenuView> {
final val = await Navigator.push( final val = await Navigator.push(
context, context,
CupertinoPageRoute( CupertinoPageRoute(
builder: (context) => ActiveGameView( builder: (context) =>
gameSession: ActiveGameView(
gameManager.gameList[index]), gameSession: gameManager
.gameList[index]),
), ),
); );
setState(() {}); setState(() {});
@@ -169,6 +173,7 @@ class _MainMenuViewState extends State<MainMenuView> {
), ),
), ),
); );
});
}, },
), ),
), ),
@@ -215,4 +220,10 @@ class _MainMenuViewState extends State<MainMenuView> {
false; false;
return shouldDelete; return shouldDelete;
} }
@override
void dispose() {
gameManager.removeListener(_updateView);
super.dispose();
}
} }

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.2.1+171 version: 0.2.3+181
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4