@@ -1,8 +1,8 @@
|
|||||||
# CABO Counter
|
# 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#",
|
"$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",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string"
|
"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 = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
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
|
notifyListeners(); // Propagate session changes
|
||||||
});
|
});
|
||||||
gameList.add(session);
|
gameList.add(session);
|
||||||
print(
|
|
||||||
'[game_manager.dart] Added game session: ${session.gameTitle} at ${session.createdAt}');
|
|
||||||
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
print(
|
|
||||||
'[game_manager.dart] Sorted game sessions by creation date. Total sessions: ${gameList.length}');
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await LocalStorageService.saveGameSessions();
|
await LocalStorageService.saveGameSessions();
|
||||||
print('[game_manager.dart] Saved game sessions to local storage.');
|
|
||||||
return gameList.indexOf(session);
|
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`,
|
/// 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.
|
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
|
||||||
/// It also saves the updated game sessions to local storage.
|
/// It also saves the updated game sessions to local storage.
|
||||||
void removeGameSession(int index) {
|
void removeGameSessionByIndex(int index) {
|
||||||
gameList[index].removeListener(notifyListeners);
|
gameList[index].removeListener(notifyListeners);
|
||||||
gameList.removeAt(index);
|
gameList.removeAt(index);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
LocalStorageService.saveGameSessions();
|
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();
|
final gameManager = GameManager();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:cabo_counter/data/round.dart';
|
import 'package:cabo_counter/data/round.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:uuid/uuid.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.
|
||||||
@@ -12,6 +13,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
/// [isGameFinished] is a boolean indicating if the game has ended yet.
|
/// [isGameFinished] is a boolean indicating if the game has ended yet.
|
||||||
/// [winner] is the name of the player who won the game.
|
/// [winner] is the name of the player who won the game.
|
||||||
class GameSession extends ChangeNotifier {
|
class GameSession extends ChangeNotifier {
|
||||||
|
late String id;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final String gameTitle;
|
final String gameTitle;
|
||||||
final List<String> players;
|
final List<String> players;
|
||||||
@@ -33,17 +35,20 @@ class GameSession extends ChangeNotifier {
|
|||||||
required this.isPointsLimitEnabled,
|
required this.isPointsLimitEnabled,
|
||||||
}) {
|
}) {
|
||||||
playerScores = List.filled(players.length, 0);
|
playerScores = List.filled(players.length, 0);
|
||||||
|
var uuid = const Uuid();
|
||||||
|
id = uuid.v1();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
toString() {
|
toString() {
|
||||||
return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, '
|
return ('GameSession: [id: $id, createdAt: $createdAt, gameTitle: $gameTitle, '
|
||||||
'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,'
|
'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,'
|
||||||
' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]');
|
' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the GameSession object to a JSON map.
|
/// Converts the GameSession object to a JSON map.
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
'createdAt': createdAt.toIso8601String(),
|
'createdAt': createdAt.toIso8601String(),
|
||||||
'gameTitle': gameTitle,
|
'gameTitle': gameTitle,
|
||||||
'players': players,
|
'players': players,
|
||||||
@@ -59,7 +64,8 @@ class GameSession extends ChangeNotifier {
|
|||||||
|
|
||||||
/// Creates a GameSession object from a JSON map.
|
/// Creates a GameSession object from a JSON map.
|
||||||
GameSession.fromJson(Map<String, dynamic> json)
|
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'],
|
gameTitle = json['gameTitle'],
|
||||||
players = List<String>.from(json['players']),
|
players = List<String>.from(json['players']),
|
||||||
pointLimit = json['pointLimit'],
|
pointLimit = json['pointLimit'],
|
||||||
@@ -72,11 +78,13 @@ class GameSession extends ChangeNotifier {
|
|||||||
roundList =
|
roundList =
|
||||||
(json['roundList'] as List).map((e) => Round.fromJson(e)).toList();
|
(json['roundList'] as List).map((e) => Round.fromJson(e)).toList();
|
||||||
|
|
||||||
/// Returns the length of all player names combined.
|
/// Returns the length of the longest player name.
|
||||||
int getLengthOfPlayerNames() {
|
int getMaxLengthOfPlayerNames() {
|
||||||
int length = 0;
|
int length = 0;
|
||||||
for (String player in players) {
|
for (String player in players) {
|
||||||
length += player.length;
|
if (player.length >= length) {
|
||||||
|
length = player.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"player": "Spieler:in",
|
"player": "Spieler:in",
|
||||||
"players": "Spieler:innen",
|
"players": "Spieler:innen",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"back": "Zurück",
|
||||||
|
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"about": "Über",
|
"about": "Über",
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
"empty_text_1": "Ganz schön leer hier...",
|
"empty_text_1": "Ganz schön leer hier...",
|
||||||
"empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu",
|
"empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu",
|
||||||
"delete_game_title": "Spiel löschen?",
|
"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": {
|
"@delete_game_message": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"gameTitle": {
|
"gameTitle": {
|
||||||
@@ -63,6 +64,18 @@
|
|||||||
"done": "Fertig",
|
"done": "Fertig",
|
||||||
"next_round": "Nächste Runde",
|
"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",
|
"settings": "Einstellungen",
|
||||||
"cabo_penalty": "Cabo-Strafe",
|
"cabo_penalty": "Cabo-Strafe",
|
||||||
"cabo_penalty_subtitle": "... für falsches Cabo sagen",
|
"cabo_penalty_subtitle": "... für falsches Cabo sagen",
|
||||||
@@ -72,9 +85,18 @@
|
|||||||
"game_data": "Spieldaten",
|
"game_data": "Spieldaten",
|
||||||
"import_data": "Daten importieren",
|
"import_data": "Daten importieren",
|
||||||
"export_data": "Daten exportieren",
|
"export_data": "Daten exportieren",
|
||||||
"error": "Fehler",
|
|
||||||
"error_import": "Datei konnte nicht importiert werden",
|
"import_success_title": "Import erfolgreich",
|
||||||
"error_export": "Datei konnte nicht exportiert werden",
|
"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?",
|
"error_found": "Fehler gefunden?",
|
||||||
"create_issue": "Issue erstellen",
|
"create_issue": "Issue erstellen",
|
||||||
"app_version": "App-Version",
|
"app_version": "App-Version",
|
||||||
|
|||||||
@@ -14,14 +14,15 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"players": "Players",
|
"players": "Players",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"back": "Back",
|
||||||
|
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
|
||||||
"empty_text_1": "Pretty empty here...",
|
"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_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": {
|
"@delete_game_message": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"gameTitle": {
|
"gameTitle": {
|
||||||
@@ -63,22 +64,44 @@
|
|||||||
"done": "Done",
|
"done": "Done",
|
||||||
"next_round": "Next Round",
|
"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",
|
"settings": "Settings",
|
||||||
"cabo_penalty": "Cabo Penalty",
|
"cabo_penalty": "Cabo Penalty",
|
||||||
"cabo_penalty_subtitle": "... for falsely calling Cabo",
|
"cabo_penalty_subtitle": "... for falsely calling Cabo.",
|
||||||
"point_limit": "Point Limit",
|
"point_limit": "Point Limit",
|
||||||
"point_limit_subtitle": "... the game ends here",
|
"point_limit_subtitle": "... the game ends here.",
|
||||||
"reset_to_default": "Reset to Default",
|
"reset_to_default": "Reset to Default",
|
||||||
"game_data": "Game Data",
|
"game_data": "Game Data",
|
||||||
"import_data": "Import Data",
|
"import_data": "Import Data",
|
||||||
"export_data": "Export Data",
|
"export_data": "Export Data",
|
||||||
"error": "Error",
|
"id_error_title": "ID Error",
|
||||||
"error_import": "Could not import file",
|
"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.",
|
||||||
"error_export": "Could not export file",
|
"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?",
|
"error_found": "Found a bug?",
|
||||||
"create_issue": "Create Issue",
|
"create_issue": "Create Issue",
|
||||||
"app_version": "App Version",
|
"app_version": "App Version",
|
||||||
"load_version": "Loading version...",
|
"load_version": "Loading version...",
|
||||||
"build": "Build",
|
"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'**
|
/// **'Name'**
|
||||||
String get 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.
|
/// No description provided for @home.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
@@ -209,7 +215,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @delete_game_message.
|
/// No description provided for @delete_game_message.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// 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);
|
String delete_game_message(String gameTitle);
|
||||||
|
|
||||||
/// No description provided for @overview.
|
/// No description provided for @overview.
|
||||||
@@ -356,6 +362,66 @@ abstract class AppLocalizations {
|
|||||||
/// **'Nächste Runde'**
|
/// **'Nächste Runde'**
|
||||||
String get next_round;
|
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.
|
/// No description provided for @settings.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
@@ -410,23 +476,65 @@ abstract class AppLocalizations {
|
|||||||
/// **'Daten exportieren'**
|
/// **'Daten exportieren'**
|
||||||
String get export_data;
|
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:
|
/// In de, this message translates to:
|
||||||
/// **'Fehler'**
|
/// **'Fehler'**
|
||||||
String get error;
|
String get export_error_title;
|
||||||
|
|
||||||
/// No description provided for @error_import.
|
/// No description provided for @export_error_message.
|
||||||
///
|
|
||||||
/// In de, this message translates to:
|
|
||||||
/// **'Datei konnte nicht importiert werden'**
|
|
||||||
String get error_import;
|
|
||||||
|
|
||||||
/// No description provided for @error_export.
|
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Datei konnte nicht exportiert werden'**
|
/// **'Datei konnte nicht exportiert werden'**
|
||||||
String get error_export;
|
String get export_error_message;
|
||||||
|
|
||||||
/// No description provided for @error_found.
|
/// No description provided for @error_found.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get name => 'Name';
|
String get name => 'Name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get back => 'Zurück';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get home => 'Home';
|
String get home => 'Home';
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String delete_game_message(String gameTitle) {
|
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
|
@override
|
||||||
@@ -146,6 +149,38 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get next_round => 'Nächste Runde';
|
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
|
@override
|
||||||
String get settings => 'Einstellungen';
|
String get settings => 'Einstellungen';
|
||||||
|
|
||||||
@@ -174,13 +209,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get export_data => 'Daten exportieren';
|
String get export_data => 'Daten exportieren';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get error => 'Fehler';
|
String get import_success_title => 'Import erfolgreich';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get error_import => 'Datei konnte nicht importiert werden';
|
String get import_success_message =>
|
||||||
|
'Die Spieldaten wurden erfolgreich importiert.';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get error_found => 'Fehler gefunden?';
|
String get error_found => 'Fehler gefunden?';
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get name => 'Name';
|
String get name => 'Name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get back => 'Back';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get home => 'Home';
|
String get home => 'Home';
|
||||||
|
|
||||||
@@ -58,14 +61,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get empty_text_2 =>
|
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
|
@override
|
||||||
String get delete_game_title => 'Delete game?';
|
String get delete_game_title => 'Delete game?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String delete_game_message(String gameTitle) {
|
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
|
@override
|
||||||
@@ -143,6 +146,38 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get next_round => 'Next Round';
|
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
|
@override
|
||||||
String get settings => 'Settings';
|
String get settings => 'Settings';
|
||||||
|
|
||||||
@@ -150,13 +185,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get cabo_penalty => 'Cabo Penalty';
|
String get cabo_penalty => 'Cabo Penalty';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get cabo_penalty_subtitle => '... for falsely calling Cabo';
|
String get cabo_penalty_subtitle => '... for falsely calling Cabo.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get point_limit => 'Point Limit';
|
String get point_limit => 'Point Limit';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get point_limit_subtitle => '... the game ends here';
|
String get point_limit_subtitle => '... the game ends here.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get reset_to_default => 'Reset to Default';
|
String get reset_to_default => 'Reset to Default';
|
||||||
@@ -171,13 +206,37 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get export_data => 'Export Data';
|
String get export_data => 'Export Data';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get error => 'Error';
|
String get import_success_title => 'Import successful';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get error_import => 'Could not import file';
|
String get import_success_message =>
|
||||||
|
'The game data has been successfully imported.';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get error_found => 'Found a bug?';
|
String get error_found => 'Found a bug?';
|
||||||
@@ -196,5 +255,5 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get about_text =>
|
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/config_service.dart';
|
||||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||||
import 'package:cabo_counter/utility/custom_theme.dart';
|
import 'package:cabo_counter/utility/custom_theme.dart';
|
||||||
import 'package:cabo_counter/utility/globals.dart';
|
|
||||||
import 'package:cabo_counter/views/tab_view.dart';
|
import 'package:cabo_counter/views/tab_view.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -12,8 +11,8 @@ Future<void> main() async {
|
|||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations(
|
||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||||
await ConfigService.initConfig();
|
await ConfigService.initConfig();
|
||||||
Globals.pointLimit = await ConfigService.getPointLimit();
|
ConfigService.pointLimit = await ConfigService.getPointLimit();
|
||||||
Globals.caboPenalty = await ConfigService.getCaboPenalty();
|
ConfigService.caboPenalty = await ConfigService.getCaboPenalty();
|
||||||
runApp(const App());
|
runApp(const App());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +55,16 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
Locale('en'), // English
|
Locale('en'), // English
|
||||||
Locale('de'), // German
|
Locale('de'), // German
|
||||||
],
|
],
|
||||||
|
localeResolutionCallback: (locale, supportedLocales) {
|
||||||
|
for (final supportedLocale in supportedLocales) {
|
||||||
|
if (supportedLocale.languageCode == locale?.languageCode) {
|
||||||
|
return supportedLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supportedLocales.first;
|
||||||
|
},
|
||||||
theme: CupertinoThemeData(
|
theme: CupertinoThemeData(
|
||||||
|
applyThemeToAll: true,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primaryColor: CustomTheme.primaryColor,
|
primaryColor: CustomTheme.primaryColor,
|
||||||
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:cabo_counter/utility/globals.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// This class handles the configuration settings for the app.
|
/// This class handles the configuration settings for the app.
|
||||||
@@ -7,8 +6,12 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
class ConfigService {
|
class ConfigService {
|
||||||
static const String _keyPointLimit = 'pointLimit';
|
static const String _keyPointLimit = 'pointLimit';
|
||||||
static const String _keyCaboPenalty = 'caboPenalty';
|
static const String _keyCaboPenalty = 'caboPenalty';
|
||||||
static const int _defaultPointLimit = 100; // Default Value
|
// Actual values used in the app
|
||||||
static const int _defaultCaboPenalty = 5; // Default Value
|
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 {
|
static Future<void> initConfig() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -48,8 +51,8 @@ class ConfigService {
|
|||||||
|
|
||||||
/// Resets the configuration to default values.
|
/// Resets the configuration to default values.
|
||||||
static Future<void> resetConfig() async {
|
static Future<void> resetConfig() async {
|
||||||
Globals.pointLimit = _defaultPointLimit;
|
ConfigService.pointLimit = _defaultPointLimit;
|
||||||
Globals.caboPenalty = _defaultCaboPenalty;
|
ConfigService.caboPenalty = _defaultCaboPenalty;
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setInt(_keyPointLimit, _defaultPointLimit);
|
await prefs.setInt(_keyPointLimit, _defaultPointLimit);
|
||||||
await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty);
|
await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty);
|
||||||
|
|||||||
@@ -9,11 +9,19 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:json_schema/json_schema.dart';
|
import 'package:json_schema/json_schema.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
enum ImportStatus {
|
||||||
|
success,
|
||||||
|
canceled,
|
||||||
|
validationError,
|
||||||
|
formatError,
|
||||||
|
genericError
|
||||||
|
}
|
||||||
|
|
||||||
class LocalStorageService {
|
class LocalStorageService {
|
||||||
static const String _fileName = 'game_data.json';
|
static const String _fileName = 'game_data.json';
|
||||||
|
|
||||||
/// Writes the game session list to a JSON file and returns it as string.
|
/// Writes the game session list to a JSON file and returns it as string.
|
||||||
static String getJsonFile() {
|
static String getGameDataAsJsonFile() {
|
||||||
final jsonFile =
|
final jsonFile =
|
||||||
gameManager.gameList.map((session) => session.toJson()).toList();
|
gameManager.gameList.map((session) => session.toJson()).toList();
|
||||||
return json.encode(jsonFile);
|
return json.encode(jsonFile);
|
||||||
@@ -31,7 +39,7 @@ class LocalStorageService {
|
|||||||
print('[local_storage_service.dart] Versuche, Daten zu speichern...');
|
print('[local_storage_service.dart] Versuche, Daten zu speichern...');
|
||||||
try {
|
try {
|
||||||
final file = await _getFilePath();
|
final file = await _getFilePath();
|
||||||
final jsonFile = getJsonFile();
|
final jsonFile = getGameDataAsJsonFile();
|
||||||
await file.writeAsString(jsonFile);
|
await file.writeAsString(jsonFile);
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.');
|
'[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.');
|
||||||
@@ -62,7 +70,7 @@ class LocalStorageService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await validateJsonSchema(jsonString)) {
|
if (!await validateJsonSchema(jsonString, true)) {
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Die Datei konnte nicht validiert werden');
|
'[local_storage_service.dart] Die Datei konnte nicht validiert werden');
|
||||||
gameManager.gameList = [];
|
gameManager.gameList = [];
|
||||||
@@ -78,6 +86,11 @@ class LocalStorageService {
|
|||||||
GameSession.fromJson(jsonItem as Map<String, dynamic>))
|
GameSession.fromJson(jsonItem as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
for (GameSession session in gameManager.gameList) {
|
||||||
|
print(
|
||||||
|
'[local_storage_service.dart] Geladene Session: ${session.gameTitle} - ${session.id}');
|
||||||
|
}
|
||||||
|
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Die Spieldaten wurden erfolgreich geladen und verarbeitet');
|
'[local_storage_service.dart] Die Spieldaten wurden erfolgreich geladen und verarbeitet');
|
||||||
return true;
|
return true;
|
||||||
@@ -89,19 +102,27 @@ class LocalStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens the file picker to save a JSON file with the current game data.
|
/// Opens the file picker to export game data as a JSON file.
|
||||||
static Future<bool> exportJsonFile() async {
|
/// This method will export the given [jsonString] as a JSON file. It opens
|
||||||
final jsonString = getJsonFile();
|
/// the file picker with the choosen [fileName].
|
||||||
|
static Future<bool> exportJsonData(
|
||||||
|
String jsonString,
|
||||||
|
String fileName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
||||||
final result = await FileSaver.instance.saveAs(
|
final path = await FileSaver.instance.saveAs(
|
||||||
name: 'cabo_counter_data',
|
name: fileName,
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
ext: 'json',
|
ext: 'json',
|
||||||
mimeType: MimeType.json,
|
mimeType: MimeType.json,
|
||||||
);
|
);
|
||||||
print(
|
if (path == null) {
|
||||||
'[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $result');
|
print('[local_storage_service.dart]: Export abgebrochen');
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'[local_storage_service.dart] Die Spieldaten wurden exportiert. Dateipfad: $path');
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(
|
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.
|
/// Opens the file picker to import a JSON file and loads the game data from it.
|
||||||
static Future<bool> importJsonFile() async {
|
static Future<ImportStatus> importJsonFile() async {
|
||||||
final result = await FilePicker.platform.pickFiles(
|
final path = await FilePicker.platform.pickFiles(
|
||||||
dialogTitle: 'Wähle eine Datei mit Spieldaten aus',
|
dialogTitle: 'Wähle eine Datei mit Spieldaten aus',
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['json'],
|
allowedExtensions: ['json'],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == null) {
|
if (path == null) {
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen');
|
'[local_storage_service.dart] Der Filepicker-Dialog wurde abgebrochen');
|
||||||
return false;
|
return ImportStatus.canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonString = await _readFileContent(result.files.single);
|
final jsonString = await _readFileContent(path.files.single);
|
||||||
|
|
||||||
if (!await validateJsonSchema(jsonString)) {
|
if (await validateJsonSchema(jsonString, true)) {
|
||||||
return false;
|
// 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(
|
print(
|
||||||
'[local_storage_service.dart] Die Datei wurde erfolgreich Importiertn');
|
'[local_storage_service.dart] Die Datei wurde erfolgreich Importiert');
|
||||||
return true;
|
await saveGameSessions();
|
||||||
|
return ImportStatus.success;
|
||||||
} on FormatException catch (e) {
|
} on FormatException catch (e) {
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Ungültiges JSON-Format. Exception: $e');
|
'[local_storage_service.dart] Ungültiges JSON-Format. Exception: $e');
|
||||||
return false;
|
return ImportStatus.formatError;
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
print(
|
print(
|
||||||
'[local_storage_service.dart] Fehler beim Dateizugriff. Exception: $e');
|
'[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
|
/// Helper method to read file content from either bytes or path
|
||||||
static Future<String> _readFileContent(PlatformFile file) async {
|
static Future<String> _readFileContent(PlatformFile file) async {
|
||||||
if (file.bytes != null) return utf8.decode(file.bytes!);
|
if (file.bytes != null) return utf8.decode(file.bytes!);
|
||||||
@@ -158,15 +218,28 @@ class LocalStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validates the JSON data against the schema.
|
/// 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 {
|
try {
|
||||||
final schemaString = await rootBundle.loadString('assets/schema.json');
|
|
||||||
final schema = JsonSchema.create(json.decode(schemaString));
|
final schema = JsonSchema.create(json.decode(schemaString));
|
||||||
final jsonData = json.decode(jsonString);
|
final jsonData = json.decode(jsonString);
|
||||||
final result = schema.validate(jsonData);
|
final result = schema.validate(jsonData);
|
||||||
|
|
||||||
if (result.isValid) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
class Globals {
|
class Globals {
|
||||||
static int pointLimit = 100;
|
|
||||||
static int caboPenalty = 5;
|
|
||||||
static String appDevPhase = 'Beta';
|
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/data/game_session.dart';
|
||||||
import 'package:cabo_counter/l10n/app_localizations.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/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:cabo_counter/views/round_view.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ActiveGameView extends StatefulWidget {
|
class ActiveGameView extends StatefulWidget {
|
||||||
final GameSession gameSession;
|
final GameSession gameSession;
|
||||||
@@ -14,15 +19,23 @@ class ActiveGameView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ActiveGameViewState extends State<ActiveGameView> {
|
class _ActiveGameViewState extends State<ActiveGameView> {
|
||||||
|
late final GameSession gameSession;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
gameSession = widget.gameSession;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: widget.gameSession,
|
listenable: gameSession,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
|
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text(widget.gameSession.gameTitle),
|
middle: Text(gameSession.gameTitle),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -39,7 +52,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: widget.gameSession.players.length,
|
itemCount: gameSession.players.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
int playerIndex = sortedPlayerIndices[index];
|
int playerIndex = sortedPlayerIndices[index];
|
||||||
return CupertinoListTile(
|
return CupertinoListTile(
|
||||||
@@ -48,7 +61,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
_getPlacementPrefix(index),
|
_getPlacementPrefix(index),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
widget.gameSession.players[playerIndex],
|
gameSession.players[playerIndex],
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
@@ -57,8 +70,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
trailing: Row(
|
trailing: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text('${gameSession.playerScores[playerIndex]} '
|
||||||
'${widget.gameSession.playerScores[playerIndex]} '
|
|
||||||
'${AppLocalizations.of(context).points}')
|
'${AppLocalizations.of(context).points}')
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -75,38 +87,133 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: widget.gameSession.roundNumber,
|
itemCount: gameSession.roundNumber,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(1),
|
padding: const EdgeInsets.all(1),
|
||||||
child: CupertinoListTile(
|
child: CupertinoListTile(
|
||||||
|
backgroundColorActivated:
|
||||||
|
CustomTheme.backgroundColor,
|
||||||
title: Text(
|
title: Text(
|
||||||
'${AppLocalizations.of(context).round} ${index + 1}',
|
'${AppLocalizations.of(context).round} ${index + 1}',
|
||||||
),
|
),
|
||||||
trailing: index + 1 !=
|
trailing:
|
||||||
widget.gameSession.roundNumber ||
|
index + 1 != gameSession.roundNumber ||
|
||||||
widget.gameSession.isGameFinished ==
|
gameSession.isGameFinished == true
|
||||||
true
|
? (const Text('\u{2705}',
|
||||||
? (const Text('\u{2705}',
|
style: TextStyle(fontSize: 22)))
|
||||||
style: TextStyle(fontSize: 22)))
|
: const Text('\u{23F3}',
|
||||||
: const Text('\u{23F3}',
|
style: TextStyle(fontSize: 22)),
|
||||||
style: TextStyle(fontSize: 22)),
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// ignore: unused_local_variable
|
_openRoundView(index + 1);
|
||||||
final val = await Navigator.of(context,
|
|
||||||
rootNavigator: true)
|
|
||||||
.push(
|
|
||||||
CupertinoPageRoute(
|
|
||||||
fullscreenDialog: true,
|
|
||||||
builder: (context) => RoundView(
|
|
||||||
gameSession: widget.gameSession,
|
|
||||||
roundNumber: 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
|
/// Returns a list of player indices sorted by their scores in
|
||||||
/// ascending order.
|
/// ascending order.
|
||||||
List<int> _getSortedPlayerIndices() {
|
List<int> _getSortedPlayerIndices() {
|
||||||
List<int> playerIndices =
|
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
|
// Sort the indices based on the summed points
|
||||||
playerIndices.sort((a, b) {
|
playerIndices.sort((a, b) {
|
||||||
int scoreA = widget.gameSession.playerScores[a];
|
int scoreA = gameSession.playerScores[a];
|
||||||
int scoreB = widget.gameSession.playerScores[b];
|
int scoreB = gameSession.playerScores[b];
|
||||||
return scoreA.compareTo(scoreB);
|
return scoreA.compareTo(scoreB);
|
||||||
});
|
});
|
||||||
return playerIndices;
|
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_manager.dart';
|
||||||
import 'package:cabo_counter/data/game_session.dart';
|
import 'package:cabo_counter/data/game_session.dart';
|
||||||
import 'package:cabo_counter/l10n/app_localizations.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/custom_theme.dart';
|
||||||
import 'package:cabo_counter/utility/globals.dart';
|
|
||||||
import 'package:cabo_counter/views/active_game_view.dart';
|
import 'package:cabo_counter/views/active_game_view.dart';
|
||||||
import 'package:cabo_counter/views/mode_selection_view.dart';
|
import 'package:cabo_counter/views/mode_selection_view.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
class CreateGame extends StatefulWidget {
|
enum CreateStatus {
|
||||||
const CreateGame({super.key});
|
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
|
@override
|
||||||
// ignore: library_private_types_in_public_api
|
// 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 = [
|
final List<TextEditingController> _playerNameTextControllers = [
|
||||||
TextEditingController()
|
TextEditingController()
|
||||||
];
|
];
|
||||||
@@ -25,8 +42,23 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
/// Maximum number of players allowed in the game.
|
/// Maximum number of players allowed in the game.
|
||||||
final int maxPlayers = 5;
|
final int maxPlayers = 5;
|
||||||
|
|
||||||
/// Variable to store the selected game mode.
|
/// Variable to store whether the points limit feature is enabled.
|
||||||
bool? selectedMode;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -69,10 +101,10 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
suffix: Row(
|
suffix: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
selectedMode == null
|
_isPointsLimitEnabled == null
|
||||||
? AppLocalizations.of(context).select_mode
|
? AppLocalizations.of(context).select_mode
|
||||||
: (selectedMode!
|
: (_isPointsLimitEnabled!
|
||||||
? '${Globals.pointLimit} ${AppLocalizations.of(context).points}'
|
? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}'
|
||||||
: AppLocalizations.of(context).unlimited),
|
: AppLocalizations.of(context).unlimited),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
@@ -80,18 +112,18 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final selected = await Navigator.push(
|
final selectedMode = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) => ModeSelectionMenu(
|
builder: (context) => ModeSelectionMenu(
|
||||||
pointLimit: Globals.pointLimit,
|
pointLimit: ConfigService.pointLimit,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selected != null) {
|
if (selectedMode != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedMode = selected;
|
_isPointsLimitEnabled = selectedMode;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -139,22 +171,7 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
.add(TextEditingController());
|
.add(TextEditingController());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showCupertinoDialog(
|
showFeedbackDialog(CreateStatus.maxPlayers);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -183,6 +200,7 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CupertinoTextField(
|
child: CupertinoTextField(
|
||||||
controller: _playerNameTextControllers[index],
|
controller: _playerNameTextControllers[index],
|
||||||
|
maxLength: 12,
|
||||||
placeholder:
|
placeholder:
|
||||||
'${AppLocalizations.of(context).player} ${index + 1}',
|
'${AppLocalizations.of(context).player} ${index + 1}',
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
@@ -212,73 +230,19 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_gameTitleTextController.text == '') {
|
if (_gameTitleTextController.text == '') {
|
||||||
showCupertinoDialog(
|
showFeedbackDialog(CreateStatus.noGameTitle);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedMode == null) {
|
if (_isPointsLimitEnabled == null) {
|
||||||
showCupertinoDialog(
|
showFeedbackDialog(CreateStatus.noModeSelected);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_playerNameTextControllers.length < 2) {
|
if (_playerNameTextControllers.length < 2) {
|
||||||
showCupertinoDialog(
|
showFeedbackDialog(CreateStatus.minPlayers);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!everyPlayerHasAName()) {
|
if (!everyPlayerHasAName()) {
|
||||||
showCupertinoDialog(
|
showFeedbackDialog(CreateStatus.noPlayerName);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,17 +254,18 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
gameTitle: _gameTitleTextController.text,
|
gameTitle: _gameTitleTextController.text,
|
||||||
players: players,
|
players: players,
|
||||||
pointLimit: Globals.pointLimit,
|
pointLimit: ConfigService.pointLimit,
|
||||||
caboPenalty: Globals.caboPenalty,
|
caboPenalty: ConfigService.caboPenalty,
|
||||||
isPointsLimitEnabled: selectedMode!,
|
isPointsLimitEnabled: _isPointsLimitEnabled!,
|
||||||
);
|
);
|
||||||
final index = await gameManager.addGameSession(gameSession);
|
final index = await gameManager.addGameSession(gameSession);
|
||||||
|
final session = gameManager.gameList[index];
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) => ActiveGameView(
|
builder: (context) =>
|
||||||
gameSession: gameManager.gameList[index])));
|
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() {
|
bool everyPlayerHasAName() {
|
||||||
for (var controller in _playerNameTextControllers) {
|
for (var controller in _playerNameTextControllers) {
|
||||||
if (controller.text == '') {
|
if (controller.text == '') {
|
||||||
@@ -320,9 +339,11 @@ class _CreateGameState extends State<CreateGame> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_gameTitleTextController.dispose();
|
||||||
for (var controller in _playerNameTextControllers) {
|
for (var controller in _playerNameTextControllers) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.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/data/game_manager.dart';
|
||||||
import 'package:cabo_counter/l10n/app_localizations.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/services/local_storage_service.dart';
|
||||||
import 'package:cabo_counter/utility/custom_theme.dart';
|
import 'package:cabo_counter/utility/custom_theme.dart';
|
||||||
import 'package:cabo_counter/utility/globals.dart';
|
|
||||||
import 'package:cabo_counter/views/active_game_view.dart';
|
import 'package:cabo_counter/views/active_game_view.dart';
|
||||||
import 'package:cabo_counter/views/create_game_view.dart';
|
import 'package:cabo_counter/views/create_game_view.dart';
|
||||||
import 'package:cabo_counter/views/settings_view.dart';
|
import 'package:cabo_counter/views/settings_view.dart';
|
||||||
@@ -50,7 +50,9 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) => const SettingsView(),
|
builder: (context) => const SettingsView(),
|
||||||
),
|
),
|
||||||
);
|
).then((_) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
||||||
middle: const Text('Cabo Counter'),
|
middle: const Text('Cabo Counter'),
|
||||||
@@ -59,7 +61,7 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
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
|
const SizedBox(height: 30), // Abstand von oben
|
||||||
Center(
|
Center(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => setState(() {}),
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
const CreateGameView(),
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
CupertinoIcons.plus,
|
CupertinoIcons.plus,
|
||||||
size: 60,
|
size: 60,
|
||||||
@@ -85,12 +93,13 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
const SizedBox(height: 10), // Abstand von oben
|
const SizedBox(height: 10), // Abstand von oben
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 70),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 70),
|
||||||
child: Text(
|
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,
|
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),
|
key: Key(session.gameTitle),
|
||||||
background: Container(
|
background: Container(
|
||||||
color: CupertinoColors.destructiveRed,
|
color: CupertinoColors.destructiveRed,
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerRight,
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(left: 20.0),
|
const EdgeInsets.only(right: 20.0),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
CupertinoIcons.delete,
|
CupertinoIcons.delete,
|
||||||
color: CupertinoColors.white,
|
color: CupertinoColors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
direction: DismissDirection.startToEnd,
|
direction: DismissDirection.endToStart,
|
||||||
confirmDismiss: (direction) async {
|
confirmDismiss: (direction) async {
|
||||||
final String gameTitle = gameManager
|
final String gameTitle = gameManager
|
||||||
.gameList[index].gameTitle;
|
.gameList[index].gameTitle;
|
||||||
@@ -122,7 +131,8 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
gameTitle);
|
gameTitle);
|
||||||
},
|
},
|
||||||
onDismissed: (direction) {
|
onDismissed: (direction) {
|
||||||
gameManager.removeGameSession(index);
|
gameManager
|
||||||
|
.removeGameSessionByIndex(index);
|
||||||
},
|
},
|
||||||
dismissThresholds: const {
|
dismissThresholds: const {
|
||||||
DismissDirection.startToEnd: 0.6
|
DismissDirection.startToEnd: 0.6
|
||||||
@@ -159,18 +169,19 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
CupertinoIcons.person_2_fill),
|
CupertinoIcons.person_2_fill),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
//ignore: unused_local_variable
|
final session =
|
||||||
final val = await Navigator.push(
|
gameManager.gameList[index];
|
||||||
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
ActiveGameView(
|
ActiveGameView(
|
||||||
gameSession: gameManager
|
gameSession: session),
|
||||||
.gameList[index]),
|
|
||||||
),
|
),
|
||||||
);
|
).then((_) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -188,7 +199,7 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
/// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'.
|
/// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'.
|
||||||
String _translateGameMode(bool pointLimit) {
|
String _translateGameMode(bool pointLimit) {
|
||||||
if (pointLimit) {
|
if (pointLimit) {
|
||||||
return '${Globals.pointLimit} ${AppLocalizations.of(context).points}';
|
return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}';
|
||||||
}
|
}
|
||||||
return AppLocalizations.of(context).unlimited;
|
return AppLocalizations.of(context).unlimited;
|
||||||
}
|
}
|
||||||
@@ -215,7 +226,11 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context, true);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||||
|
final maxLength = widget.gameSession.getMaxLengthOfPlayerNames();
|
||||||
|
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -75,10 +76,8 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
middle: Text(AppLocalizations.of(context).results),
|
middle: Text(AppLocalizations.of(context).results),
|
||||||
leading: CupertinoButton(
|
leading: CupertinoButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () => {
|
onPressed: () =>
|
||||||
LocalStorageService.saveGameSessions(),
|
{LocalStorageService.saveGameSessions(), Navigator.pop(context)},
|
||||||
Navigator.pop(context, widget.gameSession)
|
|
||||||
},
|
|
||||||
child: Text(AppLocalizations.of(context).cancel),
|
child: Text(AppLocalizations.of(context).cancel),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -122,28 +121,21 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
index,
|
index,
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: widget.gameSession
|
horizontal: 4 +
|
||||||
.getLengthOfPlayerNames() >
|
_getSegmentedControlPadding(maxLength),
|
||||||
20
|
|
||||||
? (widget.gameSession
|
|
||||||
.getLengthOfPlayerNames() >
|
|
||||||
32
|
|
||||||
? 5
|
|
||||||
: 10)
|
|
||||||
: 15,
|
|
||||||
vertical: 6,
|
vertical: 6,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: FittedBox(
|
||||||
name,
|
fit: BoxFit.scaleDown,
|
||||||
textAlign: TextAlign.center,
|
child: Text(
|
||||||
maxLines: 1,
|
name,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontWeight: FontWeight.bold,
|
maxLines: 1,
|
||||||
fontSize: widget.gameSession
|
style: TextStyle(
|
||||||
.getLengthOfPlayerNames() >
|
fontWeight: FontWeight.bold,
|
||||||
28
|
fontSize: _getSegmentedControlFontSize(
|
||||||
? 14
|
maxLength),
|
||||||
: 18,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -191,7 +183,13 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: CupertinoListTile(
|
child: CupertinoListTile(
|
||||||
backgroundColor: CupertinoColors.secondaryLabel,
|
backgroundColor: CupertinoColors.secondaryLabel,
|
||||||
title: Row(children: [Text(name)]),
|
title: Row(children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
))
|
||||||
|
]),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${widget.gameSession.playerScores[index]}'
|
'${widget.gameSession.playerScores[index]}'
|
||||||
' ${AppLocalizations.of(context).points}'),
|
' ${AppLocalizations.of(context).points}'),
|
||||||
@@ -290,7 +288,7 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
? () {
|
? () {
|
||||||
_finishRound();
|
_finishRound();
|
||||||
LocalStorageService.saveGameSessions();
|
LocalStorageService.saveGameSessions();
|
||||||
Navigator.pop(context, widget.gameSession);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Text(AppLocalizations.of(context).done),
|
child: Text(AppLocalizations.of(context).done),
|
||||||
@@ -301,17 +299,10 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
_finishRound();
|
_finishRound();
|
||||||
LocalStorageService.saveGameSessions();
|
LocalStorageService.saveGameSessions();
|
||||||
if (widget.gameSession.isGameFinished == true) {
|
if (widget.gameSession.isGameFinished == true) {
|
||||||
Navigator.pop(context, widget.gameSession);
|
Navigator.pop(context);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context, rootNavigator: true)
|
Navigator.pop(
|
||||||
.pushReplacement(
|
context, widget.roundNumber + 1);
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) => RoundView(
|
|
||||||
gameSession: widget.gameSession,
|
|
||||||
roundNumber: widget.roundNumber + 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: 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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
for (final controller in _scoreControllerList) {
|
for (final controller in _scoreControllerList) {
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
AppLocalizations.of(context).cabo_penalty_subtitle),
|
AppLocalizations.of(context).cabo_penalty_subtitle),
|
||||||
trailing: Stepper(
|
trailing: Stepper(
|
||||||
key: _stepperKey1,
|
key: _stepperKey1,
|
||||||
initialValue: Globals.caboPenalty,
|
initialValue: ConfigService.caboPenalty,
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 50,
|
maxValue: 50,
|
||||||
step: 1,
|
step: 1,
|
||||||
onChanged: (newCaboPenalty) {
|
onChanged: (newCaboPenalty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
ConfigService.setCaboPenalty(newCaboPenalty);
|
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),
|
Text(AppLocalizations.of(context).point_limit_subtitle),
|
||||||
trailing: Stepper(
|
trailing: Stepper(
|
||||||
key: _stepperKey2,
|
key: _stepperKey2,
|
||||||
initialValue: Globals.pointLimit,
|
initialValue: ConfigService.pointLimit,
|
||||||
minValue: 30,
|
minValue: 30,
|
||||||
maxValue: 1000,
|
maxValue: 1000,
|
||||||
step: 10,
|
step: 10,
|
||||||
onChanged: (newPointLimit) {
|
onChanged: (newPointLimit) {
|
||||||
setState(() {
|
setState(() {
|
||||||
ConfigService.setPointLimit(newPointLimit);
|
ConfigService.setPointLimit(newPointLimit);
|
||||||
Globals.pointLimit = newPointLimit;
|
ConfigService.pointLimit = newPointLimit;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -125,27 +125,7 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final success =
|
final success =
|
||||||
await LocalStorageService.importJsonFile();
|
await LocalStorageService.importJsonFile();
|
||||||
if (!success && context.mounted) {
|
showFeedbackDialog(success);
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
@@ -160,15 +140,15 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final success =
|
final success =
|
||||||
await LocalStorageService.exportJsonFile();
|
await LocalStorageService.exportGameData();
|
||||||
if (!success && context.mounted) {
|
if (!success && context.mounted) {
|
||||||
showCupertinoDialog(
|
showCupertinoDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CupertinoAlertDialog(
|
builder: (context) => CupertinoAlertDialog(
|
||||||
title:
|
title: Text(AppLocalizations.of(context)
|
||||||
Text(AppLocalizations.of(context).error),
|
.export_error_title),
|
||||||
content: Text(AppLocalizations.of(context)
|
content: Text(AppLocalizations.of(context)
|
||||||
.error_export),
|
.export_error_message),
|
||||||
actions: [
|
actions: [
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
child:
|
child:
|
||||||
@@ -236,4 +216,52 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
Future<PackageInfo> _getPackageInfo() async {
|
Future<PackageInfo> _getPackageInfo() async {
|
||||||
return await PackageInfo.fromPlatform();
|
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"
|
description: "Mobile app for the card game Cabo"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.3.0+232
|
version: 0.3.9+331
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -25,6 +25,8 @@ dependencies:
|
|||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: any
|
intl: any
|
||||||
|
syncfusion_flutter_charts: ^30.1.37
|
||||||
|
uuid: ^4.5.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -37,4 +39,5 @@ flutter:
|
|||||||
uses-material-design: false
|
uses-material-design: false
|
||||||
assets:
|
assets:
|
||||||
- assets/cabo_counter-logo_rounded.png
|
- 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', () {
|
group('Helper Functions', () {
|
||||||
test('getLengthOfPlayerNames', () {
|
test('getMaxLengthOfPlayerNames', () {
|
||||||
expect(session.getLengthOfPlayerNames(),
|
expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie (7)
|
||||||
equals(15)); // Alice(5) + Bob(3) + Charlie(7)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('increaseRound', () {
|
test('increaseRound', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user