Merge branch 'develop' into feature/29-create-and-test-android-version

# Conflicts:
#	pubspec.yaml
This commit is contained in:
2025-06-11 17:04:04 +02:00
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`,
/// 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) {
/// 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);
print(
'[game_manager.dart] Added game session: ${session.gameTitle} at ${session.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();
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.
@@ -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.
/// It also saves the updated game sessions to local storage.
void removeGameSession(int index) {
gameList[index].removeListener(notifyListeners);
gameList.removeAt(index);
notifyListeners();
LocalStorageService.saveGameSessions();

View File

@@ -11,7 +11,7 @@ import 'package:flutter/cupertino.dart';
/// [roundNumber] is the current round number.
/// [isGameFinished] is a boolean indicating if the game has ended yet.
/// [winner] is the name of the player who won the game.
class GameSession {
class GameSession extends ChangeNotifier {
final DateTime createdAt;
final String gameTitle;
final List<String> players;
@@ -222,6 +222,7 @@ class GameSession {
} else {
roundList[roundNum - 1] = newRound;
}
notifyListeners();
}
/// This method updates the points of each player after a round.
@@ -248,6 +249,7 @@ class GameSession {
}
}
}
notifyListeners();
}
@visibleForTesting
@@ -262,6 +264,7 @@ class GameSession {
playerScores[i] += roundList[j].scoreUpdates[i];
}
}
notifyListeners();
}
/// Checks if a player has reached 100 points in the current round.
@@ -291,10 +294,14 @@ class GameSession {
}
}
winner = lowestPlayer;
notifyListeners();
}
/// Increases the round number by 1.
void increaseRound() {
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.
static Future<void> saveGameSessions() async {
print('[local_storage_service.dart] Versuche, Daten zu speichern...');
try {
final file = await _getFilePath();
final jsonFile = getJsonFile();

View File

@@ -15,92 +15,98 @@ class ActiveGameView extends StatefulWidget {
class _ActiveGameViewState extends State<ActiveGameView> {
@override
Widget build(BuildContext context) {
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(widget.gameSession.gameTitle),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
'Spieler:innen',
style: CustomTheme.rowTitle,
return ListenableBuilder(
listenable: widget.gameSession,
builder: (context, _) {
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(widget.gameSession.gameTitle),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
'Spieler:innen',
style: CustomTheme.rowTitle,
),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.gameSession.players.length,
itemBuilder: (BuildContext context, int index) {
int playerIndex = sortedPlayerIndices[index];
return CupertinoListTile(
title: Row(
children: [
_getPlacementPrefix(index),
const SizedBox(width: 5),
Text(
widget.gameSession.players[playerIndex],
style:
const TextStyle(fontWeight: FontWeight.bold),
),
],
),
trailing: Row(
children: [
const SizedBox(width: 5),
Text(
'${widget.gameSession.playerScores[playerIndex]} '
'Punkte')
],
),
);
},
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
'Runden',
style: CustomTheme.rowTitle,
),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.gameSession.roundNumber,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(1),
child: CupertinoListTile(
title: Text(
'Runde ${index + 1}',
),
trailing: index + 1 !=
widget.gameSession.roundNumber ||
widget.gameSession.isGameFinished == true
? (const Text('\u{2705}',
style: TextStyle(fontSize: 22)))
: 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: widget.gameSession,
roundNumber: index + 1),
),
);
},
));
},
),
],
),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.gameSession.players.length,
itemBuilder: (BuildContext context, int index) {
int playerIndex = sortedPlayerIndices[index];
return CupertinoListTile(
title: Row(
children: [
_getPlacementPrefix(index),
const SizedBox(width: 5),
Text(
widget.gameSession.players[playerIndex],
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
trailing: Row(
children: [
const SizedBox(width: 5),
Text('${widget.gameSession.playerScores[playerIndex]} '
'Punkte')
],
),
);
},
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
'Runden',
style: CustomTheme.rowTitle,
),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.gameSession.roundNumber,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(1),
child: CupertinoListTile(
title: Text(
'Runde ${index + 1}',
),
trailing: index + 1 != widget.gameSession.roundNumber ||
widget.gameSession.isGameFinished == true
? (const Text('\u{2705}',
style: TextStyle(fontSize: 22)))
: 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: widget.gameSession,
roundNumber: index + 1),
),
);
setState(() {});
},
));
},
),
],
),
),
);
);
});
}
/// 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 == '') {
showCupertinoDialog(
context: context,
@@ -289,13 +289,14 @@ class _CreateGameState extends State<CreateGame> {
caboPenalty: Globals.caboPenalty,
isPointsLimitEnabled: selectedMode!,
);
gameManager.addGameSession(gameSession);
final index = await gameManager.addGameSession(gameSession);
print('index des spiels: $index');
if (context.mounted) {
Navigator.pushReplacement(
context,
CupertinoPageRoute(
builder: (context) =>
ActiveGameView(gameSession: gameSession)));
builder: (context) => ActiveGameView(
gameSession: gameManager.gameList[index])));
} else {
print('Context is not mounted');
}

View File

@@ -35,9 +35,6 @@ class _MainMenuViewState extends State<MainMenuView> {
@override
Widget build(BuildContext context) {
print('MainMenuView build');
LocalStorageService.loadGameSessions();
return ListenableBuilder(
listenable: gameManager,
builder: (context, _) {
@@ -100,75 +97,83 @@ class _MainMenuViewState extends State<MainMenuView> {
itemCount: gameManager.gameList.length,
itemBuilder: (context, index) {
final session = gameManager.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 =
gameManager.gameList[index].gameTitle;
return await _showDeleteGamePopup(gameTitle);
},
onDismissed: (direction) {
gameManager.removeGameSession(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:
gameManager.gameList[index]),
return ListenableBuilder(
listenable: session,
builder: (context, _) {
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,
),
);
setState(() {});
},
),
),
);
),
direction: DismissDirection.startToEnd,
confirmDismiss: (direction) async {
final String gameTitle = gameManager
.gameList[index].gameTitle;
return await _showDeleteGamePopup(
gameTitle);
},
onDismissed: (direction) {
gameManager.removeGameSession(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: gameManager
.gameList[index]),
),
);
setState(() {});
},
),
),
);
});
},
),
),
@@ -215,4 +220,10 @@ class _MainMenuViewState extends State<MainMenuView> {
false;
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"
publish_to: 'none'
version: 0.2.1+182
version: 0.2.4+192
environment:
sdk: ^3.5.4