@@ -1,8 +1,8 @@
|
||||
# CABO Counter
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
291
assets/game-schema.json
Normal file
291
assets/game-schema.json
Normal file
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Generated schema for a single cabo counter game",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"gameTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pointLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPenalty": {
|
||||
"type": "integer"
|
||||
},
|
||||
"isPointsLimitEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isGameFinished": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"winner": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundNumber": {
|
||||
"type": "integer"
|
||||
},
|
||||
"playerScores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roundList": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roundNum": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPlayerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kamikazePlayerIndex": {
|
||||
"type": "null"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scoreUpdates": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roundNum",
|
||||
"caboPlayerIndex",
|
||||
"kamikazePlayerIndex",
|
||||
"scores",
|
||||
"scoreUpdates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roundNum": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPlayerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kamikazePlayerIndex": {
|
||||
"type": "null"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scoreUpdates": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roundNum",
|
||||
"caboPlayerIndex",
|
||||
"kamikazePlayerIndex",
|
||||
"scores",
|
||||
"scoreUpdates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roundNum": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPlayerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kamikazePlayerIndex": {
|
||||
"type": "null"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scoreUpdates": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roundNum",
|
||||
"caboPlayerIndex",
|
||||
"kamikazePlayerIndex",
|
||||
"scores",
|
||||
"scoreUpdates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roundNum": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPlayerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kamikazePlayerIndex": {
|
||||
"type": "null"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scoreUpdates": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roundNum",
|
||||
"caboPlayerIndex",
|
||||
"kamikazePlayerIndex",
|
||||
"scores",
|
||||
"scoreUpdates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roundNum": {
|
||||
"type": "integer"
|
||||
},
|
||||
"caboPlayerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kamikazePlayerIndex": {
|
||||
"type": "null"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scoreUpdates": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roundNum",
|
||||
"caboPlayerIndex",
|
||||
"kamikazePlayerIndex",
|
||||
"scores",
|
||||
"scoreUpdates"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"gameTitle",
|
||||
"players",
|
||||
"pointLimit",
|
||||
"caboPenalty",
|
||||
"isPointsLimitEnabled",
|
||||
"isGameFinished",
|
||||
"winner",
|
||||
"roundNumber",
|
||||
"playerScores",
|
||||
"roundList"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Generated schema for cabo game data",
|
||||
"title": "Generated schema for the cabo counter game data",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@@ -293,14 +293,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
@@ -15,14 +15,9 @@ class GameManager extends ChangeNotifier {
|
||||
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();
|
||||
await LocalStorageService.saveGameSessions();
|
||||
print('[game_manager.dart] Saved game sessions to local storage.');
|
||||
return gameList.indexOf(session);
|
||||
}
|
||||
|
||||
@@ -30,12 +25,44 @@ class GameManager extends ChangeNotifier {
|
||||
/// 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) {
|
||||
void removeGameSessionByIndex(int index) {
|
||||
gameList[index].removeListener(notifyListeners);
|
||||
gameList.removeAt(index);
|
||||
notifyListeners();
|
||||
LocalStorageService.saveGameSessions();
|
||||
}
|
||||
|
||||
/// Removes a game session by its ID.
|
||||
/// Takes a String [id] as input. It finds the index of the game session with the matching ID
|
||||
/// in the `gameList`, and then calls `removeGameSessionByIndex` with that index.
|
||||
void removeGameSessionById(String id) {
|
||||
final int index =
|
||||
gameList.indexWhere((session) => session.id.toString() == id);
|
||||
if (index == -1) return;
|
||||
removeGameSessionByIndex(index);
|
||||
}
|
||||
|
||||
/// Retrieves a game session by its ID.
|
||||
/// Takes a String [id] as input. It finds the game session with the matching id
|
||||
bool gameExistsInGameList(String id) {
|
||||
return gameList.any((session) => session.id.toString() == id);
|
||||
}
|
||||
|
||||
/// Ends a game session if its in unlimited mode.
|
||||
/// Takes a String [id] as input. It finds the index of the game
|
||||
/// session with the matching ID marks it as finished,
|
||||
void endGame(String id) {
|
||||
final int index =
|
||||
gameList.indexWhere((session) => session.id.toString() == id);
|
||||
|
||||
// Game session not found or not in unlimited mode
|
||||
if (index == -1 || gameList[index].isPointsLimitEnabled == true) return;
|
||||
|
||||
gameList[index].roundNumber--;
|
||||
gameList[index].isGameFinished = true;
|
||||
notifyListeners();
|
||||
LocalStorageService.saveGameSessions();
|
||||
}
|
||||
}
|
||||
|
||||
final gameManager = GameManager();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cabo_counter/data/round.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// This class represents a game session for Cabo game.
|
||||
/// [createdAt] is the timestamp of when the game session was created.
|
||||
@@ -12,6 +13,7 @@ import 'package:flutter/cupertino.dart';
|
||||
/// [isGameFinished] is a boolean indicating if the game has ended yet.
|
||||
/// [winner] is the name of the player who won the game.
|
||||
class GameSession extends ChangeNotifier {
|
||||
late String id;
|
||||
final DateTime createdAt;
|
||||
final String gameTitle;
|
||||
final List<String> players;
|
||||
@@ -33,17 +35,20 @@ class GameSession extends ChangeNotifier {
|
||||
required this.isPointsLimitEnabled,
|
||||
}) {
|
||||
playerScores = List.filled(players.length, 0);
|
||||
var uuid = const Uuid();
|
||||
id = uuid.v1();
|
||||
}
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, '
|
||||
return ('GameSession: [id: $id, createdAt: $createdAt, gameTitle: $gameTitle, '
|
||||
'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,'
|
||||
' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]');
|
||||
}
|
||||
|
||||
/// Converts the GameSession object to a JSON map.
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'gameTitle': gameTitle,
|
||||
'players': players,
|
||||
@@ -59,7 +64,8 @@ class GameSession extends ChangeNotifier {
|
||||
|
||||
/// Creates a GameSession object from a JSON map.
|
||||
GameSession.fromJson(Map<String, dynamic> json)
|
||||
: createdAt = DateTime.parse(json['createdAt']),
|
||||
: id = json['id'] ?? const Uuid().v1(),
|
||||
createdAt = DateTime.parse(json['createdAt']),
|
||||
gameTitle = json['gameTitle'],
|
||||
players = List<String>.from(json['players']),
|
||||
pointLimit = json['pointLimit'],
|
||||
@@ -72,11 +78,13 @@ class GameSession extends ChangeNotifier {
|
||||
roundList =
|
||||
(json['roundList'] as List).map((e) => Round.fromJson(e)).toList();
|
||||
|
||||
/// Returns the length of all player names combined.
|
||||
int getLengthOfPlayerNames() {
|
||||
/// Returns the length of the longest player name.
|
||||
int getMaxLengthOfPlayerNames() {
|
||||
int length = 0;
|
||||
for (String player in players) {
|
||||
length += player.length;
|
||||
if (player.length >= length) {
|
||||
length = player.length;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"player": "Spieler:in",
|
||||
"players": "Spieler:innen",
|
||||
"name": "Name",
|
||||
"back": "Zurück",
|
||||
|
||||
"home": "Home",
|
||||
"about": "Über",
|
||||
@@ -21,7 +22,7 @@
|
||||
"empty_text_1": "Ganz schön leer hier...",
|
||||
"empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu",
|
||||
"delete_game_title": "Spiel löschen?",
|
||||
"delete_game_message": "Bist du sicher, dass du die Runde {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"delete_game_message": "Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"@delete_game_message": {
|
||||
"placeholders": {
|
||||
"gameTitle": {
|
||||
@@ -63,6 +64,18 @@
|
||||
"done": "Fertig",
|
||||
"next_round": "Nächste Runde",
|
||||
|
||||
"statistics": "Statistiken",
|
||||
"end_game": "Spiel beenden",
|
||||
"delete_game": "Spiel löschen",
|
||||
"new_game_same_settings": "Neues Spiel mit gleichen Einstellungen",
|
||||
"export_game": "Spiel exportieren",
|
||||
"id_error_title": "ID Fehler",
|
||||
"id_error_message": "Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.",
|
||||
"end_game_title": "Spiel beenden?",
|
||||
"end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.",
|
||||
|
||||
"game_process": "Spielverlauf",
|
||||
|
||||
"settings": "Einstellungen",
|
||||
"cabo_penalty": "Cabo-Strafe",
|
||||
"cabo_penalty_subtitle": "... für falsches Cabo sagen",
|
||||
@@ -72,9 +85,18 @@
|
||||
"game_data": "Spieldaten",
|
||||
"import_data": "Daten importieren",
|
||||
"export_data": "Daten exportieren",
|
||||
"error": "Fehler",
|
||||
"error_import": "Datei konnte nicht importiert werden",
|
||||
"error_export": "Datei konnte nicht exportiert werden",
|
||||
|
||||
"import_success_title": "Import erfolgreich",
|
||||
"import_success_message":"Die Spieldaten wurden erfolgreich importiert.",
|
||||
"import_validation_error_title": "Validierung fehlgeschlagen",
|
||||
"import_validation_error_message": "Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.",
|
||||
"import_format_error_title": "Falsches Format",
|
||||
"import_format_error_message": "Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.",
|
||||
"import_generic_error_title": "Import fehlgeschlagen",
|
||||
"import_generic_error_message": "Der Import ist fehlgeschlagen.",
|
||||
|
||||
"export_error_title": "Fehler",
|
||||
"export_error_message": "Datei konnte nicht exportiert werden",
|
||||
"error_found": "Fehler gefunden?",
|
||||
"create_issue": "Issue erstellen",
|
||||
"app_version": "App-Version",
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
"player": "Player",
|
||||
"players": "Players",
|
||||
"name": "Name",
|
||||
"back": "Back",
|
||||
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
|
||||
"empty_text_1": "Pretty empty here...",
|
||||
"empty_text_2": "Add a new round using the button in the top right corner",
|
||||
"empty_text_2": "Add a new round using the button in the top right corner.",
|
||||
"delete_game_title": "Delete game?",
|
||||
"delete_game_message": "Are you sure you want to delete the game {gameTitle}? This action cannot be undone.",
|
||||
"delete_game_message": "Are you sure you want to delete the game \"{gameTitle}\"? This action cannot be undone.",
|
||||
"@delete_game_message": {
|
||||
"placeholders": {
|
||||
"gameTitle": {
|
||||
@@ -63,22 +64,44 @@
|
||||
"done": "Done",
|
||||
"next_round": "Next Round",
|
||||
|
||||
"statistics": "Statistics",
|
||||
"end_game": "End Game",
|
||||
"delete_game": "Delete Game",
|
||||
"new_game_same_settings": "New Game with same Settings",
|
||||
"export_game": "Export Game",
|
||||
|
||||
"game_process": "Spielverlauf",
|
||||
|
||||
"settings": "Settings",
|
||||
"cabo_penalty": "Cabo Penalty",
|
||||
"cabo_penalty_subtitle": "... for falsely calling Cabo",
|
||||
"cabo_penalty_subtitle": "... for falsely calling Cabo.",
|
||||
"point_limit": "Point Limit",
|
||||
"point_limit_subtitle": "... the game ends here",
|
||||
"point_limit_subtitle": "... the game ends here.",
|
||||
"reset_to_default": "Reset to Default",
|
||||
"game_data": "Game Data",
|
||||
"import_data": "Import Data",
|
||||
"export_data": "Export Data",
|
||||
"error": "Error",
|
||||
"error_import": "Could not import file",
|
||||
"error_export": "Could not export file",
|
||||
"id_error_title": "ID Error",
|
||||
"id_error_message": "The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.",
|
||||
"end_game_title": "End the game?",
|
||||
"end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.",
|
||||
|
||||
"import_success_title": "Import successful",
|
||||
"import_success_message":"The game data has been successfully imported.",
|
||||
"import_validation_error_title": "Validation failed",
|
||||
"import_validation_error_message": "No Cabo-Counter game data was found. Please make sure that this is a valid Cabo-Counter export file.",
|
||||
"import_format_error_title": "Wrong format",
|
||||
"import_format_error_message": "The file is not a valid JSON format or contains invalid data.",
|
||||
"import_generic_error_title": "Import failed",
|
||||
"import_generic_error_message": "The import has failed.",
|
||||
|
||||
"export_error_title": "Export failed",
|
||||
"export_error_message": "Could not export file",
|
||||
"error_found": "Found a bug?",
|
||||
"create_issue": "Create Issue",
|
||||
"app_version": "App Version",
|
||||
"load_version": "Loading version...",
|
||||
"build": "Build",
|
||||
"about_text": "Hey :) Thanks for being one of the first users of my first app! I’ve put a lot of work into this project, and even though I (hopefully) thought of a lot, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the Testflight app or a message / email. Thank you very much!"
|
||||
}
|
||||
|
||||
"about_text": "Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!"
|
||||
}
|
||||
|
||||
@@ -176,6 +176,12 @@ abstract class AppLocalizations {
|
||||
/// **'Name'**
|
||||
String get name;
|
||||
|
||||
/// No description provided for @back.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Zurück'**
|
||||
String get back;
|
||||
|
||||
/// No description provided for @home.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
@@ -209,7 +215,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @delete_game_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Bist du sicher, dass du die Runde {gameTitle} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'**
|
||||
/// **'Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'**
|
||||
String delete_game_message(String gameTitle);
|
||||
|
||||
/// No description provided for @overview.
|
||||
@@ -356,6 +362,66 @@ abstract class AppLocalizations {
|
||||
/// **'Nächste Runde'**
|
||||
String get next_round;
|
||||
|
||||
/// No description provided for @statistics.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Statistiken'**
|
||||
String get statistics;
|
||||
|
||||
/// No description provided for @end_game.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Spiel beenden'**
|
||||
String get end_game;
|
||||
|
||||
/// No description provided for @delete_game.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Spiel löschen'**
|
||||
String get delete_game;
|
||||
|
||||
/// No description provided for @new_game_same_settings.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Neues Spiel mit gleichen Einstellungen'**
|
||||
String get new_game_same_settings;
|
||||
|
||||
/// No description provided for @export_game.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Spiel exportieren'**
|
||||
String get export_game;
|
||||
|
||||
/// No description provided for @id_error_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'ID Fehler'**
|
||||
String get id_error_title;
|
||||
|
||||
/// No description provided for @id_error_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.'**
|
||||
String get id_error_message;
|
||||
|
||||
/// No description provided for @end_game_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Spiel beenden?'**
|
||||
String get end_game_title;
|
||||
|
||||
/// No description provided for @end_game_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'**
|
||||
String get end_game_message;
|
||||
|
||||
/// No description provided for @game_process.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Spielverlauf'**
|
||||
String get game_process;
|
||||
|
||||
/// No description provided for @settings.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
@@ -410,23 +476,65 @@ abstract class AppLocalizations {
|
||||
/// **'Daten exportieren'**
|
||||
String get export_data;
|
||||
|
||||
/// No description provided for @error.
|
||||
/// No description provided for @import_success_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Import erfolgreich'**
|
||||
String get import_success_title;
|
||||
|
||||
/// No description provided for @import_success_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Die Spieldaten wurden erfolgreich importiert.'**
|
||||
String get import_success_message;
|
||||
|
||||
/// No description provided for @import_validation_error_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Validierung fehlgeschlagen'**
|
||||
String get import_validation_error_title;
|
||||
|
||||
/// No description provided for @import_validation_error_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.'**
|
||||
String get import_validation_error_message;
|
||||
|
||||
/// No description provided for @import_format_error_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Falsches Format'**
|
||||
String get import_format_error_title;
|
||||
|
||||
/// No description provided for @import_format_error_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.'**
|
||||
String get import_format_error_message;
|
||||
|
||||
/// No description provided for @import_generic_error_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Import fehlgeschlagen'**
|
||||
String get import_generic_error_title;
|
||||
|
||||
/// No description provided for @import_generic_error_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Der Import ist fehlgeschlagen.'**
|
||||
String get import_generic_error_message;
|
||||
|
||||
/// No description provided for @export_error_title.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Fehler'**
|
||||
String get error;
|
||||
String get export_error_title;
|
||||
|
||||
/// No description provided for @error_import.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Datei konnte nicht importiert werden'**
|
||||
String get error_import;
|
||||
|
||||
/// No description provided for @error_export.
|
||||
/// No description provided for @export_error_message.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Datei konnte nicht exportiert werden'**
|
||||
String get error_export;
|
||||
String get export_error_message;
|
||||
|
||||
/// No description provided for @error_found.
|
||||
///
|
||||
|
||||
@@ -47,6 +47,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get name => 'Name';
|
||||
|
||||
@override
|
||||
String get back => 'Zurück';
|
||||
|
||||
@override
|
||||
String get home => 'Home';
|
||||
|
||||
@@ -65,7 +68,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String delete_game_message(String gameTitle) {
|
||||
return 'Bist du sicher, dass du die Runde $gameTitle löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.';
|
||||
return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -146,6 +149,38 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get next_round => 'Nächste Runde';
|
||||
|
||||
@override
|
||||
String get statistics => 'Statistiken';
|
||||
|
||||
@override
|
||||
String get end_game => 'Spiel beenden';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Spiel löschen';
|
||||
|
||||
@override
|
||||
String get new_game_same_settings => 'Neues Spiel mit gleichen Einstellungen';
|
||||
|
||||
@override
|
||||
String get export_game => 'Spiel exportieren';
|
||||
|
||||
@override
|
||||
String get id_error_title => 'ID Fehler';
|
||||
|
||||
@override
|
||||
String get id_error_message =>
|
||||
'Das Spiel hat bisher noch keine ID zugewiesen bekommen. Falls du das Spiel löschen möchtest, mache das bitte über das Hauptmenü. Alle neu erstellten Spiele haben eine ID.';
|
||||
|
||||
@override
|
||||
String get end_game_title => 'Spiel beenden?';
|
||||
|
||||
@override
|
||||
String get end_game_message =>
|
||||
'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.';
|
||||
|
||||
@override
|
||||
String get game_process => 'Spielverlauf';
|
||||
|
||||
@override
|
||||
String get settings => 'Einstellungen';
|
||||
|
||||
@@ -174,13 +209,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get export_data => 'Daten exportieren';
|
||||
|
||||
@override
|
||||
String get error => 'Fehler';
|
||||
String get import_success_title => 'Import erfolgreich';
|
||||
|
||||
@override
|
||||
String get error_import => 'Datei konnte nicht importiert werden';
|
||||
String get import_success_message =>
|
||||
'Die Spieldaten wurden erfolgreich importiert.';
|
||||
|
||||
@override
|
||||
String get error_export => 'Datei konnte nicht exportiert werden';
|
||||
String get import_validation_error_title => 'Validierung fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get import_validation_error_message =>
|
||||
'Es wurden keine Cabo-Counter Spieldaten gefunden. Bitte stellen Sie sicher, dass es sich um eine gültige Cabo-Counter Exportdatei handelt.';
|
||||
|
||||
@override
|
||||
String get import_format_error_title => 'Falsches Format';
|
||||
|
||||
@override
|
||||
String get import_format_error_message =>
|
||||
'Die Datei ist kein gültiges JSON-Format oder enthält ungültige Daten.';
|
||||
|
||||
@override
|
||||
String get import_generic_error_title => 'Import fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get import_generic_error_message => 'Der Import ist fehlgeschlagen.';
|
||||
|
||||
@override
|
||||
String get export_error_title => 'Fehler';
|
||||
|
||||
@override
|
||||
String get export_error_message => 'Datei konnte nicht exportiert werden';
|
||||
|
||||
@override
|
||||
String get error_found => 'Fehler gefunden?';
|
||||
|
||||
@@ -47,6 +47,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get name => 'Name';
|
||||
|
||||
@override
|
||||
String get back => 'Back';
|
||||
|
||||
@override
|
||||
String get home => 'Home';
|
||||
|
||||
@@ -58,14 +61,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get empty_text_2 =>
|
||||
'Add a new round using the button in the top right corner';
|
||||
'Add a new round using the button in the top right corner.';
|
||||
|
||||
@override
|
||||
String get delete_game_title => 'Delete game?';
|
||||
|
||||
@override
|
||||
String delete_game_message(String gameTitle) {
|
||||
return 'Are you sure you want to delete the game $gameTitle? This action cannot be undone.';
|
||||
return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -143,6 +146,38 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get next_round => 'Next Round';
|
||||
|
||||
@override
|
||||
String get statistics => 'Statistics';
|
||||
|
||||
@override
|
||||
String get end_game => 'End Game';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Delete Game';
|
||||
|
||||
@override
|
||||
String get new_game_same_settings => 'New Game with same Settings';
|
||||
|
||||
@override
|
||||
String get export_game => 'Export Game';
|
||||
|
||||
@override
|
||||
String get id_error_title => 'ID Error';
|
||||
|
||||
@override
|
||||
String get id_error_message =>
|
||||
'The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.';
|
||||
|
||||
@override
|
||||
String get end_game_title => 'End the game?';
|
||||
|
||||
@override
|
||||
String get end_game_message =>
|
||||
'Do you want to end the game? The game gets marked as finished and cannot be continued.';
|
||||
|
||||
@override
|
||||
String get game_process => 'Spielverlauf';
|
||||
|
||||
@override
|
||||
String get settings => 'Settings';
|
||||
|
||||
@@ -150,13 +185,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get cabo_penalty => 'Cabo Penalty';
|
||||
|
||||
@override
|
||||
String get cabo_penalty_subtitle => '... for falsely calling Cabo';
|
||||
String get cabo_penalty_subtitle => '... for falsely calling Cabo.';
|
||||
|
||||
@override
|
||||
String get point_limit => 'Point Limit';
|
||||
|
||||
@override
|
||||
String get point_limit_subtitle => '... the game ends here';
|
||||
String get point_limit_subtitle => '... the game ends here.';
|
||||
|
||||
@override
|
||||
String get reset_to_default => 'Reset to Default';
|
||||
@@ -171,13 +206,37 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get export_data => 'Export Data';
|
||||
|
||||
@override
|
||||
String get error => 'Error';
|
||||
String get import_success_title => 'Import successful';
|
||||
|
||||
@override
|
||||
String get error_import => 'Could not import file';
|
||||
String get import_success_message =>
|
||||
'The game data has been successfully imported.';
|
||||
|
||||
@override
|
||||
String get error_export => 'Could not export file';
|
||||
String get import_validation_error_title => 'Validation failed';
|
||||
|
||||
@override
|
||||
String get import_validation_error_message =>
|
||||
'No Cabo-Counter game data was found. Please make sure that this is a valid Cabo-Counter export file.';
|
||||
|
||||
@override
|
||||
String get import_format_error_title => 'Wrong format';
|
||||
|
||||
@override
|
||||
String get import_format_error_message =>
|
||||
'The file is not a valid JSON format or contains invalid data.';
|
||||
|
||||
@override
|
||||
String get import_generic_error_title => 'Import failed';
|
||||
|
||||
@override
|
||||
String get import_generic_error_message => 'The import has failed.';
|
||||
|
||||
@override
|
||||
String get export_error_title => 'Export failed';
|
||||
|
||||
@override
|
||||
String get export_error_message => 'Could not export file';
|
||||
|
||||
@override
|
||||
String get error_found => 'Found a bug?';
|
||||
@@ -196,5 +255,5 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get about_text =>
|
||||
'Hey :) Thanks for being one of the first users of my first app! I’ve put a lot of work into this project, and even though I (hopefully) thought of a lot, not everything will work 100% yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the Testflight app or a message / email. Thank you very much!';
|
||||
'Hey :) Thanks for being one of the first users of my app! I’ve put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!';
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:cabo_counter/l10n/app_localizations.dart';
|
||||
import 'package:cabo_counter/services/config_service.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||
import 'package:cabo_counter/utility/custom_theme.dart';
|
||||
import 'package:cabo_counter/utility/globals.dart';
|
||||
import 'package:cabo_counter/views/tab_view.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -12,8 +11,8 @@ Future<void> main() async {
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
await ConfigService.initConfig();
|
||||
Globals.pointLimit = await ConfigService.getPointLimit();
|
||||
Globals.caboPenalty = await ConfigService.getCaboPenalty();
|
||||
ConfigService.pointLimit = await ConfigService.getPointLimit();
|
||||
ConfigService.caboPenalty = await ConfigService.getCaboPenalty();
|
||||
runApp(const App());
|
||||
}
|
||||
|
||||
@@ -56,7 +55,16 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
Locale('en'), // English
|
||||
Locale('de'), // German
|
||||
],
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
for (final supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale?.languageCode) {
|
||||
return supportedLocale;
|
||||
}
|
||||
}
|
||||
return supportedLocales.first;
|
||||
},
|
||||
theme: CupertinoThemeData(
|
||||
applyThemeToAll: true,
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: CustomTheme.primaryColor,
|
||||
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:cabo_counter/utility/globals.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// This class handles the configuration settings for the app.
|
||||
@@ -7,8 +6,12 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
class ConfigService {
|
||||
static const String _keyPointLimit = 'pointLimit';
|
||||
static const String _keyCaboPenalty = 'caboPenalty';
|
||||
static const int _defaultPointLimit = 100; // Default Value
|
||||
static const int _defaultCaboPenalty = 5; // Default Value
|
||||
// Actual values used in the app
|
||||
static int pointLimit = 100;
|
||||
static int caboPenalty = 5;
|
||||
// Default values
|
||||
static const int _defaultPointLimit = 100;
|
||||
static const int _defaultCaboPenalty = 5;
|
||||
|
||||
static Future<void> initConfig() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -48,8 +51,8 @@ class ConfigService {
|
||||
|
||||
/// Resets the configuration to default values.
|
||||
static Future<void> resetConfig() async {
|
||||
Globals.pointLimit = _defaultPointLimit;
|
||||
Globals.caboPenalty = _defaultCaboPenalty;
|
||||
ConfigService.pointLimit = _defaultPointLimit;
|
||||
ConfigService.caboPenalty = _defaultCaboPenalty;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_keyPointLimit, _defaultPointLimit);
|
||||
await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty);
|
||||
|
||||
@@ -9,11 +9,19 @@ import 'package:flutter/services.dart';
|
||||
import 'package:json_schema/json_schema.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
enum ImportStatus {
|
||||
success,
|
||||
canceled,
|
||||
validationError,
|
||||
formatError,
|
||||
genericError
|
||||
}
|
||||
|
||||
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() {
|
||||
/// Writes the game session list to a JSON file and returns it as string.
|
||||
static String getGameDataAsJsonFile() {
|
||||
final jsonFile =
|
||||
gameManager.gameList.map((session) => session.toJson()).toList();
|
||||
return json.encode(jsonFile);
|
||||
@@ -31,7 +39,7 @@ class LocalStorageService {
|
||||
print('[local_storage_service.dart] Versuche, Daten zu speichern...');
|
||||
try {
|
||||
final file = await _getFilePath();
|
||||
final jsonFile = getJsonFile();
|
||||
final jsonFile = getGameDataAsJsonFile();
|
||||
await file.writeAsString(jsonFile);
|
||||
print(
|
||||
'[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.');
|
||||
@@ -62,7 +70,7 @@ class LocalStorageService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await validateJsonSchema(jsonString)) {
|
||||
if (!await validateJsonSchema(jsonString, true)) {
|
||||
print(
|
||||
'[local_storage_service.dart] Die Datei konnte nicht validiert werden');
|
||||
gameManager.gameList = [];
|
||||
@@ -78,6 +86,11 @@ class LocalStorageService {
|
||||
GameSession.fromJson(jsonItem as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
for (GameSession session in gameManager.gameList) {
|
||||
print(
|
||||
'[local_storage_service.dart] Geladene Session: ${session.gameTitle} - ${session.id}');
|
||||
}
|
||||
|
||||
print(
|
||||
'[local_storage_service.dart] Die Spieldaten wurden erfolgreich geladen und verarbeitet');
|
||||
return true;
|
||||
@@ -89,19 +102,27 @@ class LocalStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the file picker to save a JSON file with the current game data.
|
||||
static Future<bool> exportJsonFile() async {
|
||||
final jsonString = getJsonFile();
|
||||
/// Opens the file picker to export game data as a JSON file.
|
||||
/// This method will export the given [jsonString] as a JSON file. It opens
|
||||
/// the file picker with the choosen [fileName].
|
||||
static Future<bool> exportJsonData(
|
||||
String jsonString,
|
||||
String fileName,
|
||||
) async {
|
||||
try {
|
||||
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
||||
final result = await FileSaver.instance.saveAs(
|
||||
name: 'cabo_counter_data',
|
||||
final path = await FileSaver.instance.saveAs(
|
||||
name: fileName,
|
||||
bytes: bytes,
|
||||
ext: 'json',
|
||||
mimeType: MimeType.json,
|
||||
);
|
||||
print(
|
||||
'[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $result');
|
||||
if (path == null) {
|
||||
print('[local_storage_service.dart]: Export abgebrochen');
|
||||
} else {
|
||||
print(
|
||||
'[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $path');
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
print(
|
||||
@@ -110,45 +131,84 @@ class LocalStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the file picker to export all game sessions as a JSON file.
|
||||
static Future<bool> exportGameData() async {
|
||||
String jsonString = getGameDataAsJsonFile();
|
||||
String fileName = 'cabo_counter-game_data';
|
||||
return exportJsonData(jsonString, fileName);
|
||||
}
|
||||
|
||||
/// Opens the file picker to save a single game session as a JSON file.
|
||||
static Future<bool> exportSingleGameSession(GameSession session) async {
|
||||
String jsonString = json.encode(session.toJson());
|
||||
String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}';
|
||||
return exportJsonData(jsonString, fileName);
|
||||
}
|
||||
|
||||
/// Opens the file picker to import a JSON file and loads the game data from it.
|
||||
static Future<bool> importJsonFile() async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
static Future<ImportStatus> importJsonFile() async {
|
||||
final path = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: 'Wähle eine Datei mit Spieldaten aus',
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['json'],
|
||||
);
|
||||
|
||||
if (result == null) {
|
||||
if (path == null) {
|
||||
print(
|
||||
'[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen');
|
||||
return false;
|
||||
return ImportStatus.canceled;
|
||||
}
|
||||
|
||||
try {
|
||||
final jsonString = await _readFileContent(result.files.single);
|
||||
final jsonString = await _readFileContent(path.files.single);
|
||||
|
||||
if (!await validateJsonSchema(jsonString)) {
|
||||
return false;
|
||||
if (await validateJsonSchema(jsonString, true)) {
|
||||
// Checks if the JSON String is in the gameList format
|
||||
|
||||
final jsonData = json.decode(jsonString) as List<dynamic>;
|
||||
List<GameSession> importedList = jsonData
|
||||
.map((jsonItem) =>
|
||||
GameSession.fromJson(jsonItem as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
for (GameSession s in importedList) {
|
||||
importSession(s);
|
||||
}
|
||||
} else if (await validateJsonSchema(jsonString, false)) {
|
||||
// Checks if the JSON String is in the single game format
|
||||
final jsonData = json.decode(jsonString) as Map<String, dynamic>;
|
||||
importSession(GameSession.fromJson(jsonData));
|
||||
} else {
|
||||
return ImportStatus.validationError;
|
||||
}
|
||||
final jsonData = json.decode(jsonString) as List<dynamic>;
|
||||
gameManager.gameList = jsonData
|
||||
.map((jsonItem) =>
|
||||
GameSession.fromJson(jsonItem as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
print(
|
||||
'[local_storage_service.dart] Die Datei wurde erfolgreich Importiertn');
|
||||
return true;
|
||||
'[local_storage_service.dart] Die Datei wurde erfolgreich Importiert');
|
||||
await saveGameSessions();
|
||||
return ImportStatus.success;
|
||||
} on FormatException catch (e) {
|
||||
print(
|
||||
'[local_storage_service.dart] Ungültiges JSON-Format. Exception: $e');
|
||||
return false;
|
||||
return ImportStatus.formatError;
|
||||
} on Exception catch (e) {
|
||||
print(
|
||||
'[local_storage_service.dart] Fehler beim Dateizugriff. Exception: $e');
|
||||
return false;
|
||||
return ImportStatus.genericError;
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports a single game session into the gameList.
|
||||
static Future<void> importSession(GameSession session) async {
|
||||
if (gameManager.gameExistsInGameList(session.id)) {
|
||||
print(
|
||||
'[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.');
|
||||
gameManager.removeGameSessionById(session.id);
|
||||
}
|
||||
gameManager.addGameSession(session);
|
||||
print(
|
||||
'[local_storage_service.dart] Die Session mit der ID ${session.id} wurde erfolgreich importiert.');
|
||||
}
|
||||
|
||||
/// Helper method to read file content from either bytes or path
|
||||
static Future<String> _readFileContent(PlatformFile file) async {
|
||||
if (file.bytes != null) return utf8.decode(file.bytes!);
|
||||
@@ -158,15 +218,28 @@ class LocalStorageService {
|
||||
}
|
||||
|
||||
/// Validates the JSON data against the schema.
|
||||
static Future<bool> validateJsonSchema(String jsonString) async {
|
||||
/// This method checks if the provided [jsonString] is valid against the
|
||||
/// JSON schema. It takes a boolean [isGameList] to determine
|
||||
/// which schema to use (game list or single game).
|
||||
static Future<bool> validateJsonSchema(
|
||||
String jsonString, bool isGameList) async {
|
||||
final String schemaString;
|
||||
|
||||
if (isGameList) {
|
||||
schemaString =
|
||||
await rootBundle.loadString('assets/game_list-schema.json');
|
||||
} else {
|
||||
schemaString = await rootBundle.loadString('assets/game-schema.json');
|
||||
}
|
||||
|
||||
try {
|
||||
final schemaString = await rootBundle.loadString('assets/schema.json');
|
||||
final schema = JsonSchema.create(json.decode(schemaString));
|
||||
final jsonData = json.decode(jsonString);
|
||||
final result = schema.validate(jsonData);
|
||||
|
||||
if (result.isValid) {
|
||||
print('[local_storage_service.dart] JSON ist erfolgreich validiert.');
|
||||
print(
|
||||
'[local_storage_service.dart] JSON ist erfolgreich validiert. Typ: ${isGameList ? 'Game List' : 'Single Game'}');
|
||||
return true;
|
||||
}
|
||||
print(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
class Globals {
|
||||
static int pointLimit = 100;
|
||||
static int caboPenalty = 5;
|
||||
static String appDevPhase = 'Beta';
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import 'package:cabo_counter/data/game_manager.dart';
|
||||
import 'package:cabo_counter/data/game_session.dart';
|
||||
import 'package:cabo_counter/l10n/app_localizations.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||
import 'package:cabo_counter/utility/custom_theme.dart';
|
||||
import 'package:cabo_counter/views/create_game_view.dart';
|
||||
import 'package:cabo_counter/views/graph_view.dart';
|
||||
import 'package:cabo_counter/views/round_view.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActiveGameView extends StatefulWidget {
|
||||
final GameSession gameSession;
|
||||
@@ -14,15 +19,23 @@ class ActiveGameView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
late final GameSession gameSession;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
gameSession = widget.gameSession;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: widget.gameSession,
|
||||
listenable: gameSession,
|
||||
builder: (context, _) {
|
||||
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text(widget.gameSession.gameTitle),
|
||||
middle: Text(gameSession.gameTitle),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
@@ -39,7 +52,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.gameSession.players.length,
|
||||
itemCount: gameSession.players.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
int playerIndex = sortedPlayerIndices[index];
|
||||
return CupertinoListTile(
|
||||
@@ -48,7 +61,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
_getPlacementPrefix(index),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
widget.gameSession.players[playerIndex],
|
||||
gameSession.players[playerIndex],
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
@@ -57,8 +70,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
trailing: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
'${widget.gameSession.playerScores[playerIndex]} '
|
||||
Text('${gameSession.playerScores[playerIndex]} '
|
||||
'${AppLocalizations.of(context).points}')
|
||||
],
|
||||
),
|
||||
@@ -75,38 +87,133 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.gameSession.roundNumber,
|
||||
itemCount: gameSession.roundNumber,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: CupertinoListTile(
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
title: Text(
|
||||
'${AppLocalizations.of(context).round} ${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)),
|
||||
trailing:
|
||||
index + 1 != gameSession.roundNumber ||
|
||||
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),
|
||||
),
|
||||
);
|
||||
_openRoundView(index + 1);
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).game,
|
||||
style: CustomTheme.rowTitle,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
CupertinoListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).statistics,
|
||||
),
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => GraphView(
|
||||
gameSession: gameSession,
|
||||
)))),
|
||||
if (!gameSession.isPointsLimitEnabled)
|
||||
CupertinoListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).end_game,
|
||||
style: gameSession.roundNumber > 1 &&
|
||||
!gameSession.isGameFinished
|
||||
? const TextStyle(color: Colors.white)
|
||||
: const TextStyle(color: Colors.white30),
|
||||
),
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
onTap: () {
|
||||
if (gameSession.roundNumber > 1 &&
|
||||
!gameSession.isGameFinished) {
|
||||
_showEndGameDialog();
|
||||
}
|
||||
}),
|
||||
CupertinoListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).delete_game,
|
||||
),
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
onTap: () {
|
||||
_showDeleteGameDialog().then((value) {
|
||||
if (value) {
|
||||
_removeGameSession(gameSession);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
CupertinoListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)
|
||||
.new_game_same_settings,
|
||||
),
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
onTap: () {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => CreateGameView(
|
||||
gameTitle: gameSession.gameTitle,
|
||||
isPointsLimitEnabled: widget
|
||||
.gameSession
|
||||
.isPointsLimitEnabled,
|
||||
players: gameSession.players,
|
||||
)));
|
||||
},
|
||||
),
|
||||
CupertinoListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).export_game,
|
||||
),
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
onTap: () async {
|
||||
final success = await LocalStorageService
|
||||
.exportSingleGameSession(
|
||||
widget.gameSession);
|
||||
if (!success && context.mounted) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context)
|
||||
.export_error_title),
|
||||
content: Text(AppLocalizations.of(context)
|
||||
.export_error_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).ok),
|
||||
onPressed: () =>
|
||||
Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -114,15 +221,48 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Shows a dialog to confirm ending the game.
|
||||
/// If the user confirms, it calls the `endGame` method on the game manager
|
||||
void _showEndGameDialog() {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context).end_game_title),
|
||||
content: Text(AppLocalizations.of(context).end_game_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).end_game,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, color: Colors.red),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
gameManager.endGame(gameSession.id);
|
||||
});
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).cancel),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a list of player indices sorted by their scores in
|
||||
/// ascending order.
|
||||
List<int> _getSortedPlayerIndices() {
|
||||
List<int> playerIndices =
|
||||
List<int>.generate(widget.gameSession.players.length, (index) => index);
|
||||
List<int>.generate(gameSession.players.length, (index) => index);
|
||||
// Sort the indices based on the summed points
|
||||
playerIndices.sort((a, b) {
|
||||
int scoreA = widget.gameSession.playerScores[a];
|
||||
int scoreB = widget.gameSession.playerScores[b];
|
||||
int scoreA = gameSession.playerScores[a];
|
||||
int scoreB = gameSession.playerScores[b];
|
||||
return scoreA.compareTo(scoreB);
|
||||
});
|
||||
return playerIndices;
|
||||
@@ -155,4 +295,82 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _showDeleteGameDialog() async {
|
||||
return await showCupertinoDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context).delete_game_title),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)
|
||||
.delete_game_message(gameSession.gameTitle),
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).cancel),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, color: Colors.red),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
Future<void> _removeGameSession(GameSession gameSession) async {
|
||||
if (gameManager.gameExistsInGameList(gameSession.id)) {
|
||||
Navigator.pop(context);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
gameManager.removeGameSessionById(gameSession.id);
|
||||
});
|
||||
} else {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context).id_error_title),
|
||||
content: Text(AppLocalizations.of(context).id_error_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively opens the RoundView for the specified round number.
|
||||
/// It starts with the given [roundNumber] and continues to open the next round
|
||||
/// until the user navigates back or the round number is invalid.
|
||||
void _openRoundView(int roundNumber) async {
|
||||
final val = await Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => RoundView(
|
||||
gameSession: gameSession,
|
||||
roundNumber: roundNumber,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (val != null && val >= 0) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
_openRoundView(val);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
import 'package:cabo_counter/data/game_manager.dart';
|
||||
import 'package:cabo_counter/data/game_session.dart';
|
||||
import 'package:cabo_counter/l10n/app_localizations.dart';
|
||||
import 'package:cabo_counter/services/config_service.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/mode_selection_view.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class CreateGame extends StatefulWidget {
|
||||
const CreateGame({super.key});
|
||||
enum CreateStatus {
|
||||
noGameTitle,
|
||||
noModeSelected,
|
||||
minPlayers,
|
||||
maxPlayers,
|
||||
noPlayerName,
|
||||
}
|
||||
|
||||
class CreateGameView extends StatefulWidget {
|
||||
final String? gameTitle;
|
||||
final bool? isPointsLimitEnabled;
|
||||
final List<String>? players;
|
||||
|
||||
const CreateGameView({
|
||||
super.key,
|
||||
this.gameTitle,
|
||||
this.isPointsLimitEnabled,
|
||||
this.players,
|
||||
});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_CreateGameState createState() => _CreateGameState();
|
||||
_CreateGameViewState createState() => _CreateGameViewState();
|
||||
}
|
||||
|
||||
class _CreateGameState extends State<CreateGame> {
|
||||
class _CreateGameViewState extends State<CreateGameView> {
|
||||
final List<TextEditingController> _playerNameTextControllers = [
|
||||
TextEditingController()
|
||||
];
|
||||
@@ -25,8 +42,23 @@ class _CreateGameState extends State<CreateGame> {
|
||||
/// Maximum number of players allowed in the game.
|
||||
final int maxPlayers = 5;
|
||||
|
||||
/// Variable to store the selected game mode.
|
||||
bool? selectedMode;
|
||||
/// Variable to store whether the points limit feature is enabled.
|
||||
bool? _isPointsLimitEnabled;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_isPointsLimitEnabled = widget.isPointsLimitEnabled;
|
||||
_gameTitleTextController.text = widget.gameTitle ?? '';
|
||||
|
||||
if (widget.players != null) {
|
||||
_playerNameTextControllers.clear();
|
||||
for (var player in widget.players!) {
|
||||
_playerNameTextControllers.add(TextEditingController(text: player));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -69,10 +101,10 @@ class _CreateGameState extends State<CreateGame> {
|
||||
suffix: Row(
|
||||
children: [
|
||||
Text(
|
||||
selectedMode == null
|
||||
_isPointsLimitEnabled == null
|
||||
? AppLocalizations.of(context).select_mode
|
||||
: (selectedMode!
|
||||
? '${Globals.pointLimit} ${AppLocalizations.of(context).points}'
|
||||
: (_isPointsLimitEnabled!
|
||||
? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}'
|
||||
: AppLocalizations.of(context).unlimited),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
@@ -80,18 +112,18 @@ class _CreateGameState extends State<CreateGame> {
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
final selected = await Navigator.push(
|
||||
final selectedMode = await Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => ModeSelectionMenu(
|
||||
pointLimit: Globals.pointLimit,
|
||||
pointLimit: ConfigService.pointLimit,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
if (selectedMode != null) {
|
||||
setState(() {
|
||||
selectedMode = selected;
|
||||
_isPointsLimitEnabled = selectedMode;
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -139,22 +171,7 @@ class _CreateGameState extends State<CreateGame> {
|
||||
.add(TextEditingController());
|
||||
});
|
||||
} else {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context)
|
||||
.max_players_title),
|
||||
content: Text(AppLocalizations.of(context)
|
||||
.max_players_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child:
|
||||
Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showFeedbackDialog(CreateStatus.maxPlayers);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -183,6 +200,7 @@ class _CreateGameState extends State<CreateGame> {
|
||||
Expanded(
|
||||
child: CupertinoTextField(
|
||||
controller: _playerNameTextControllers[index],
|
||||
maxLength: 12,
|
||||
placeholder:
|
||||
'${AppLocalizations.of(context).player} ${index + 1}',
|
||||
padding: const EdgeInsets.all(12),
|
||||
@@ -212,73 +230,19 @@ class _CreateGameState extends State<CreateGame> {
|
||||
),
|
||||
onPressed: () async {
|
||||
if (_gameTitleTextController.text == '') {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).no_gameTitle_title),
|
||||
content: Text(
|
||||
AppLocalizations.of(context).no_gameTitle_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showFeedbackDialog(CreateStatus.noGameTitle);
|
||||
return;
|
||||
}
|
||||
if (selectedMode == null) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context).no_mode_title),
|
||||
content:
|
||||
Text(AppLocalizations.of(context).no_mode_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (_isPointsLimitEnabled == null) {
|
||||
showFeedbackDialog(CreateStatus.noModeSelected);
|
||||
return;
|
||||
}
|
||||
if (_playerNameTextControllers.length < 2) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).min_players_title),
|
||||
content: Text(
|
||||
AppLocalizations.of(context).min_players_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showFeedbackDialog(CreateStatus.minPlayers);
|
||||
return;
|
||||
}
|
||||
if (!everyPlayerHasAName()) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(AppLocalizations.of(context).no_name_title),
|
||||
content:
|
||||
Text(AppLocalizations.of(context).no_name_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showFeedbackDialog(CreateStatus.noPlayerName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,17 +254,18 @@ class _CreateGameState extends State<CreateGame> {
|
||||
createdAt: DateTime.now(),
|
||||
gameTitle: _gameTitleTextController.text,
|
||||
players: players,
|
||||
pointLimit: Globals.pointLimit,
|
||||
caboPenalty: Globals.caboPenalty,
|
||||
isPointsLimitEnabled: selectedMode!,
|
||||
pointLimit: ConfigService.pointLimit,
|
||||
caboPenalty: ConfigService.caboPenalty,
|
||||
isPointsLimitEnabled: _isPointsLimitEnabled!,
|
||||
);
|
||||
final index = await gameManager.addGameSession(gameSession);
|
||||
final session = gameManager.gameList[index];
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => ActiveGameView(
|
||||
gameSession: gameManager.gameList[index])));
|
||||
builder: (context) =>
|
||||
ActiveGameView(gameSession: session)));
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -309,6 +274,60 @@ class _CreateGameState extends State<CreateGame> {
|
||||
))));
|
||||
}
|
||||
|
||||
/// Displays a feedback dialog based on the [CreateStatus].
|
||||
void showFeedbackDialog(CreateStatus status) {
|
||||
final (title, message) = _getDialogContent(status);
|
||||
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the title and message for the dialog based on the [CreateStatus].
|
||||
(String, String) _getDialogContent(CreateStatus status) {
|
||||
switch (status) {
|
||||
case CreateStatus.noGameTitle:
|
||||
return (
|
||||
AppLocalizations.of(context).no_gameTitle_title,
|
||||
AppLocalizations.of(context).no_gameTitle_message
|
||||
);
|
||||
case CreateStatus.noModeSelected:
|
||||
return (
|
||||
AppLocalizations.of(context).no_mode_title,
|
||||
AppLocalizations.of(context).no_mode_message
|
||||
);
|
||||
|
||||
case CreateStatus.minPlayers:
|
||||
return (
|
||||
AppLocalizations.of(context).min_players_title,
|
||||
AppLocalizations.of(context).min_players_message
|
||||
);
|
||||
case CreateStatus.maxPlayers:
|
||||
return (
|
||||
AppLocalizations.of(context).max_players_title,
|
||||
AppLocalizations.of(context).max_players_message
|
||||
);
|
||||
case CreateStatus.noPlayerName:
|
||||
return (
|
||||
AppLocalizations.of(context).no_name_title,
|
||||
AppLocalizations.of(context).no_name_message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if every player has a name.
|
||||
/// Returns true if all players have a name, false otherwise.
|
||||
bool everyPlayerHasAName() {
|
||||
for (var controller in _playerNameTextControllers) {
|
||||
if (controller.text == '') {
|
||||
@@ -320,9 +339,11 @@ class _CreateGameState extends State<CreateGame> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_gameTitleTextController.dispose();
|
||||
for (var controller in _playerNameTextControllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
84
lib/views/graph_view.dart
Normal file
84
lib/views/graph_view.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'package:cabo_counter/data/game_session.dart';
|
||||
import 'package:cabo_counter/l10n/app_localizations.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
class GraphView extends StatefulWidget {
|
||||
final GameSession gameSession;
|
||||
|
||||
const GraphView({super.key, required this.gameSession});
|
||||
|
||||
@override
|
||||
State<GraphView> createState() => _GraphViewState();
|
||||
}
|
||||
|
||||
class _GraphViewState extends State<GraphView> {
|
||||
/// List of colors for the graph lines.
|
||||
List<Color> lineColors = [
|
||||
Colors.red,
|
||||
Colors.blue,
|
||||
Colors.orange.shade400,
|
||||
Colors.purple,
|
||||
Colors.green,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text(AppLocalizations.of(context).game_process),
|
||||
previousPageTitle: AppLocalizations.of(context).back,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 100, 0, 0),
|
||||
child: SfCartesianChart(
|
||||
legend:
|
||||
const Legend(isVisible: true, position: LegendPosition.bottom),
|
||||
primaryXAxis: const NumericAxis(),
|
||||
primaryYAxis: const NumericAxis(),
|
||||
series: getCumulativeScores(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a list of LineSeries representing the cumulative scores of each player.
|
||||
/// Each series contains data points for each round, showing the cumulative score up to that round.
|
||||
/// The x-axis represents the round number, and the y-axis represents the cumulative score.
|
||||
List<LineSeries<(int, int), int>> getCumulativeScores() {
|
||||
final rounds = widget.gameSession.roundList;
|
||||
final playerCount = widget.gameSession.players.length;
|
||||
final playerNames = widget.gameSession.players;
|
||||
|
||||
List<List<int>> cumulativeScores = List.generate(playerCount, (_) => []);
|
||||
List<int> runningTotals = List.filled(playerCount, 0);
|
||||
|
||||
for (var round in rounds) {
|
||||
for (int i = 0; i < playerCount; i++) {
|
||||
runningTotals[i] += round.scores[i];
|
||||
cumulativeScores[i].add(runningTotals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a list of LineSeries for each player
|
||||
/// Each series contains data points for each round
|
||||
return List.generate(playerCount, (i) {
|
||||
final data = List.generate(
|
||||
cumulativeScores[i].length,
|
||||
(j) => (j + 1, cumulativeScores[i][j]), // (round, score)
|
||||
);
|
||||
|
||||
/// Create a LineSeries for the player
|
||||
/// The xValueMapper maps the round number, and the yValueMapper maps the cumulative score.
|
||||
return LineSeries<(int, int), int>(
|
||||
name: playerNames[i],
|
||||
dataSource: data,
|
||||
xValueMapper: (record, _) => record.$1, // Runde
|
||||
yValueMapper: (record, _) => record.$2, // Punktestand
|
||||
markerSettings: const MarkerSettings(isVisible: true),
|
||||
color: lineColors[i],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:cabo_counter/data/game_manager.dart';
|
||||
import 'package:cabo_counter/l10n/app_localizations.dart';
|
||||
import 'package:cabo_counter/services/config_service.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.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/create_game_view.dart';
|
||||
import 'package:cabo_counter/views/settings_view.dart';
|
||||
@@ -50,7 +50,9 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const SettingsView(),
|
||||
),
|
||||
);
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
||||
middle: const Text('Cabo Counter'),
|
||||
@@ -59,7 +61,7 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const CreateGame(),
|
||||
builder: (context) => const CreateGameView(),
|
||||
),
|
||||
)
|
||||
},
|
||||
@@ -77,7 +79,13 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
const SizedBox(height: 30), // Abstand von oben
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() {}),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
const CreateGameView(),
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
CupertinoIcons.plus,
|
||||
size: 60,
|
||||
@@ -85,12 +93,13 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 10), // Abstand von oben
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 70),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 70),
|
||||
child: Text(
|
||||
'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.',
|
||||
'${AppLocalizations.of(context).empty_text_1}\n${AppLocalizations.of(context).empty_text_2}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -106,15 +115,15 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
key: Key(session.gameTitle),
|
||||
background: Container(
|
||||
color: CupertinoColors.destructiveRed,
|
||||
alignment: Alignment.centerLeft,
|
||||
alignment: Alignment.centerRight,
|
||||
padding:
|
||||
const EdgeInsets.only(left: 20.0),
|
||||
const EdgeInsets.only(right: 20.0),
|
||||
child: const Icon(
|
||||
CupertinoIcons.delete,
|
||||
color: CupertinoColors.white,
|
||||
),
|
||||
),
|
||||
direction: DismissDirection.startToEnd,
|
||||
direction: DismissDirection.endToStart,
|
||||
confirmDismiss: (direction) async {
|
||||
final String gameTitle = gameManager
|
||||
.gameList[index].gameTitle;
|
||||
@@ -122,7 +131,8 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
gameTitle);
|
||||
},
|
||||
onDismissed: (direction) {
|
||||
gameManager.removeGameSession(index);
|
||||
gameManager
|
||||
.removeGameSessionByIndex(index);
|
||||
},
|
||||
dismissThresholds: const {
|
||||
DismissDirection.startToEnd: 0.6
|
||||
@@ -159,18 +169,19 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
CupertinoIcons.person_2_fill),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
//ignore: unused_local_variable
|
||||
final val = await Navigator.push(
|
||||
onTap: () {
|
||||
final session =
|
||||
gameManager.gameList[index];
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
ActiveGameView(
|
||||
gameSession: gameManager
|
||||
.gameList[index]),
|
||||
gameSession: session),
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -188,7 +199,7 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
/// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'.
|
||||
String _translateGameMode(bool pointLimit) {
|
||||
if (pointLimit) {
|
||||
return '${Globals.pointLimit} ${AppLocalizations.of(context).points}';
|
||||
return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}';
|
||||
}
|
||||
return AppLocalizations.of(context).unlimited;
|
||||
}
|
||||
@@ -215,7 +226,11 @@ class _MainMenuViewState extends State<MainMenuView> {
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context).delete),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -67,6 +67,7 @@ class _RoundViewState extends State<RoundView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
final maxLength = widget.gameSession.getMaxLengthOfPlayerNames();
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
@@ -75,10 +76,8 @@ class _RoundViewState extends State<RoundView> {
|
||||
middle: Text(AppLocalizations.of(context).results),
|
||||
leading: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () => {
|
||||
LocalStorageService.saveGameSessions(),
|
||||
Navigator.pop(context, widget.gameSession)
|
||||
},
|
||||
onPressed: () =>
|
||||
{LocalStorageService.saveGameSessions(), Navigator.pop(context)},
|
||||
child: Text(AppLocalizations.of(context).cancel),
|
||||
),
|
||||
),
|
||||
@@ -122,28 +121,21 @@ class _RoundViewState extends State<RoundView> {
|
||||
index,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.gameSession
|
||||
.getLengthOfPlayerNames() >
|
||||
20
|
||||
? (widget.gameSession
|
||||
.getLengthOfPlayerNames() >
|
||||
32
|
||||
? 5
|
||||
: 10)
|
||||
: 15,
|
||||
horizontal: 4 +
|
||||
_getSegmentedControlPadding(maxLength),
|
||||
vertical: 6,
|
||||
),
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: widget.gameSession
|
||||
.getLengthOfPlayerNames() >
|
||||
28
|
||||
? 14
|
||||
: 18,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: _getSegmentedControlFontSize(
|
||||
maxLength),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -191,7 +183,13 @@ class _RoundViewState extends State<RoundView> {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CupertinoListTile(
|
||||
backgroundColor: CupertinoColors.secondaryLabel,
|
||||
title: Row(children: [Text(name)]),
|
||||
title: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
]),
|
||||
subtitle: Text(
|
||||
'${widget.gameSession.playerScores[index]}'
|
||||
' ${AppLocalizations.of(context).points}'),
|
||||
@@ -290,7 +288,7 @@ class _RoundViewState extends State<RoundView> {
|
||||
? () {
|
||||
_finishRound();
|
||||
LocalStorageService.saveGameSessions();
|
||||
Navigator.pop(context, widget.gameSession);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
child: Text(AppLocalizations.of(context).done),
|
||||
@@ -301,17 +299,10 @@ class _RoundViewState extends State<RoundView> {
|
||||
_finishRound();
|
||||
LocalStorageService.saveGameSessions();
|
||||
if (widget.gameSession.isGameFinished == true) {
|
||||
Navigator.pop(context, widget.gameSession);
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pushReplacement(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => RoundView(
|
||||
gameSession: widget.gameSession,
|
||||
roundNumber: widget.roundNumber + 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
Navigator.pop(
|
||||
context, widget.roundNumber + 1);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
@@ -395,6 +386,32 @@ class _RoundViewState extends State<RoundView> {
|
||||
}
|
||||
}
|
||||
|
||||
double _getSegmentedControlFontSize(int maxLength) {
|
||||
if (maxLength > 8) {
|
||||
// 9 - 12 characters
|
||||
return 9.0;
|
||||
} else if (maxLength > 4) {
|
||||
// 5 - 8 characters
|
||||
return 15.0;
|
||||
} else {
|
||||
// 0 - 4 characters
|
||||
return 18.0;
|
||||
}
|
||||
}
|
||||
|
||||
double _getSegmentedControlPadding(int maxLength) {
|
||||
if (maxLength > 8) {
|
||||
// 9 - 12 characters
|
||||
return 0.0;
|
||||
} else if (maxLength > 4) {
|
||||
// 5 - 8 characters
|
||||
return 5.0;
|
||||
} else {
|
||||
// 0 - 4 characters
|
||||
return 8.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in _scoreControllerList) {
|
||||
|
||||
@@ -52,14 +52,14 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
AppLocalizations.of(context).cabo_penalty_subtitle),
|
||||
trailing: Stepper(
|
||||
key: _stepperKey1,
|
||||
initialValue: Globals.caboPenalty,
|
||||
initialValue: ConfigService.caboPenalty,
|
||||
minValue: 0,
|
||||
maxValue: 50,
|
||||
step: 1,
|
||||
onChanged: (newCaboPenalty) {
|
||||
setState(() {
|
||||
ConfigService.setCaboPenalty(newCaboPenalty);
|
||||
Globals.caboPenalty = newCaboPenalty;
|
||||
ConfigService.caboPenalty = newCaboPenalty;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -73,14 +73,14 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
Text(AppLocalizations.of(context).point_limit_subtitle),
|
||||
trailing: Stepper(
|
||||
key: _stepperKey2,
|
||||
initialValue: Globals.pointLimit,
|
||||
initialValue: ConfigService.pointLimit,
|
||||
minValue: 30,
|
||||
maxValue: 1000,
|
||||
step: 10,
|
||||
onChanged: (newPointLimit) {
|
||||
setState(() {
|
||||
ConfigService.setPointLimit(newPointLimit);
|
||||
Globals.pointLimit = newPointLimit;
|
||||
ConfigService.pointLimit = newPointLimit;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -125,27 +125,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
onPressed: () async {
|
||||
final success =
|
||||
await LocalStorageService.importJsonFile();
|
||||
if (!success && context.mounted) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)
|
||||
.error),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)
|
||||
.error_import),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)
|
||||
.ok),
|
||||
onPressed: () =>
|
||||
Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
showFeedbackDialog(success);
|
||||
}),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
@@ -160,15 +140,15 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
onPressed: () async {
|
||||
final success =
|
||||
await LocalStorageService.exportJsonFile();
|
||||
await LocalStorageService.exportGameData();
|
||||
if (!success && context.mounted) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title:
|
||||
Text(AppLocalizations.of(context).error),
|
||||
title: Text(AppLocalizations.of(context)
|
||||
.export_error_title),
|
||||
content: Text(AppLocalizations.of(context)
|
||||
.error_export),
|
||||
.export_error_message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child:
|
||||
@@ -236,4 +216,52 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
Future<PackageInfo> _getPackageInfo() async {
|
||||
return await PackageInfo.fromPlatform();
|
||||
}
|
||||
|
||||
void showFeedbackDialog(ImportStatus status) {
|
||||
if (status == ImportStatus.canceled) return;
|
||||
final (title, message) = _getDialogContent(status);
|
||||
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(AppLocalizations.of(context).ok),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
(String, String) _getDialogContent(ImportStatus status) {
|
||||
switch (status) {
|
||||
case ImportStatus.success:
|
||||
return (
|
||||
AppLocalizations.of(context).import_success_title,
|
||||
AppLocalizations.of(context).import_success_message
|
||||
);
|
||||
case ImportStatus.validationError:
|
||||
return (
|
||||
AppLocalizations.of(context).import_validation_error_title,
|
||||
AppLocalizations.of(context).import_validation_error_message
|
||||
);
|
||||
|
||||
case ImportStatus.formatError:
|
||||
return (
|
||||
AppLocalizations.of(context).import_format_error_title,
|
||||
AppLocalizations.of(context).import_format_error_message
|
||||
);
|
||||
case ImportStatus.genericError:
|
||||
return (
|
||||
AppLocalizations.of(context).import_generic_error_title,
|
||||
AppLocalizations.of(context).import_generic_error_message
|
||||
);
|
||||
case ImportStatus.canceled:
|
||||
return ('', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ name: cabo_counter
|
||||
description: "Mobile app for the card game Cabo"
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.3.0+232
|
||||
version: 0.3.9+331
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
@@ -25,6 +25,8 @@ dependencies:
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
syncfusion_flutter_charts: ^30.1.37
|
||||
uuid: ^4.5.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -37,4 +39,5 @@ flutter:
|
||||
uses-material-design: false
|
||||
assets:
|
||||
- assets/cabo_counter-logo_rounded.png
|
||||
- assets/schema.json
|
||||
- assets/game_list-schema.json
|
||||
- assets/game-schema.json
|
||||
|
||||
@@ -61,9 +61,8 @@ void main() {
|
||||
});
|
||||
|
||||
group('Helper Functions', () {
|
||||
test('getLengthOfPlayerNames', () {
|
||||
expect(session.getLengthOfPlayerNames(),
|
||||
equals(15)); // Alice(5) + Bob(3) + Charlie(7)
|
||||
test('getMaxLengthOfPlayerNames', () {
|
||||
expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie (7)
|
||||
});
|
||||
|
||||
test('increaseRound', () {
|
||||
|
||||
Reference in New Issue
Block a user