Merge pull request #17 from flixcoo/feature/13-saving-and-retrieving-game-data-as-json

feature/13 saving and retrieving game data as json
This commit is contained in:
2025-05-02 11:49:47 +02:00
committed by GitHub
14 changed files with 424 additions and 158 deletions

View File

@@ -3,7 +3,7 @@ import 'package:cabo_counter/data/round.dart';
/// This class represents a game session for Cabo game. /// This class represents a game session for Cabo game.
/// [createdAt] is the timestamp of when the game session was created. /// [createdAt] is the timestamp of when the game session was created.
/// [gameTitle] is the title of the game. /// [gameTitle] is the title of the game.
/// [gameHasPointLimit] is a boolean indicating if the game has the default /// [isPointsLimitEnabled] is a boolean indicating if the game has the default
/// point limit of 101 points or not. /// point limit of 101 points or not.
/// [players] is a string list of player names. /// [players] is a string list of player names.
/// [playerScores] is a list of the summed scores of all players. /// [playerScores] is a list of the summed scores of all players.
@@ -13,7 +13,7 @@ import 'package:cabo_counter/data/round.dart';
class GameSession { class GameSession {
final DateTime createdAt = DateTime.now(); final DateTime createdAt = DateTime.now();
final String gameTitle; final String gameTitle;
final bool gameHasPointLimit; final bool isPointsLimitEnabled;
final List<String> players; final List<String> players;
late List<int> playerScores; late List<int> playerScores;
List<Round> roundList = []; List<Round> roundList = [];
@@ -23,21 +23,45 @@ class GameSession {
GameSession({ GameSession({
required this.gameTitle, required this.gameTitle,
required this.gameHasPointLimit, required this.isPointsLimitEnabled,
required this.players, required this.players,
}) { }) {
playerScores = List.filled(players.length, 0); playerScores = List.filled(players.length, 0);
} }
@override @override
String toString() { toString() {
return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, ' return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, '
'gameHasPointLimit: $gameHasPointLimit, players: $players, ' 'isPointsLimitEnabled: $isPointsLimitEnabled, players: $players, '
'playerScores: $playerScores, roundList: $roundList, ' 'playerScores: $playerScores, roundList: $roundList, '
'roundNumber: $roundNumber, isGameFinished: $isGameFinished, ' 'roundNumber: $roundNumber, isGameFinished: $isGameFinished, '
'winner: $winner]'); 'winner: $winner]');
} }
/// Converts the GameSession object to a JSON map.
Map<String, dynamic> toJson() => {
'gameTitle': gameTitle,
'gameHasPointLimit': isPointsLimitEnabled,
'players': players,
'playerScores': playerScores,
'roundNumber': roundNumber,
'isGameFinished': isGameFinished,
'winner': winner,
'roundList': roundList.map((e) => e.toJson()).toList()
};
/// Creates a GameSession object from a JSON map.
GameSession.fromJson(Map<String, dynamic> json)
: gameTitle = json['gameTitle'],
isPointsLimitEnabled = json['gameHasPointLimit'],
players = List<String>.from(json['players']),
playerScores = List<int>.from(json['playerScores']),
roundNumber = json['roundNumber'],
isGameFinished = json['isGameFinished'],
winner = json['winner'],
roundList =
(json['roundList'] as List).map((e) => Round.fromJson(e)).toList();
/// Returns the length of all player names combined. /// Returns the length of all player names combined.
int getLengthOfPlayerNames() { int getLengthOfPlayerNames() {
int length = 0; int length = 0;
@@ -184,7 +208,7 @@ class GameSession {
/// the winner. /// the winner.
void updatePoints() { void updatePoints() {
_sumPoints(); _sumPoints();
if (gameHasPointLimit) { if (isPointsLimitEnabled) {
_checkHundredPointsReached(); _checkHundredPointsReached();
for (int i = 0; i < playerScores.length; i++) { for (int i = 0; i < playerScores.length; i++) {

View File

@@ -1,3 +1,5 @@
import 'package:cabo_counter/data/game_session.dart';
/// This class represents a single round in the game. /// This class represents a single round in the game.
/// It is stored within the [GameSession] class. /// It is stored within the [GameSession] class.
/// [roundNum] is the number of the round its reppresenting. /// [roundNum] is the number of the round its reppresenting.
@@ -16,4 +18,25 @@ class Round {
required this.scores, required this.scores,
required this.scoreUpdates, required this.scoreUpdates,
this.kamikazePlayerIndex}); this.kamikazePlayerIndex});
@override
toString() {
return 'Round $roundNum: scores: $scores, scoreUpdates: $scoreUpdates, '
'kamikazePlayerIndex: $kamikazePlayerIndex';
}
/// Converts the Round object to a JSON map.
Map<String, dynamic> toJson() => {
'roundNum': roundNum,
'scores': scores,
'scoreUpdates': scoreUpdates,
'kamikazePlayerIndex': kamikazePlayerIndex,
};
/// Creates a Round object from a JSON map.
Round.fromJson(Map<String, dynamic> json)
: roundNum = json['roundNum'],
scores = List<int>.from(json['scores']),
scoreUpdates = List<int>.from(json['scoreUpdates']),
kamikazePlayerIndex = json['kamikazePlayerIndex'];
} }

View File

@@ -1,23 +1,90 @@
import 'package:cabo_counter/utility/theme.dart' as theme; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/utility/globals.dart';
import 'package:cabo_counter/utility/local_storage_service.dart';
import 'package:cabo_counter/views/main_menu_view.dart'; import 'package:cabo_counter/views/main_menu_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
void main() { void main() {
/// FIXME Just for Debugging
/// Fills the game list with some test data.
Globals.addGameSession(GameSession(
gameTitle: 'Spiel am 27.02.2025',
players: ['Clara', 'Tobias', 'Yannik', 'Lena', 'Lekaia'],
isPointsLimitEnabled: true));
Globals.addGameSession(GameSession(
gameTitle: 'Freundschaftsrunde',
players: ['Felix', 'Jonas', 'Nils'],
isPointsLimitEnabled: false));
Globals.addGameSession(GameSession(
gameTitle: 'Familienabend',
players: ['Mama', 'Papa', 'Lisa'],
isPointsLimitEnabled: true,
));
Globals.addGameSession(GameSession(
gameTitle: 'Turnier 1. Runde',
players: ['Tim', 'Max', 'Sophie', 'Lena'],
isPointsLimitEnabled: false));
Globals.addGameSession(GameSession(
gameTitle: '2 Namen max length',
players: ['Heinrich', 'Johannes'],
isPointsLimitEnabled: true));
Globals.addGameSession(GameSession(
gameTitle: '3 Namen max length',
players: ['Benjamin', 'Stefanie', 'Wolfgang'],
isPointsLimitEnabled: false));
Globals.addGameSession(GameSession(
gameTitle: '4 Namen max length',
players: ['Leonhard', 'Mathilde', 'Bernhard', 'Gerlinde'],
isPointsLimitEnabled: true));
Globals.addGameSession(GameSession(
gameTitle: '5 Namen max length',
players: ['Hartmuth', 'Elisabet', 'Rosalind', 'Theresia', 'Karoline'],
isPointsLimitEnabled: false));
runApp(const App()); runApp(const App());
} }
class App extends StatelessWidget { class App extends StatefulWidget {
const App({super.key}); const App({super.key});
@override
State<StatefulWidget> createState() => _AppState();
}
class _AppState extends State<App> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
LocalStorageService.loadGameSessions();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
LocalStorageService.saveGameSessions();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
LocalStorageService.loadGameSessions();
return CupertinoApp( return CupertinoApp(
theme: CupertinoThemeData( theme: CupertinoThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
primaryColor: theme.primaryColor, primaryColor: AppTheme.primaryColor,
scaffoldBackgroundColor: theme.backgroundColor, scaffoldBackgroundColor: AppTheme.backgroundColor,
textTheme: CupertinoTextThemeData( textTheme: CupertinoTextThemeData(
primaryColor: theme.primaryColor, primaryColor: AppTheme.primaryColor,
), ),
), ),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View File

@@ -1,8 +1,10 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
abstract class Styles { class AppTheme {
static Color white = CupertinoColors.white;
static Color primaryColor = CupertinoColors.systemGreen; static Color primaryColor = CupertinoColors.systemGreen;
static Color backgroundColor = const Color(0xFF080808); static Color backgroundColor = const Color(0xFF101010);
static Color backgroundTintColor = CupertinoColors.darkBackgroundGray;
static TextStyle modeTitle = TextStyle( static TextStyle modeTitle = TextStyle(
color: primaryColor, color: primaryColor,
@@ -20,15 +22,15 @@ abstract class Styles {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
); );
static TextStyle roundTitle = const TextStyle( static TextStyle roundTitle = TextStyle(
fontSize: 60, fontSize: 60,
color: CupertinoColors.white, color: white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
); );
static TextStyle roundPlayers = const TextStyle( static TextStyle roundPlayers = TextStyle(
fontSize: 20, fontSize: 20,
color: CupertinoColors.white, color: white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
); );
} }

View File

@@ -1,5 +1,11 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
class Globals { class Globals {
static Map<int, GameSession> gamesMap = <int, GameSession>{}; /// 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));
}
} }

View File

@@ -0,0 +1,121 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
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_saver/file_saver.dart';
import 'package:path_provider/path_provider.dart';
class LocalStorageService {
static const String _fileName = 'game_data.json';
/// 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();
return json.encode(jsonFile);
}
/// Returns the path to the local JSON file.
static Future<File> _getFilePath() async {
final directory = await getApplicationDocumentsDirectory();
final path = '${directory.path}/$_fileName';
return File(path);
}
/// Saves the game sessions to a local JSON file.
static Future<void> saveGameSessions() async {
try {
final file = await _getFilePath();
final jsonFile = getJsonFile();
await file.writeAsString(jsonFile);
print('Daten gespeichert');
} catch (e) {
print('Fehler beim Speichern: $e');
}
}
/// Loads the game data from a local JSON file.
static Future<void> loadGameSessions() async {
print('Versuche, Daten zu laden...');
try {
final file = await _getFilePath();
if (await file.exists()) {
print('Es existiert bereits eine Datei mit Spieldaten');
final jsonString = await file.readAsString();
if (jsonString.isNotEmpty) {
print('Die gefundene Datei ist nicht leer');
final jsonList = json.decode(jsonString) as List<dynamic>;
Globals.gameList = jsonList
.map((jsonItem) =>
GameSession.fromJson(jsonItem as Map<String, dynamic>))
.toList()
.cast<GameSession>();
print('Die Daten wurden erfolgreich geladen');
} else {
print('Die Datei ist leer');
}
} else {
print('Es existiert bisher noch keine Datei mit Spieldaten');
}
} catch (e) {
print('Fehler beim Laden der Spieldaten:\n$e');
Globals.gameList = [];
}
}
/// Opens the file picker to save a JSON file with the current game data.
static Future<bool> exportJsonFile() async {
final jsonString = getJsonFile();
try {
final bytes = Uint8List.fromList(utf8.encode(jsonString));
final result = await FileSaver.instance.saveAs(
name: 'cabo_counter_data',
bytes: bytes,
ext: 'json',
mimeType: MimeType.json,
);
print('Datei gespeichert: $result');
return true;
} catch (e) {
print('Fehler beim Speichern: $e');
return false;
}
}
/// Opens the file picker to import a JSON file and loads the game data from it.
static Future<bool> importJsonFile() async {
try {
final result = await FilePicker.platform.pickFiles(
dialogTitle: 'Wähle eine Datei mit Spieldaten aus',
type: FileType.custom,
allowedExtensions: ['json'],
);
String jsonString = '';
if (result != null) {
if (result.files.single.bytes != null) {
final Uint8List fileBytes = result.files.single.bytes!;
jsonString = utf8.decode(fileBytes);
} else if (result.files.single.path != null) {
final file = File(result.files.single.path!);
jsonString = await file.readAsString();
}
final jsonList = json.decode(jsonString) as List<dynamic>;
print('JSON Inhalt: $jsonList');
Globals.gameList = jsonList
.map((jsonItem) =>
GameSession.fromJson(jsonItem as Map<String, dynamic>))
.toList();
return true;
} else {
print('Der Dialog wurde abgebrochen');
return true;
}
} catch (e) {
print('Fehler beim Importieren: $e');
return false;
}
}
}

View File

@@ -1,34 +0,0 @@
import 'package:flutter/cupertino.dart';
Color white = CupertinoColors.white;
Color primaryColor = CupertinoColors.systemGreen;
Color backgroundColor = const Color(0xFF101010);
Color backgroundTintColor = CupertinoColors.darkBackgroundGray;
TextStyle modeTitle = TextStyle(
color: primaryColor,
fontSize: 20,
fontWeight: FontWeight.bold,
);
const TextStyle modeDescription = TextStyle(
fontSize: 16,
);
TextStyle createGameTitle = TextStyle(
fontSize: 20,
color: primaryColor,
fontWeight: FontWeight.bold,
);
TextStyle roundTitle = const TextStyle(
fontSize: 60,
color: CupertinoColors.white,
fontWeight: FontWeight.bold,
);
TextStyle roundPlayers = const TextStyle(
fontSize: 20,
color: CupertinoColors.white,
fontWeight: FontWeight.bold,
);

View File

@@ -1,5 +1,5 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/utility/theme.dart' as theme; import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/views/round_view.dart'; import 'package:cabo_counter/views/round_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -28,7 +28,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text( child: Text(
'Spieler:innen', 'Spieler:innen',
style: theme.createGameTitle, style: AppTheme.createGameTitle,
), ),
), ),
ListView.builder( ListView.builder(
@@ -61,7 +61,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text( child: Text(
'Runden', 'Runden',
style: theme.createGameTitle, style: AppTheme.createGameTitle,
), ),
), ),
ListView.builder( ListView.builder(

View File

@@ -1,5 +1,7 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/utility/styles.dart'; import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/utility/globals.dart';
import 'package:cabo_counter/utility/local_storage_service.dart';
import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/active_game_view.dart';
import 'package:cabo_counter/views/mode_selection_view.dart'; import 'package:cabo_counter/views/mode_selection_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -42,7 +44,7 @@ class _CreateGameState extends State<CreateGame> {
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text( child: Text(
'Spiel', 'Spiel',
style: Styles.createGameTitle, style: AppTheme.createGameTitle,
), ),
), ),
Padding( Padding(
@@ -96,7 +98,7 @@ class _CreateGameState extends State<CreateGame> {
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text( child: Text(
'Spieler:innen', 'Spieler:innen',
style: Styles.createGameTitle, style: AppTheme.createGameTitle,
), ),
), ),
Expanded( Expanded(
@@ -273,7 +275,6 @@ class _CreateGameState extends State<CreateGame> {
], ],
), ),
); );
return;
} }
List<String> players = []; List<String> players = [];
@@ -283,8 +284,10 @@ class _CreateGameState extends State<CreateGame> {
GameSession gameSession = GameSession( GameSession gameSession = GameSession(
gameTitle: _gameTitleTextController.text, gameTitle: _gameTitleTextController.text,
players: players, players: players,
gameHasPointLimit: selectedMode!, isPointsLimitEnabled: selectedMode!,
); );
Globals.addGameSession(gameSession);
LocalStorageService.saveGameSessions();
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
CupertinoPageRoute( CupertinoPageRoute(

View File

@@ -1,3 +1,4 @@
import 'package:cabo_counter/utility/local_storage_service.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -14,6 +15,7 @@ class InformationView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
navigationBar: const CupertinoNavigationBar( navigationBar: const CupertinoNavigationBar(
middle: Text('Über'), middle: Text('Über'),
), ),
@@ -79,7 +81,52 @@ class InformationView extends StatelessWidget {
Uri.parse('https://www.github.com/flixcoo')), Uri.parse('https://www.github.com/flixcoo')),
icon: const Icon(FontAwesomeIcons.github)), icon: const Icon(FontAwesomeIcons.github)),
], ],
) ),
CupertinoButton(
sizeStyle: CupertinoButtonSize.medium,
child: const Text('Spieldaten exportieren'),
onPressed: () async {
final success = await LocalStorageService.exportJsonFile();
if (!success && context.mounted) {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('Fehler'),
content: const Text(
'Datei konnte nicht exportiert werden.'),
actions: [
CupertinoDialogAction(
child: const Text('OK'),
onPressed: () => Navigator.pop(context),
),
],
),
);
}
},
),
CupertinoButton(
sizeStyle: CupertinoButtonSize.medium,
child: const Text('Spieldaten importieren'),
onPressed: () async {
final success =
await LocalStorageService.importJsonFile();
if (!success && context.mounted) {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('Fehler'),
content: const Text(
'Datei konnte nicht importiert werden.'),
actions: [
CupertinoDialogAction(
child: const Text('OK'),
onPressed: () => Navigator.pop(context),
),
],
));
}
}),
], ],
), ),
Positioned( Positioned(

View File

@@ -1,4 +1,6 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/utility/globals.dart';
import 'package:cabo_counter/utility/local_storage_service.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/information_view.dart'; import 'package:cabo_counter/views/information_view.dart';
@@ -14,47 +16,19 @@ class MainMenuView extends StatefulWidget {
} }
class _MainMenuViewState extends State<MainMenuView> { class _MainMenuViewState extends State<MainMenuView> {
final List<GameSession> gameSessionArray = [ @override
GameSession( initState() {
gameTitle: 'Spiel am 27.02.2025', super.initState();
players: ['Clara', 'Tobias', 'Yannik', 'Lena', 'Lekaia'], LocalStorageService.loadGameSessions().then((_) {
gameHasPointLimit: true), setState(() {});
GameSession( });
gameTitle: 'Freundschaftsrunde', }
players: ['Felix', 'Jonas', 'Nils'],
gameHasPointLimit: false),
GameSession(
gameTitle: 'Familienabend',
players: ['Mama', 'Papa', 'Lisa'],
gameHasPointLimit: true,
),
GameSession(
gameTitle: 'Turnier 1. Runde',
players: ['Tim', 'Max', 'Sophie', 'Lena'],
gameHasPointLimit: false),
GameSession(
gameTitle: '2 Namen max length',
players: ['Heinrich', 'Johannes'],
gameHasPointLimit: true),
GameSession(
gameTitle: '3 Namen max length',
players: ['Benjamin', 'Stefanie', 'Wolfgang'],
gameHasPointLimit: false),
GameSession(
gameTitle: '4 Namen max length',
players: ['Leonhard', 'Mathilde', 'Bernhard', 'Gerlinde'],
gameHasPointLimit: true),
GameSession(
gameTitle: '5 Namen max length',
players: ['Hartmuth', 'Elisabet', 'Rosalind', 'Theresia', 'Karoline'],
gameHasPointLimit: false),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
gameSessionArray.sort((b, a) => a.createdAt.compareTo(b.createdAt)); LocalStorageService.loadGameSessions();
return CupertinoPageScaffold( return CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
leading: IconButton( leading: IconButton(
onPressed: () { onPressed: () {
@@ -83,49 +57,74 @@ class _MainMenuViewState extends State<MainMenuView> {
), ),
child: CupertinoPageScaffold( child: CupertinoPageScaffold(
child: SafeArea( child: SafeArea(
child: ListView.builder( child: Globals.gameList.isEmpty
itemCount: gameSessionArray.length, ? Column(
itemBuilder: (context, index) { mainAxisAlignment:
final session = gameSessionArray[index]; MainAxisAlignment.center, // Oben ausrichten
return Padding( children: [
padding: const EdgeInsets.symmetric(vertical: 10.0), const SizedBox(height: 30), // Abstand von oben
child: CupertinoListTile( Center(
title: Text(session.gameTitle), child: GestureDetector(
subtitle: session.isGameFinished == true onTap: () => setState(() {}),
? Text( child: Icon(
'\u{1F947} ${session.winner}', CupertinoIcons.plus,
style: const TextStyle(fontSize: 14), size: 60,
) color: AppTheme.primaryColor,
: Text( ),
'Modus: ${_translateGameMode(session.gameHasPointLimit)}', )),
style: const TextStyle(fontSize: 14), const SizedBox(height: 10), // Abstand von oben
), const Padding(
trailing: Row( padding: EdgeInsets.symmetric(horizontal: 70),
children: [ child: Text(
Text('${session.roundNumber}'), 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.',
const SizedBox(width: 3), textAlign: TextAlign.center,
const Icon( style: TextStyle(fontSize: 16),
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( : ListView.builder(
context, itemCount: Globals.gameList.length,
CupertinoPageRoute( itemBuilder: (context, index) {
builder: (context) => ActiveGameView( final session = Globals.gameList[index];
gameSession: gameSessionArray[index]), return Padding(
), padding: const EdgeInsets.symmetric(vertical: 10.0),
); child: CupertinoListTile(
setState(() {}); 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(() {});
},
));
}),
), ),
), ),
); );

View File

@@ -1,5 +1,4 @@
import 'package:cabo_counter/utility/styles.dart'; import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/utility/theme.dart' as theme;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class ModeSelectionMenu extends StatelessWidget { class ModeSelectionMenu extends StatelessWidget {
@@ -16,10 +15,10 @@ class ModeSelectionMenu extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), padding: const EdgeInsets.fromLTRB(0, 16, 0, 0),
child: CupertinoListTile( child: CupertinoListTile(
title: Text('101 Punkte', style: Styles.modeTitle), title: Text('101 Punkte', style: AppTheme.modeTitle),
subtitle: const Text( subtitle: const Text(
'Es wird solange gespielt, bis einer Spieler mehr als 100 Punkte erreicht', 'Es wird solange gespielt, bis einer Spieler mehr als 100 Punkte erreicht',
style: Styles.modeDescription, style: AppTheme.modeDescription,
maxLines: 3, maxLines: 3,
), ),
onTap: () { onTap: () {
@@ -30,11 +29,11 @@ class ModeSelectionMenu extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: CupertinoListTile( child: CupertinoListTile(
title: Text('Unbegrenzt', style: theme.modeTitle), title: Text('Unbegrenzt', style: AppTheme.modeTitle),
subtitle: const Text( subtitle: const Text(
'Dem Spiel sind keine Grenzen gesetzt. Es wird so lange ' 'Dem Spiel sind keine Grenzen gesetzt. Es wird so lange '
'gespielt, bis Ihr keine Lust mehr habt.', 'gespielt, bis Ihr keine Lust mehr habt.',
style: Styles.modeDescription, style: AppTheme.modeDescription,
maxLines: 3, maxLines: 3,
), ),
onTap: () { onTap: () {

View File

@@ -1,5 +1,6 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/utility/theme.dart' as theme; import 'package:cabo_counter/utility/apptheme.dart';
import 'package:cabo_counter/utility/local_storage_service.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
@@ -71,7 +72,10 @@ class _RoundViewState extends State<RoundView> {
previousPageTitle: 'Übersicht', previousPageTitle: 'Übersicht',
leading: CupertinoButton( leading: CupertinoButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: () => Navigator.pop(context, widget.gameSession), onPressed: () => {
LocalStorageService.saveGameSessions(),
Navigator.pop(context, widget.gameSession)
},
child: const Text('Abbrechen'), child: const Text('Abbrechen'),
), ),
), ),
@@ -86,7 +90,7 @@ class _RoundViewState extends State<RoundView> {
children: [ children: [
const SizedBox(height: 40), const SizedBox(height: 40),
Text('Runde ${widget.roundNumber}', Text('Runde ${widget.roundNumber}',
style: theme.roundTitle), style: AppTheme.roundTitle),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text( const Text(
'Wer hat CABO gesagt?', 'Wer hat CABO gesagt?',
@@ -101,8 +105,8 @@ class _RoundViewState extends State<RoundView> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: CupertinoSegmentedControl<int>( child: CupertinoSegmentedControl<int>(
unselectedColor: theme.backgroundTintColor, unselectedColor: AppTheme.backgroundTintColor,
selectedColor: theme.primaryColor, selectedColor: AppTheme.primaryColor,
groupValue: _caboPlayerIndex, groupValue: _caboPlayerIndex,
children: Map.fromEntries(widget.gameSession.players children: Map.fromEntries(widget.gameSession.players
.asMap() .asMap()
@@ -267,7 +271,7 @@ class _RoundViewState extends State<RoundView> {
return Container( return Container(
height: 80, height: 80,
padding: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.only(bottom: 20),
color: theme.backgroundTintColor, color: AppTheme.backgroundTintColor,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
@@ -275,6 +279,7 @@ class _RoundViewState extends State<RoundView> {
onPressed: _areRoundInputsValid() onPressed: _areRoundInputsValid()
? () { ? () {
_finishRound(); _finishRound();
LocalStorageService.saveGameSessions();
Navigator.pop(context, widget.gameSession); Navigator.pop(context, widget.gameSession);
} }
: null, : null,
@@ -284,6 +289,7 @@ class _RoundViewState extends State<RoundView> {
onPressed: _areRoundInputsValid() onPressed: _areRoundInputsValid()
? () { ? () {
_finishRound(); _finishRound();
LocalStorageService.saveGameSessions();
if (widget.gameSession.isGameFinished == true) { if (widget.gameSession.isGameFinished == true) {
Navigator.pop(context, widget.gameSession); Navigator.pop(context, widget.gameSession);
} else { } else {

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.3+65 version: 0.1.5+110
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -11,15 +11,18 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
font_awesome_flutter: ^10.8.0 file_picker: ^10.1.2
url_launcher: any file_saver: ^0.2.6
package_info_plus: any
flutter_keyboard_visibility: ^6.0.0 flutter_keyboard_visibility: ^6.0.0
font_awesome_flutter: ^10.8.0
package_info_plus: any
path_provider: ^2.1.1
typed_data: ^1.3.2
url_launcher: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
test: ^1.25.15 test: ^1.25.15