Beta-Version 0.5.3 (#136)
* Updated createGameView ListBuilder * Added ReorderableListView * Increment build no * Fixed bug with wrong medal icon * change not equal to greater than * Updated bool var * Fixed deletion error * Small translation improvements * Implemented first version of point overview * Visual improvements on table * Added details and sum row * Updated strings * Implemented new strings * Refactoring * Updated graph displayment * Moved new views to statistics section * Added seperator in main menu * Renaming * Updated sign * Updated colors & class name * Removed empty line * Updated round index * Updated types * Added new kamikaze button and bundles navigation functionality * Updated lock icon * Updated button position and design * Removed title row and changed segmendetControl Padding * Refactored logic and added comments * Updated comment * Chaned icon * Added comment * Removed print * Updated colors * Changed var name * Removed unused strings * Added gameMode * Changed creation variable * Updated mode selection * Updated strings * Changed mode order * Implemented default mode selection * Updated initState * Removed print * Removed print * Removed comments * Updated config service * Changed create game view * Changed icon * Updated strings * Updated config * Updated mode selection logic * Deleted getter * Removed not used code * Implemented reset logic for default game mode * Updated to 0.5.0 * Hotfix: Pixel Overflow * Changed the overall return type for gamemodes * Updated documentation * Fixed merge issues * Added Custom button * Updated strings * Updated buttons, implemented animatedOpacity * Keyboard still doesnt works * Fixed keyboard behaviour * Changed keyboard height * Added method getGameSessionById() * Updated gameSession class * id gets added to gameSession class at creation * Cleaned up file * Added docs and dependency * Removed toString * Implemented null safety * Added named parameter * Replaced button with custom button * Updated key * Updated addGameSessionMethod * Update README.md * Added Strings for popup * Implemented popup & confetti * Extracted code to method _playFinishAnimation() * Replaced tenary operator with Visibility Widget * Replaced tenary operator with Visibility Widget * Used variable again * Added delays in constants.dart * Removed confetti button * Updated strings * Removed print * Added dispose for confettiController * Implemented missing constant in code * Updated gameSession logic so more than one player can be winner * Updated strings * Updated winner popup * game names now can have up to 20 chars * Updated strings * Added sized box for visual enhancement * Centered the add player button and made it wider * New created player textfields get automatically focused * Added focus nodes for autofocus and navigation between textfields * Updated version number * Updated game title textfield with focus node and textaction * Added focusnodes to dispose * Update README.md * Fixed bug with no popup shown * Fixed bug with out of range error * Updated listener notification
This commit is contained in:
45
README.md
45
README.md
@@ -1,6 +1,6 @@
|
|||||||
# CABO Counter
|
# CABO Counter
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -12,25 +12,29 @@ A mobile score tracker for the card game Cabo, helping players effortlessly mana
|
|||||||
|
|
||||||
## 📱 Description
|
## 📱 Description
|
||||||
|
|
||||||
Cabo Counter is an intuitive Flutter-based mobile application designed to enhance your CABO card game experience. It eliminates manual scorekeeping by automatically calculating points per round.
|
Cabo Counter is an intuitive Flutter-based mobile application designed to enhance your CABO card game experience. It eliminates manual scorekeeping by automatically calculating points per round.
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- 🆕 Create new games with customizable rules
|
|
||||||
- 👥 Support for 2-5 players
|
- 👥 Support for 2-5 players
|
||||||
- ⚖️ Two game modes:
|
- ⚖️ Two game modes:
|
||||||
- **100 Points Mode** (Standard)
|
- **Point Limit Mode**: Play until a certain point limit is reached
|
||||||
- **Infinite Mode** (Casual play)
|
- **Unlimited Mode**: Play without an limit and end the round at any point
|
||||||
- 🔢 Automatic score calculation with:
|
- 🔢 Automatic score calculation with:
|
||||||
|
- Falsly calling Cabo
|
||||||
|
- Exact 100-point bonus (score halving)
|
||||||
- Kamikaze rule handling
|
- Kamikaze rule handling
|
||||||
- Exact 100-point bonus (score halving)
|
- 📊 Round history tracking via graph and table
|
||||||
- 📊 Round history tracking
|
- 🎨 Customizable
|
||||||
|
- Change the default settings for point limits and cabo penaltys
|
||||||
|
- Choose a default game mode for every new created game
|
||||||
|
- 💿 Im- and exporting certain games or the whole app data
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Flutter 3.24.5+
|
- Flutter 3.32.1+
|
||||||
- Dart 3.5.4+
|
- Dart 3.8.1+
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
@@ -43,18 +47,22 @@ flutter run
|
|||||||
|
|
||||||
## 🎮 Usage
|
## 🎮 Usage
|
||||||
|
|
||||||
1. **Start New Game**
|
1. **Start a new game**
|
||||||
- Choose game mode (100 Points or Infinite)
|
- Click the "+"-Button
|
||||||
|
- Choose a game title and a game mode
|
||||||
- Add 2-5 players
|
- Add 2-5 players
|
||||||
|
|
||||||
2. **Gameplay**
|
2. **Gameplay**
|
||||||
- Track rounds with automatic scoring
|
- Open the first round
|
||||||
- Handle special rules (Kamikaze, exact 100 points)
|
- Choose the player who called Cabo
|
||||||
- View real-time standings
|
- Enter the points of every player
|
||||||
|
- If given: Choose a Kamikaze player
|
||||||
|
- Navigate to the next round or back to the overview
|
||||||
|
- Let the app calculate all points for you
|
||||||
|
|
||||||
3. **Round Management**
|
3. **Statistics**
|
||||||
- Automatic winner detection
|
- View the progress graph for the game
|
||||||
- Penalty point calculation
|
- Get a detailed table overview for every points made or lost
|
||||||
- Game-over detection (100 Points mode)
|
- Game-over detection (100 Points mode)
|
||||||
|
|
||||||
## 🃏 Key Rules Overview
|
## 🃏 Key Rules Overview
|
||||||
@@ -67,7 +75,8 @@ flutter run
|
|||||||
- Exact 100 points: Score halved
|
- Exact 100 points: Score halved
|
||||||
|
|
||||||
### Game End
|
### Game End
|
||||||
- First player ≥101 points triggers final scoring
|
- First player ≥100 points triggers final scoring
|
||||||
|
- In unlimited mode you can end the game via the End Game Button
|
||||||
- Lowest total score wins
|
- Lowest total score wins
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|||||||
@@ -19,4 +19,13 @@ class Constants {
|
|||||||
remindDays: 45,
|
remindDays: 45,
|
||||||
minLaunches: 15,
|
minLaunches: 15,
|
||||||
remindLaunches: 40);
|
remindLaunches: 40);
|
||||||
|
|
||||||
|
/// Delay in milliseconds before a pop-up appears.
|
||||||
|
static const int popUpDelay = 300;
|
||||||
|
|
||||||
|
/// Delay in milliseconds before the round view appears after the previous one is closed.
|
||||||
|
static const int roundViewDelay = 600;
|
||||||
|
|
||||||
|
/// Duration in milliseconds for the fade-in animation of texts.
|
||||||
|
static const int fadeInDuration = 300;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ class CustomTheme {
|
|||||||
static Color white = CupertinoColors.white;
|
static Color white = CupertinoColors.white;
|
||||||
static Color primaryColor = CupertinoColors.systemGreen;
|
static Color primaryColor = CupertinoColors.systemGreen;
|
||||||
static Color backgroundColor = const Color(0xFF101010);
|
static Color backgroundColor = const Color(0xFF101010);
|
||||||
static Color backgroundTintColor = CupertinoColors.darkBackgroundGray;
|
static Color mainElementBackgroundColor = CupertinoColors.darkBackgroundGray;
|
||||||
|
static Color playerTileColor = CupertinoColors.secondaryLabel;
|
||||||
|
static Color buttonBackgroundColor = const Color(0xFF202020);
|
||||||
|
|
||||||
// Line Colors for GraphView
|
// Line Colors for GraphView
|
||||||
static const Color graphColor1 = Color(0xFFF44336);
|
static const Color graphColor1 = Color(0xFFF44336);
|
||||||
@@ -13,6 +15,10 @@ class CustomTheme {
|
|||||||
static const Color graphColor4 = Color(0xFF9C27B0);
|
static const Color graphColor4 = Color(0xFF9C27B0);
|
||||||
static final Color graphColor5 = primaryColor;
|
static final Color graphColor5 = primaryColor;
|
||||||
|
|
||||||
|
// Colors for PointsView
|
||||||
|
static Color pointLossColor = primaryColor;
|
||||||
|
static const Color pointGainColor = Color(0xFFF44336);
|
||||||
|
|
||||||
static TextStyle modeTitle = TextStyle(
|
static TextStyle modeTitle = TextStyle(
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:cabo_counter/data/game_session.dart';
|
import 'package:cabo_counter/data/game_session.dart';
|
||||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class GameManager extends ChangeNotifier {
|
class GameManager extends ChangeNotifier {
|
||||||
@@ -10,17 +11,25 @@ class GameManager extends ChangeNotifier {
|
|||||||
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
|
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
|
||||||
/// It also saves the updated game sessions to local storage.
|
/// It also saves the updated game sessions to local storage.
|
||||||
/// Returns the index of the newly added session in the sorted list.
|
/// Returns the index of the newly added session in the sorted list.
|
||||||
Future<int> addGameSession(GameSession session) async {
|
int addGameSession(GameSession session) {
|
||||||
session.addListener(() {
|
session.addListener(() {
|
||||||
notifyListeners(); // Propagate session changes
|
notifyListeners(); // Propagate session changes
|
||||||
});
|
});
|
||||||
gameList.add(session);
|
gameList.add(session);
|
||||||
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await LocalStorageService.saveGameSessions();
|
LocalStorageService.saveGameSessions();
|
||||||
return gameList.indexOf(session);
|
return gameList.indexOf(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves a game session by its id.
|
||||||
|
/// Takes a String [id] as input. It searches the `gameList` for a session
|
||||||
|
/// with a matching id and returns it if found.
|
||||||
|
/// If no session is found, it returns null.
|
||||||
|
GameSession? getGameSessionById(String id) {
|
||||||
|
return gameList.firstWhereOrNull((session) => session.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes a game session from the list and sorts it by creation date.
|
/// Removes a game session from the list and sorts it by creation date.
|
||||||
/// 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.
|
||||||
@@ -60,6 +69,7 @@ class GameManager extends ChangeNotifier {
|
|||||||
|
|
||||||
gameList[index].roundNumber--;
|
gameList[index].roundNumber--;
|
||||||
gameList[index].isGameFinished = true;
|
gameList[index].isGameFinished = true;
|
||||||
|
gameList[index].setWinner();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
LocalStorageService.saveGameSessions();
|
LocalStorageService.saveGameSessions();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'package:uuid/uuid.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 String id;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final String gameTitle;
|
final String gameTitle;
|
||||||
final List<String> players;
|
final List<String> players;
|
||||||
@@ -27,6 +27,7 @@ class GameSession extends ChangeNotifier {
|
|||||||
List<Round> roundList = [];
|
List<Round> roundList = [];
|
||||||
|
|
||||||
GameSession({
|
GameSession({
|
||||||
|
required this.id,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.gameTitle,
|
required this.gameTitle,
|
||||||
required this.players,
|
required this.players,
|
||||||
@@ -35,8 +36,6 @@ 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
|
||||||
@@ -256,7 +255,7 @@ class GameSession extends ChangeNotifier {
|
|||||||
isGameFinished = true;
|
isGameFinished = true;
|
||||||
print('${players[i]} hat die 100 Punkte ueberschritten, '
|
print('${players[i]} hat die 100 Punkte ueberschritten, '
|
||||||
'deswegen wurde das Spiel beendet');
|
'deswegen wurde das Spiel beendet');
|
||||||
_setWinner();
|
setWinner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,16 +298,20 @@ class GameSession extends ChangeNotifier {
|
|||||||
/// Determines the winner of the game session.
|
/// Determines the winner of the game session.
|
||||||
/// It iterates through the player scores and finds the player
|
/// It iterates through the player scores and finds the player
|
||||||
/// with the lowest score.
|
/// with the lowest score.
|
||||||
void _setWinner() {
|
void setWinner() {
|
||||||
int score = playerScores[0];
|
int minScore = playerScores.reduce((a, b) => a < b ? a : b);
|
||||||
String lowestPlayer = players[0];
|
List<String> lowestPlayers = [];
|
||||||
for (int i = 0; i < players.length; i++) {
|
for (int i = 0; i < players.length; i++) {
|
||||||
if (playerScores[i] < score) {
|
if (playerScores[i] == minScore) {
|
||||||
score = playerScores[i];
|
lowestPlayers.add(players[i]);
|
||||||
lowestPlayer = players[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
winner = lowestPlayer;
|
if (lowestPlayers.length > 1) {
|
||||||
|
winner =
|
||||||
|
'${lowestPlayers.sublist(0, lowestPlayers.length - 1).join(', ')} & ${lowestPlayers.last}';
|
||||||
|
} else {
|
||||||
|
winner = lowestPlayers.first;
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,12 @@
|
|||||||
"min_players_title": "Zu wenig Spieler:innen",
|
"min_players_title": "Zu wenig Spieler:innen",
|
||||||
"min_players_message": "Es müssen mindestens 2 Spieler:innen hinzugefügt werden",
|
"min_players_message": "Es müssen mindestens 2 Spieler:innen hinzugefügt werden",
|
||||||
"no_name_title": "Kein Name",
|
"no_name_title": "Kein Name",
|
||||||
"no_name_message": "Jeder Spieler muss einen Namen haben.",
|
"no_name_message": "Jede:r Spieler:in muss einen Namen haben.",
|
||||||
|
|
||||||
"select_game_mode": "Spielmodus auswählen",
|
"select_game_mode": "Spielmodus auswählen",
|
||||||
|
"no_mode_selected": "Wähle einen Spielmodus",
|
||||||
|
"no_default_mode": "Kein Modus",
|
||||||
|
"no_default_description": "Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.",
|
||||||
"point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht",
|
"point_limit_description": "Es wird so lange gespielt, bis ein:e Spieler:in mehr als {pointLimit} Punkte erreicht",
|
||||||
"@point_limit_description": {
|
"@point_limit_description": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -71,6 +74,7 @@
|
|||||||
"results": "Ergebnisse",
|
"results": "Ergebnisse",
|
||||||
"who_said_cabo": "Wer hat CABO gesagt?",
|
"who_said_cabo": "Wer hat CABO gesagt?",
|
||||||
"kamikaze": "Kamikaze",
|
"kamikaze": "Kamikaze",
|
||||||
|
"who_has_kamikaze": "Wer hat Kamikaze?",
|
||||||
"done": "Fertig",
|
"done": "Fertig",
|
||||||
"next_round": "Nächste Runde",
|
"next_round": "Nächste Runde",
|
||||||
"bonus_points_title": "Bonus-Punkte!",
|
"bonus_points_title": "Bonus-Punkte!",
|
||||||
@@ -92,7 +96,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"end_of_game_title": "Spiel beendet",
|
||||||
|
"end_of_game_message": "{playerCount, plural, =1{{names} hat das Spiel mit {points} Punkten gewonnen. Glückwunsch!} other{{names} haben das Spiel mit {points} Punkten gewonnen. Glückwunsch!}}",
|
||||||
|
"@end_of_game_message": {
|
||||||
|
"placeholders": {
|
||||||
|
"playerCount": {
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"end_game": "Spiel beenden",
|
"end_game": "Spiel beenden",
|
||||||
"delete_game": "Spiel löschen",
|
"delete_game": "Spiel löschen",
|
||||||
"new_game_same_settings": "Neues Spiel mit gleichen Einstellungen",
|
"new_game_same_settings": "Neues Spiel mit gleichen Einstellungen",
|
||||||
@@ -102,14 +120,15 @@
|
|||||||
"end_game_title": "Spiel beenden?",
|
"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.",
|
"end_game_message": "Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.",
|
||||||
|
|
||||||
"game_process": "Spielverlauf",
|
"statistics": "Statistiken",
|
||||||
|
"point_overview": "Punkteübersicht",
|
||||||
|
"scoring_history": "Spielverlauf",
|
||||||
"empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.",
|
"empty_graph_text": "Du musst mindestens eine Runde spielen, damit der Graph des Spielverlaufes angezeigt werden kann.",
|
||||||
|
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"cabo_penalty": "Cabo-Strafe",
|
"cabo_penalty": "Cabo-Strafe",
|
||||||
"cabo_penalty_subtitle": "... für falsches Cabo sagen",
|
|
||||||
"point_limit": "Punkte-Limit",
|
"point_limit": "Punkte-Limit",
|
||||||
"point_limit_subtitle": "... hier ist Schluss",
|
"standard_mode": "Standard-Modus",
|
||||||
"reset_to_default": "Auf Standard zurücksetzen",
|
"reset_to_default": "Auf Standard zurücksetzen",
|
||||||
"game_data": "Spieldaten",
|
"game_data": "Spieldaten",
|
||||||
"import_data": "Spieldaten importieren",
|
"import_data": "Spieldaten importieren",
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"players": "Players",
|
"players": "Players",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"back": "Back",
|
"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": "Create a new game using the button in the top right.",
|
||||||
"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": {
|
||||||
@@ -46,19 +46,22 @@
|
|||||||
"select_mode": "Select a mode",
|
"select_mode": "Select a mode",
|
||||||
"add_player": "Add Player",
|
"add_player": "Add Player",
|
||||||
"create_game": "Create Game",
|
"create_game": "Create Game",
|
||||||
"max_players_title": "Maximum reached",
|
"max_players_title": "Player Limit Reached",
|
||||||
"max_players_message": "A maximum of 5 players can be added.",
|
"max_players_message": "You can add a maximum of 5 players.",
|
||||||
"no_gameTitle_title": "No Title",
|
"no_gameTitle_title": "Missing Game Title",
|
||||||
"no_gameTitle_message": "You must enter a title for the game.",
|
"no_gameTitle_message": "Please enter a title for your game.",
|
||||||
"no_mode_title": "No Mode",
|
"no_mode_title": "Game Mode Required",
|
||||||
"no_mode_message": "You must select a game mode.",
|
"no_mode_message": "Please select a game mode to continue",
|
||||||
"min_players_title": "Too few players",
|
"min_players_title": "Too Few Players",
|
||||||
"min_players_message": "At least 2 players must be added.",
|
"min_players_message": "At least 2 players are required to start the game.",
|
||||||
"no_name_title": "No Name",
|
"no_name_title": "Missing Player Names",
|
||||||
"no_name_message": "Each player must have a name.",
|
"no_name_message": "Each player must have a name.",
|
||||||
|
|
||||||
"select_game_mode": "Select game mode",
|
"select_game_mode": "Select game mode",
|
||||||
"point_limit_description": "The game ends when a player reaches more than {pointLimit} points.",
|
"no_mode_selected": "No mode selected",
|
||||||
|
"no_default_mode": "No default mode",
|
||||||
|
"no_default_description": "The default mode gets reset.",
|
||||||
|
"point_limit_description": "The game ends when a player scores more than {pointLimit} points.",
|
||||||
"@point_limit_description": {
|
"@point_limit_description": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"pointLimit": {
|
"pointLimit": {
|
||||||
@@ -66,11 +69,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unlimited_description": "There is no limit. The game continues until you decide to stop.",
|
"unlimited_description": "The game continues until you decide to stop playing",
|
||||||
|
|
||||||
"results": "Results",
|
"results": "Results",
|
||||||
"who_said_cabo": "Who said CABO?",
|
"who_said_cabo": "Who called Cabo?",
|
||||||
"kamikaze": "Kamikaze",
|
"kamikaze": "Kamikaze",
|
||||||
|
"who_has_kamikaze": "Who has Kamikaze?",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"next_round": "Next Round",
|
"next_round": "Next Round",
|
||||||
"bonus_points_title": "Bonus-Points!",
|
"bonus_points_title": "Bonus-Points!",
|
||||||
@@ -92,7 +96,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"end_of_game_title": "End of Game",
|
||||||
|
"end_of_game_message": "{playerCount, plural, =1{{names} won the game with {points} points. Congratulations!} other{{names} won the game with {points} points. Congratulations to everyone!}}",
|
||||||
|
"@end_of_game_message": {
|
||||||
|
"placeholders": {
|
||||||
|
"playerCount": {
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"end_game": "End Game",
|
"end_game": "End Game",
|
||||||
"delete_game": "Delete Game",
|
"delete_game": "Delete Game",
|
||||||
"new_game_same_settings": "New Game with same Settings",
|
"new_game_same_settings": "New Game with same Settings",
|
||||||
@@ -102,14 +120,15 @@
|
|||||||
"end_game_title": "End the game?",
|
"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.",
|
"end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.",
|
||||||
|
|
||||||
"game_process": "Scoring History",
|
"statistics": "Statistics",
|
||||||
|
"point_overview": "Point Overview",
|
||||||
|
"scoring_history": "Scoring History",
|
||||||
"empty_graph_text": "You must play at least one round for the game progress graph to be displayed.",
|
"empty_graph_text": "You must play at least one round for the game progress graph to be displayed.",
|
||||||
|
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"cabo_penalty": "Cabo Penalty",
|
"cabo_penalty": "Cabo Penalty",
|
||||||
"cabo_penalty_subtitle": "... for falsely calling Cabo.",
|
|
||||||
"point_limit": "Point Limit",
|
"point_limit": "Point Limit",
|
||||||
"point_limit_subtitle": "... the game ends here.",
|
"standard_mode": "Default Mode",
|
||||||
"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",
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @no_name_message.
|
/// No description provided for @no_name_message.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Jeder Spieler muss einen Namen haben.'**
|
/// **'Jede:r Spieler:in muss einen Namen haben.'**
|
||||||
String get no_name_message;
|
String get no_name_message;
|
||||||
|
|
||||||
/// No description provided for @select_game_mode.
|
/// No description provided for @select_game_mode.
|
||||||
@@ -374,6 +374,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'Spielmodus auswählen'**
|
/// **'Spielmodus auswählen'**
|
||||||
String get select_game_mode;
|
String get select_game_mode;
|
||||||
|
|
||||||
|
/// No description provided for @no_mode_selected.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Wähle einen Spielmodus'**
|
||||||
|
String get no_mode_selected;
|
||||||
|
|
||||||
|
/// No description provided for @no_default_mode.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Kein Modus'**
|
||||||
|
String get no_default_mode;
|
||||||
|
|
||||||
|
/// No description provided for @no_default_description.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.'**
|
||||||
|
String get no_default_description;
|
||||||
|
|
||||||
/// No description provided for @point_limit_description.
|
/// No description provided for @point_limit_description.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
@@ -404,6 +422,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Kamikaze'**
|
/// **'Kamikaze'**
|
||||||
String get kamikaze;
|
String get kamikaze;
|
||||||
|
|
||||||
|
/// No description provided for @who_has_kamikaze.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Wer hat Kamikaze?'**
|
||||||
|
String get who_has_kamikaze;
|
||||||
|
|
||||||
/// No description provided for @done.
|
/// No description provided for @done.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
@@ -429,6 +453,18 @@ abstract class AppLocalizations {
|
|||||||
String bonus_points_message(
|
String bonus_points_message(
|
||||||
int playerCount, String names, int pointLimit, int bonusPoints);
|
int playerCount, String names, int pointLimit, int bonusPoints);
|
||||||
|
|
||||||
|
/// No description provided for @end_of_game_title.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Spiel beendet'**
|
||||||
|
String get end_of_game_title;
|
||||||
|
|
||||||
|
/// No description provided for @end_of_game_message.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{playerCount, plural, =1{{names} hat das Spiel mit {points} Punkten gewonnen. Glückwunsch!} other{{names} haben das Spiel mit {points} Punkten gewonnen. Glückwunsch!}}'**
|
||||||
|
String end_of_game_message(int playerCount, String names, int points);
|
||||||
|
|
||||||
/// No description provided for @end_game.
|
/// No description provided for @end_game.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
@@ -477,11 +513,23 @@ abstract class AppLocalizations {
|
|||||||
/// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'**
|
/// **'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.'**
|
||||||
String get end_game_message;
|
String get end_game_message;
|
||||||
|
|
||||||
/// No description provided for @game_process.
|
/// No description provided for @statistics.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Statistiken'**
|
||||||
|
String get statistics;
|
||||||
|
|
||||||
|
/// No description provided for @point_overview.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Punkteübersicht'**
|
||||||
|
String get point_overview;
|
||||||
|
|
||||||
|
/// No description provided for @scoring_history.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Spielverlauf'**
|
/// **'Spielverlauf'**
|
||||||
String get game_process;
|
String get scoring_history;
|
||||||
|
|
||||||
/// No description provided for @empty_graph_text.
|
/// No description provided for @empty_graph_text.
|
||||||
///
|
///
|
||||||
@@ -501,23 +549,17 @@ abstract class AppLocalizations {
|
|||||||
/// **'Cabo-Strafe'**
|
/// **'Cabo-Strafe'**
|
||||||
String get cabo_penalty;
|
String get cabo_penalty;
|
||||||
|
|
||||||
/// No description provided for @cabo_penalty_subtitle.
|
|
||||||
///
|
|
||||||
/// In de, this message translates to:
|
|
||||||
/// **'... für falsches Cabo sagen'**
|
|
||||||
String get cabo_penalty_subtitle;
|
|
||||||
|
|
||||||
/// No description provided for @point_limit.
|
/// No description provided for @point_limit.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Punkte-Limit'**
|
/// **'Punkte-Limit'**
|
||||||
String get point_limit;
|
String get point_limit;
|
||||||
|
|
||||||
/// No description provided for @point_limit_subtitle.
|
/// No description provided for @standard_mode.
|
||||||
///
|
///
|
||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'... hier ist Schluss'**
|
/// **'Standard-Modus'**
|
||||||
String get point_limit_subtitle;
|
String get standard_mode;
|
||||||
|
|
||||||
/// No description provided for @reset_to_default.
|
/// No description provided for @reset_to_default.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -149,11 +149,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get no_name_title => 'Kein Name';
|
String get no_name_title => 'Kein Name';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_name_message => 'Jeder Spieler muss einen Namen haben.';
|
String get no_name_message => 'Jede:r Spieler:in muss einen Namen haben.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get select_game_mode => 'Spielmodus auswählen';
|
String get select_game_mode => 'Spielmodus auswählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_mode_selected => 'Wähle einen Spielmodus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_default_mode => 'Kein Modus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_default_description =>
|
||||||
|
'Entscheide bei jedem Spiel selber, welchen Modus du spielen möchtest.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String point_limit_description(int pointLimit) {
|
String point_limit_description(int pointLimit) {
|
||||||
return 'Es wird so lange gespielt, bis ein:e Spieler:in mehr als $pointLimit Punkte erreicht';
|
return 'Es wird so lange gespielt, bis ein:e Spieler:in mehr als $pointLimit Punkte erreicht';
|
||||||
@@ -172,6 +182,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get kamikaze => 'Kamikaze';
|
String get kamikaze => 'Kamikaze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get who_has_kamikaze => 'Wer hat Kamikaze?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get done => 'Fertig';
|
String get done => 'Fertig';
|
||||||
|
|
||||||
@@ -195,6 +208,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get end_of_game_title => 'Spiel beendet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String end_of_game_message(int playerCount, String names, int points) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
playerCount,
|
||||||
|
locale: localeName,
|
||||||
|
other:
|
||||||
|
'$names haben das Spiel mit $points Punkten gewonnen. Glückwunsch!',
|
||||||
|
one: '$names hat das Spiel mit $points Punkten gewonnen. Glückwunsch!',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get end_game => 'Spiel beenden';
|
String get end_game => 'Spiel beenden';
|
||||||
|
|
||||||
@@ -222,7 +250,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.';
|
'Möchtest du das Spiel beenden? Das Spiel wird als beendet markiert und kann nicht fortgeführt werden.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get game_process => 'Spielverlauf';
|
String get statistics => 'Statistiken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get point_overview => 'Punkteübersicht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scoring_history => 'Spielverlauf';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get empty_graph_text =>
|
String get empty_graph_text =>
|
||||||
@@ -234,14 +268,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get cabo_penalty => 'Cabo-Strafe';
|
String get cabo_penalty => 'Cabo-Strafe';
|
||||||
|
|
||||||
@override
|
|
||||||
String get cabo_penalty_subtitle => '... für falsches Cabo sagen';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get point_limit => 'Punkte-Limit';
|
String get point_limit => 'Punkte-Limit';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get point_limit_subtitle => '... hier ist Schluss';
|
String get standard_mode => 'Standard-Modus';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get reset_to_default => 'Auf Standard zurücksetzen';
|
String get reset_to_default => 'Auf Standard zurücksetzen';
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ 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.';
|
'Create a new game using the button in the top right.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete_game_title => 'Delete game?';
|
String get delete_game_title => 'Delete game?';
|
||||||
@@ -119,31 +119,32 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get create_game => 'Create Game';
|
String get create_game => 'Create Game';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max_players_title => 'Maximum reached';
|
String get max_players_title => 'Player Limit Reached';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max_players_message => 'A maximum of 5 players can be added.';
|
String get max_players_message => 'You can add a maximum of 5 players.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_gameTitle_title => 'No Title';
|
String get no_gameTitle_title => 'Missing Game Title';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_gameTitle_message => 'You must enter a title for the game.';
|
String get no_gameTitle_message => 'Please enter a title for your game.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_mode_title => 'No Mode';
|
String get no_mode_title => 'Game Mode Required';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_mode_message => 'You must select a game mode.';
|
String get no_mode_message => 'Please select a game mode to continue';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get min_players_title => 'Too few players';
|
String get min_players_title => 'Too Few Players';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get min_players_message => 'At least 2 players must be added.';
|
String get min_players_message =>
|
||||||
|
'At least 2 players are required to start the game.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_name_title => 'No Name';
|
String get no_name_title => 'Missing Player Names';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_name_message => 'Each player must have a name.';
|
String get no_name_message => 'Each player must have a name.';
|
||||||
@@ -151,24 +152,36 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get select_game_mode => 'Select game mode';
|
String get select_game_mode => 'Select game mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_mode_selected => 'No mode selected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_default_mode => 'No default mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_default_description => 'The default mode gets reset.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String point_limit_description(int pointLimit) {
|
String point_limit_description(int pointLimit) {
|
||||||
return 'The game ends when a player reaches more than $pointLimit points.';
|
return 'The game ends when a player scores more than $pointLimit points.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get unlimited_description =>
|
String get unlimited_description =>
|
||||||
'There is no limit. The game continues until you decide to stop.';
|
'The game continues until you decide to stop playing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get results => 'Results';
|
String get results => 'Results';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get who_said_cabo => 'Who said CABO?';
|
String get who_said_cabo => 'Who called Cabo?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get kamikaze => 'Kamikaze';
|
String get kamikaze => 'Kamikaze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get who_has_kamikaze => 'Who has Kamikaze?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get done => 'Done';
|
String get done => 'Done';
|
||||||
|
|
||||||
@@ -192,6 +205,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
return '$_temp0';
|
return '$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get end_of_game_title => 'End of Game';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String end_of_game_message(int playerCount, String names, int points) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
playerCount,
|
||||||
|
locale: localeName,
|
||||||
|
other:
|
||||||
|
'$names won the game with $points points. Congratulations to everyone!',
|
||||||
|
one: '$names won the game with $points points. Congratulations!',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get end_game => 'End Game';
|
String get end_game => 'End Game';
|
||||||
|
|
||||||
@@ -219,7 +247,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
'Do you want to end the game? The game gets marked as finished and cannot be continued.';
|
'Do you want to end the game? The game gets marked as finished and cannot be continued.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get game_process => 'Scoring History';
|
String get statistics => 'Statistics';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get point_overview => 'Point Overview';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scoring_history => 'Scoring History';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get empty_graph_text =>
|
String get empty_graph_text =>
|
||||||
@@ -231,14 +265,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get cabo_penalty => 'Cabo Penalty';
|
String get cabo_penalty => 'Cabo Penalty';
|
||||||
|
|
||||||
@override
|
|
||||||
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 standard_mode => 'Default Mode';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get reset_to_default => 'Reset to Default';
|
String get reset_to_default => 'Reset to Default';
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ Future<void> main() async {
|
|||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations(
|
||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||||
await ConfigService.initConfig();
|
await ConfigService.initConfig();
|
||||||
ConfigService.pointLimit = await ConfigService.getPointLimit();
|
|
||||||
ConfigService.caboPenalty = await ConfigService.getCaboPenalty();
|
|
||||||
await VersionService.init();
|
await VersionService.init();
|
||||||
runApp(const App());
|
runApp(const App());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import 'package:cabo_counter/core/constants.dart';
|
||||||
import 'package:cabo_counter/core/custom_theme.dart';
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
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/generated/app_localizations.dart';
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
import 'package:cabo_counter/presentation/views/create_game_view.dart';
|
import 'package:cabo_counter/presentation/views/create_game_view.dart';
|
||||||
import 'package:cabo_counter/presentation/views/graph_view.dart';
|
import 'package:cabo_counter/presentation/views/graph_view.dart';
|
||||||
|
import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
|
||||||
|
import 'package:cabo_counter/presentation/views/points_view.dart';
|
||||||
import 'package:cabo_counter/presentation/views/round_view.dart';
|
import 'package:cabo_counter/presentation/views/round_view.dart';
|
||||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:confetti/confetti.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -19,6 +24,9 @@ class ActiveGameView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ActiveGameViewState extends State<ActiveGameView> {
|
class _ActiveGameViewState extends State<ActiveGameView> {
|
||||||
|
final confettiController = ConfettiController(
|
||||||
|
duration: const Duration(seconds: 10),
|
||||||
|
);
|
||||||
late final GameSession gameSession;
|
late final GameSession gameSession;
|
||||||
late List<int> denseRanks;
|
late List<int> denseRanks;
|
||||||
late List<int> sortedPlayerIndices;
|
late List<int> sortedPlayerIndices;
|
||||||
@@ -31,200 +39,257 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return Stack(
|
||||||
listenable: gameSession,
|
children: [
|
||||||
builder: (context, _) {
|
ListenableBuilder(
|
||||||
sortedPlayerIndices = _getSortedPlayerIndices();
|
listenable: gameSession,
|
||||||
denseRanks = _calculateDenseRank(
|
builder: (context, _) {
|
||||||
gameSession.playerScores, sortedPlayerIndices);
|
sortedPlayerIndices = _getSortedPlayerIndices();
|
||||||
return CupertinoPageScaffold(
|
denseRanks = _calculateDenseRank(
|
||||||
navigationBar: CupertinoNavigationBar(
|
gameSession.playerScores, sortedPlayerIndices);
|
||||||
middle: Text(gameSession.gameTitle),
|
return CupertinoPageScaffold(
|
||||||
),
|
navigationBar: CupertinoNavigationBar(
|
||||||
child: SafeArea(
|
middle: Text(
|
||||||
child: SingleChildScrollView(
|
gameSession.gameTitle,
|
||||||
child: Column(
|
overflow: TextOverflow.ellipsis,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
),
|
||||||
Padding(
|
child: SafeArea(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
child: SingleChildScrollView(
|
||||||
child: Text(
|
child: Column(
|
||||||
AppLocalizations.of(context).players,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: CustomTheme.rowTitle,
|
children: [
|
||||||
),
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
ListView.builder(
|
child: Text(
|
||||||
shrinkWrap: true,
|
AppLocalizations.of(context).players,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
style: CustomTheme.rowTitle,
|
||||||
itemCount: gameSession.players.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
int playerIndex = sortedPlayerIndices[index];
|
|
||||||
return CupertinoListTile(
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
_getPlacementTextWidget(index),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(
|
|
||||||
gameSession.players[playerIndex],
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
trailing: Row(
|
),
|
||||||
children: [
|
ListView.builder(
|
||||||
const SizedBox(width: 5),
|
shrinkWrap: true,
|
||||||
Text('${gameSession.playerScores[playerIndex]} '
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
'${AppLocalizations.of(context).points}')
|
itemCount: gameSession.players.length,
|
||||||
],
|
itemBuilder: (BuildContext context, int index) {
|
||||||
),
|
int playerIndex = sortedPlayerIndices[index];
|
||||||
);
|
return CupertinoListTile(
|
||||||
},
|
title: Row(
|
||||||
),
|
children: [
|
||||||
Padding(
|
_getPlacementTextWidget(index),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
const SizedBox(width: 5),
|
||||||
child: Text(
|
Text(
|
||||||
AppLocalizations.of(context).rounds,
|
gameSession.players[playerIndex],
|
||||||
style: CustomTheme.rowTitle,
|
style: const TextStyle(
|
||||||
),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
ListView.builder(
|
],
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: gameSession.roundNumber,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(1),
|
|
||||||
child: CupertinoListTile(
|
|
||||||
backgroundColorActivated:
|
|
||||||
CustomTheme.backgroundColor,
|
|
||||||
title: Text(
|
|
||||||
'${AppLocalizations.of(context).round} ${index + 1}',
|
|
||||||
),
|
),
|
||||||
trailing:
|
trailing: Row(
|
||||||
index + 1 != gameSession.roundNumber ||
|
children: [
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
'${gameSession.playerScores[playerIndex]} '
|
||||||
|
'${AppLocalizations.of(context).points}')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).rounds,
|
||||||
|
style: CustomTheme.rowTitle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: gameSession.roundNumber,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
child: CupertinoListTile(
|
||||||
|
backgroundColorActivated:
|
||||||
|
CustomTheme.backgroundColor,
|
||||||
|
title: Text(
|
||||||
|
'${AppLocalizations.of(context).round} ${index + 1}',
|
||||||
|
),
|
||||||
|
trailing: index + 1 !=
|
||||||
|
gameSession.roundNumber ||
|
||||||
gameSession.isGameFinished == true
|
gameSession.isGameFinished == 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 {
|
||||||
_openRoundView(index + 1);
|
_openRoundView(context, index + 1);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context).game,
|
AppLocalizations.of(context).statistics,
|
||||||
style: CustomTheme.rowTitle,
|
style: CustomTheme.rowTitle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
CupertinoListTile(
|
CupertinoListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).game_process,
|
AppLocalizations.of(context)
|
||||||
|
.scoring_history,
|
||||||
|
),
|
||||||
|
backgroundColorActivated:
|
||||||
|
CustomTheme.backgroundColor,
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (_) => GraphView(
|
||||||
|
gameSession: gameSession,
|
||||||
|
)))),
|
||||||
|
CupertinoListTile(
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context).point_overview,
|
||||||
|
),
|
||||||
|
backgroundColorActivated:
|
||||||
|
CustomTheme.backgroundColor,
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (_) => PointsView(
|
||||||
|
gameSession: gameSession,
|
||||||
|
)))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).game,
|
||||||
|
style: CustomTheme.rowTitle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Visibility(
|
||||||
|
visible: !gameSession.isPointsLimitEnabled,
|
||||||
|
child: 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();
|
||||||
|
}
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
backgroundColorActivated:
|
CupertinoListTile(
|
||||||
CustomTheme.backgroundColor,
|
|
||||||
onTap: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (_) => GraphView(
|
|
||||||
gameSession: gameSession,
|
|
||||||
)))),
|
|
||||||
Visibility(
|
|
||||||
visible: !gameSession.isPointsLimitEnabled,
|
|
||||||
child: CupertinoListTile(
|
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).end_game,
|
AppLocalizations.of(context).delete_game,
|
||||||
style: gameSession.roundNumber > 1 &&
|
|
||||||
!gameSession.isGameFinished
|
|
||||||
? const TextStyle(color: Colors.white)
|
|
||||||
: const TextStyle(color: Colors.white30),
|
|
||||||
),
|
),
|
||||||
backgroundColorActivated:
|
backgroundColorActivated:
|
||||||
CustomTheme.backgroundColor,
|
CustomTheme.backgroundColor,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (gameSession.roundNumber > 1 &&
|
_showDeleteGameDialog().then((value) {
|
||||||
!gameSession.isGameFinished) {
|
if (value) {
|
||||||
_showEndGameDialog();
|
_removeGameSession(gameSession);
|
||||||
}
|
}
|
||||||
}),
|
});
|
||||||
),
|
},
|
||||||
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:
|
CupertinoListTile(
|
||||||
CustomTheme.backgroundColor,
|
title: Text(
|
||||||
onTap: () async {
|
AppLocalizations.of(context)
|
||||||
final success = await LocalStorageService
|
.new_game_same_settings,
|
||||||
.exportSingleGameSession(
|
),
|
||||||
widget.gameSession);
|
backgroundColorActivated:
|
||||||
if (!success && context.mounted) {
|
CustomTheme.backgroundColor,
|
||||||
showCupertinoDialog(
|
onTap: () {
|
||||||
context: context,
|
Navigator.pushReplacement(
|
||||||
builder: (context) => CupertinoAlertDialog(
|
context,
|
||||||
title: Text(AppLocalizations.of(context)
|
CupertinoPageRoute(
|
||||||
.export_error_title),
|
builder: (_) => CreateGameView(
|
||||||
content: Text(AppLocalizations.of(context)
|
gameTitle:
|
||||||
.export_error_message),
|
gameSession.gameTitle,
|
||||||
actions: [
|
gameMode: widget.gameSession
|
||||||
CupertinoDialogAction(
|
.isPointsLimitEnabled ==
|
||||||
child: Text(
|
true
|
||||||
AppLocalizations.of(context).ok),
|
? GameMode.pointLimit
|
||||||
onPressed: () =>
|
: GameMode.unlimited,
|
||||||
Navigator.pop(context),
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
}
|
||||||
);
|
}),
|
||||||
}
|
],
|
||||||
}),
|
)
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
),
|
));
|
||||||
),
|
}),
|
||||||
));
|
Column(
|
||||||
});
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: ConfettiWidget(
|
||||||
|
blastDirectionality: BlastDirectionality.explosive,
|
||||||
|
particleDrag: 0.07,
|
||||||
|
emissionFrequency: 0.1,
|
||||||
|
numberOfParticles: 10,
|
||||||
|
minBlastForce: 5,
|
||||||
|
maxBlastForce: 20,
|
||||||
|
confettiController: confettiController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a dialog to confirm ending the game.
|
/// Shows a dialog to confirm ending the game.
|
||||||
@@ -247,6 +312,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
gameManager.endGame(gameSession.id);
|
gameManager.endGame(gameSession.id);
|
||||||
|
_playFinishAnimation(context);
|
||||||
});
|
});
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
@@ -376,8 +442,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
/// Recursively opens the RoundView for the specified round number.
|
/// Recursively opens the RoundView for the specified round number.
|
||||||
/// It starts with the given [roundNumber] and continues to open the next round
|
/// It starts with the given [roundNumber] and continues to open the next round
|
||||||
/// until the user navigates back or the round number is invalid.
|
/// until the user navigates back or the round number is invalid.
|
||||||
void _openRoundView(int roundNumber) async {
|
void _openRoundView(BuildContext context, int roundNumber) async {
|
||||||
final val = await Navigator.of(context, rootNavigator: true).push(
|
final round = await Navigator.of(context, rootNavigator: true).push(
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (context) => RoundView(
|
builder: (context) => RoundView(
|
||||||
@@ -386,11 +452,58 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (val != null && val >= 0) {
|
|
||||||
|
if (widget.gameSession.isGameFinished && context.mounted) {
|
||||||
|
_playFinishAnimation(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the previous round was not the last one
|
||||||
|
if (round != null && round >= 0) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 600));
|
await Future.delayed(
|
||||||
_openRoundView(val);
|
const Duration(milliseconds: Constants.roundViewDelay));
|
||||||
|
if (context.mounted) {
|
||||||
|
_openRoundView(context, round);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays the confetti animation and shows a dialog with the winner's information.
|
||||||
|
Future<void> _playFinishAnimation(BuildContext context) async {
|
||||||
|
String winner = widget.gameSession.winner;
|
||||||
|
int winnerPoints = widget.gameSession.playerScores.min;
|
||||||
|
int winnerAmount = winner.contains('&') ? 2 : 1;
|
||||||
|
|
||||||
|
confettiController.play();
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: Constants.popUpDelay));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CupertinoAlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context).end_of_game_title),
|
||||||
|
content: Text(AppLocalizations.of(context)
|
||||||
|
.end_of_game_message(winnerAmount, winner, winnerPoints)),
|
||||||
|
actions: [
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: Text(AppLocalizations.of(context).ok),
|
||||||
|
onPressed: () {
|
||||||
|
confettiController.stop();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
confettiController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import 'package:cabo_counter/core/constants.dart';
|
||||||
import 'package:cabo_counter/core/custom_theme.dart';
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
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/generated/app_localizations.dart';
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
import 'package:cabo_counter/presentation/views/active_game_view.dart';
|
import 'package:cabo_counter/presentation/views/active_game_view.dart';
|
||||||
import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
|
import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
|
||||||
|
import 'package:cabo_counter/presentation/widgets/custom_button.dart';
|
||||||
import 'package:cabo_counter/services/config_service.dart';
|
import 'package:cabo_counter/services/config_service.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
enum CreateStatus {
|
enum CreateStatus {
|
||||||
noGameTitle,
|
noGameTitle,
|
||||||
@@ -16,15 +21,15 @@ enum CreateStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CreateGameView extends StatefulWidget {
|
class CreateGameView extends StatefulWidget {
|
||||||
|
final GameMode gameMode;
|
||||||
final String? gameTitle;
|
final String? gameTitle;
|
||||||
final bool? isPointsLimitEnabled;
|
|
||||||
final List<String>? players;
|
final List<String>? players;
|
||||||
|
|
||||||
const CreateGameView({
|
const CreateGameView({
|
||||||
super.key,
|
super.key,
|
||||||
this.gameTitle,
|
this.gameTitle,
|
||||||
this.isPointsLimitEnabled,
|
|
||||||
this.players,
|
this.players,
|
||||||
|
required this.gameMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,29 +38,39 @@ class CreateGameView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CreateGameViewState extends State<CreateGameView> {
|
class _CreateGameViewState extends State<CreateGameView> {
|
||||||
|
final TextEditingController _gameTitleTextController =
|
||||||
|
TextEditingController();
|
||||||
|
|
||||||
|
/// List of text controllers for player names.
|
||||||
final List<TextEditingController> _playerNameTextControllers = [
|
final List<TextEditingController> _playerNameTextControllers = [
|
||||||
TextEditingController()
|
TextEditingController()
|
||||||
];
|
];
|
||||||
final TextEditingController _gameTitleTextController =
|
|
||||||
TextEditingController();
|
/// List of focus nodes for player name text fields.
|
||||||
|
final List<FocusNode> _playerNameFocusNodes = [FocusNode()];
|
||||||
|
|
||||||
/// 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 whether the points limit feature is enabled.
|
/// Factor to adjust the view length when the keyboard is visible.
|
||||||
bool? _isPointsLimitEnabled;
|
final double keyboardHeightAdjustmentFactor = 0.75;
|
||||||
|
|
||||||
|
/// Variable to hold the selected game mode.
|
||||||
|
late GameMode gameMode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_isPointsLimitEnabled = widget.isPointsLimitEnabled;
|
gameMode = widget.gameMode;
|
||||||
|
|
||||||
_gameTitleTextController.text = widget.gameTitle ?? '';
|
_gameTitleTextController.text = widget.gameTitle ?? '';
|
||||||
|
|
||||||
if (widget.players != null) {
|
if (widget.players != null) {
|
||||||
_playerNameTextControllers.clear();
|
_playerNameTextControllers.clear();
|
||||||
for (var player in widget.players!) {
|
for (var player in widget.players!) {
|
||||||
_playerNameTextControllers.add(TextEditingController(text: player));
|
_playerNameTextControllers.add(TextEditingController(text: player));
|
||||||
|
_playerNameFocusNodes.add(FocusNode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,124 +78,100 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
previousPageTitle: AppLocalizations.of(context).overview,
|
previousPageTitle: AppLocalizations.of(context).overview,
|
||||||
middle: Text(AppLocalizations.of(context).new_game),
|
middle: Text(AppLocalizations.of(context).new_game),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Center(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context).game,
|
AppLocalizations.of(context).game,
|
||||||
style: CustomTheme.rowTitle,
|
style: CustomTheme.rowTitle,
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
|
|
||||||
child: CupertinoTextField(
|
|
||||||
decoration: const BoxDecoration(),
|
|
||||||
maxLength: 16,
|
|
||||||
prefix: Text(AppLocalizations.of(context).name),
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
placeholder: AppLocalizations.of(context).game_title,
|
|
||||||
controller: _gameTitleTextController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Spielmodus-Auswahl mit Chevron
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
|
|
||||||
child: CupertinoTextField(
|
|
||||||
decoration: const BoxDecoration(),
|
|
||||||
readOnly: true,
|
|
||||||
prefix: Text(AppLocalizations.of(context).mode),
|
|
||||||
suffix: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_isPointsLimitEnabled == null
|
|
||||||
? AppLocalizations.of(context).select_mode
|
|
||||||
: (_isPointsLimitEnabled!
|
|
||||||
? '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}'
|
|
||||||
: AppLocalizations.of(context).unlimited),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 3),
|
|
||||||
const CupertinoListTileChevron(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
onTap: () async {
|
|
||||||
final selectedMode = await Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) => ModeSelectionMenu(
|
|
||||||
pointLimit: ConfigService.pointLimit,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedMode != null) {
|
|
||||||
setState(() {
|
|
||||||
_isPointsLimitEnabled = selectedMode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
child: CupertinoTextField(
|
||||||
child: Text(
|
decoration: const BoxDecoration(),
|
||||||
AppLocalizations.of(context).players,
|
maxLength: 20,
|
||||||
style: CustomTheme.rowTitle,
|
prefix: Text(AppLocalizations.of(context).name),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
placeholder: AppLocalizations.of(context).game_title,
|
||||||
|
controller: _gameTitleTextController,
|
||||||
|
onSubmitted: (_) {
|
||||||
|
_playerNameFocusNodes.isNotEmpty
|
||||||
|
? _playerNameFocusNodes[0].requestFocus()
|
||||||
|
: FocusScope.of(context).unfocus();
|
||||||
|
},
|
||||||
|
textInputAction: _playerNameFocusNodes.isNotEmpty
|
||||||
|
? TextInputAction.next
|
||||||
|
: TextInputAction.done,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Expanded(
|
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
|
||||||
child: ListView.builder(
|
child: CupertinoTextField(
|
||||||
itemCount: _playerNameTextControllers.length +
|
decoration: const BoxDecoration(),
|
||||||
1, // +1 für den + Button
|
readOnly: true,
|
||||||
itemBuilder: (context, index) {
|
prefix: Text(AppLocalizations.of(context).mode),
|
||||||
if (index == _playerNameTextControllers.length) {
|
suffix: Row(
|
||||||
// + Button als letztes Element
|
children: [
|
||||||
return Padding(
|
_getDisplayedGameMode(),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
const SizedBox(width: 3),
|
||||||
child: CupertinoButton(
|
const CupertinoListTileChevron(),
|
||||||
padding: EdgeInsets.zero,
|
],
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onTap: () async {
|
||||||
children: [
|
final selectedMode = await Navigator.push(
|
||||||
const Icon(
|
context,
|
||||||
CupertinoIcons.add_circled,
|
CupertinoPageRoute(
|
||||||
color: CupertinoColors.activeGreen,
|
builder: (context) => ModeSelectionMenu(
|
||||||
size: 25,
|
pointLimit: ConfigService.getPointLimit(),
|
||||||
),
|
showDeselection: false,
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).add_player,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: CupertinoColors.activeGreen,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
|
||||||
if (_playerNameTextControllers.length < maxPlayers) {
|
|
||||||
setState(() {
|
|
||||||
_playerNameTextControllers
|
|
||||||
.add(TextEditingController());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showFeedbackDialog(CreateStatus.maxPlayers);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Spieler-Einträge
|
setState(() {
|
||||||
|
gameMode = selectedMode ?? gameMode;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).players,
|
||||||
|
style: CustomTheme.rowTitle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ReorderableListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
itemCount: _playerNameTextControllers.length,
|
||||||
|
onReorder: (oldIndex, newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (oldIndex < _playerNameTextControllers.length &&
|
||||||
|
newIndex <= _playerNameTextControllers.length) {
|
||||||
|
if (newIndex > oldIndex) newIndex--;
|
||||||
|
final item =
|
||||||
|
_playerNameTextControllers.removeAt(oldIndex);
|
||||||
|
_playerNameTextControllers.insert(newIndex, item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
key: ValueKey(index),
|
||||||
vertical: 8.0, horizontal: 5),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
@@ -200,82 +191,187 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CupertinoTextField(
|
child: CupertinoTextField(
|
||||||
controller: _playerNameTextControllers[index],
|
controller: _playerNameTextControllers[index],
|
||||||
|
focusNode: _playerNameFocusNodes[index],
|
||||||
maxLength: 12,
|
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),
|
||||||
decoration: const BoxDecoration(),
|
decoration: const BoxDecoration(),
|
||||||
|
textInputAction:
|
||||||
|
index + 1 < _playerNameTextControllers.length
|
||||||
|
? TextInputAction.next
|
||||||
|
: TextInputAction.done,
|
||||||
|
onSubmitted: (_) {
|
||||||
|
if (index + 1 < _playerNameFocusNodes.length) {
|
||||||
|
_playerNameFocusNodes[index + 1]
|
||||||
|
.requestFocus();
|
||||||
|
} else {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: _playerNameTextControllers.length > 1
|
||||||
|
? 1.0
|
||||||
|
: 0.0,
|
||||||
|
duration: const Duration(
|
||||||
|
milliseconds: Constants.fadeInDuration),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: ReorderableDragStartListener(
|
||||||
|
index: index,
|
||||||
|
child: const Icon(
|
||||||
|
CupertinoIcons.line_horizontal_3,
|
||||||
|
color: CupertinoColors.systemGrey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}),
|
||||||
},
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 50),
|
||||||
),
|
child: Stack(
|
||||||
Center(
|
|
||||||
child: CupertinoButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
AppLocalizations.of(context).create_game,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
style: const TextStyle(
|
children: [
|
||||||
color: CupertinoColors.activeGreen,
|
CupertinoButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: null,
|
||||||
|
child: Icon(
|
||||||
|
CupertinoIcons.plus_circle_fill,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
size: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: CupertinoButton(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).add_player,
|
||||||
|
style: TextStyle(
|
||||||
|
color: CustomTheme.primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (_playerNameTextControllers.length < maxPlayers) {
|
||||||
|
setState(() {
|
||||||
|
_playerNameTextControllers
|
||||||
|
.add(TextEditingController());
|
||||||
|
_playerNameFocusNodes.add(FocusNode());
|
||||||
|
});
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_playerNameFocusNodes.last.requestFocus();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_showFeedbackDialog(CreateStatus.maxPlayers);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
|
||||||
if (_gameTitleTextController.text == '') {
|
|
||||||
showFeedbackDialog(CreateStatus.noGameTitle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_isPointsLimitEnabled == null) {
|
|
||||||
showFeedbackDialog(CreateStatus.noModeSelected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_playerNameTextControllers.length < 2) {
|
|
||||||
showFeedbackDialog(CreateStatus.minPlayers);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!everyPlayerHasAName()) {
|
|
||||||
showFeedbackDialog(CreateStatus.noPlayerName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> players = [];
|
|
||||||
for (var controller in _playerNameTextControllers) {
|
|
||||||
players.add(controller.text);
|
|
||||||
}
|
|
||||||
GameSession gameSession = GameSession(
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
gameTitle: _gameTitleTextController.text,
|
|
||||||
players: players,
|
|
||||||
pointLimit: ConfigService.pointLimit,
|
|
||||||
caboPenalty: ConfigService.caboPenalty,
|
|
||||||
isPointsLimitEnabled: _isPointsLimitEnabled!,
|
|
||||||
);
|
|
||||||
final index = await gameManager.addGameSession(gameSession);
|
|
||||||
final session = gameManager.gameList[index];
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
ActiveGameView(gameSession: session)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
],
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 50),
|
||||||
))));
|
child: Center(
|
||||||
|
key: const ValueKey('create_game_button'),
|
||||||
|
child: CustomButton(
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).create_game,
|
||||||
|
style: TextStyle(
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_checkAllGameAttributes();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
KeyboardVisibilityBuilder(builder: (context, visible) {
|
||||||
|
if (visible) {
|
||||||
|
return SizedBox(
|
||||||
|
height: MediaQuery.of(context).viewInsets.bottom *
|
||||||
|
keyboardHeightAdjustmentFactor,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a widget that displays the currently selected game mode in the View.
|
||||||
|
Text _getDisplayedGameMode() {
|
||||||
|
if (gameMode == GameMode.none) {
|
||||||
|
return Text(AppLocalizations.of(context).no_mode_selected);
|
||||||
|
} else if (gameMode == GameMode.pointLimit) {
|
||||||
|
return Text(
|
||||||
|
'${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}',
|
||||||
|
style: TextStyle(color: CustomTheme.primaryColor));
|
||||||
|
} else {
|
||||||
|
return Text(AppLocalizations.of(context).unlimited,
|
||||||
|
style: TextStyle(color: CustomTheme.primaryColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks all game attributes before creating a new game.
|
||||||
|
/// If any attribute is invalid, it shows a feedback dialog.
|
||||||
|
/// If all attributes are valid, it calls the `_createGame` method.
|
||||||
|
void _checkAllGameAttributes() {
|
||||||
|
if (_gameTitleTextController.text == '') {
|
||||||
|
_showFeedbackDialog(CreateStatus.noGameTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameMode == GameMode.none) {
|
||||||
|
_showFeedbackDialog(CreateStatus.noModeSelected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_playerNameTextControllers.length < 2) {
|
||||||
|
_showFeedbackDialog(CreateStatus.minPlayers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_everyPlayerHasAName()) {
|
||||||
|
_showFeedbackDialog(CreateStatus.noPlayerName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_createGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if every player has a name.
|
||||||
|
/// Returns true if all players have a name, false otherwise.
|
||||||
|
bool _everyPlayerHasAName() {
|
||||||
|
for (var controller in _playerNameTextControllers) {
|
||||||
|
if (controller.text == '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays a feedback dialog based on the [CreateStatus].
|
/// Displays a feedback dialog based on the [CreateStatus].
|
||||||
void showFeedbackDialog(CreateStatus status) {
|
void _showFeedbackDialog(CreateStatus status) {
|
||||||
final (title, message) = _getDialogContent(status);
|
final (title, message) = _getDialogContent(status);
|
||||||
|
|
||||||
showCupertinoDialog(
|
showCupertinoDialog(
|
||||||
@@ -326,15 +422,36 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if every player has a name.
|
/// Creates a new gameSession and navigates to the active game view.
|
||||||
/// Returns true if all players have a name, false otherwise.
|
/// This method creates a new gameSession object with the provided attributes in the text fields.
|
||||||
bool everyPlayerHasAName() {
|
/// It then adds the game session to the game manager and navigates to the active game view.
|
||||||
|
void _createGame() {
|
||||||
|
var uuid = const Uuid();
|
||||||
|
final String id = uuid.v1();
|
||||||
|
|
||||||
|
List<String> players = [];
|
||||||
for (var controller in _playerNameTextControllers) {
|
for (var controller in _playerNameTextControllers) {
|
||||||
if (controller.text == '') {
|
players.add(controller.text);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
bool isPointsLimitEnabled = gameMode == GameMode.pointLimit;
|
||||||
|
|
||||||
|
GameSession gameSession = GameSession(
|
||||||
|
id: id,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
gameTitle: _gameTitleTextController.text,
|
||||||
|
players: players,
|
||||||
|
pointLimit: ConfigService.getPointLimit(),
|
||||||
|
caboPenalty: ConfigService.getCaboPenalty(),
|
||||||
|
isPointsLimitEnabled: isPointsLimitEnabled,
|
||||||
|
);
|
||||||
|
gameManager.addGameSession(gameSession);
|
||||||
|
final session = gameManager.getGameSessionById(id) ?? gameSession;
|
||||||
|
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => ActiveGameView(gameSession: session)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -343,6 +460,9 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
for (var controller in _playerNameTextControllers) {
|
for (var controller in _playerNameTextControllers) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
|
for (var focusnode in _playerNameFocusNodes) {
|
||||||
|
focusnode.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,46 +27,61 @@ class _GraphViewState extends State<GraphView> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text(AppLocalizations.of(context).game_process),
|
middle: Text(AppLocalizations.of(context).scoring_history),
|
||||||
previousPageTitle: AppLocalizations.of(context).back,
|
previousPageTitle: AppLocalizations.of(context).back,
|
||||||
),
|
),
|
||||||
child: widget.gameSession.roundNumber > 1
|
child: Visibility(
|
||||||
? Padding(
|
visible: widget.gameSession.roundNumber > 1 ||
|
||||||
padding: const EdgeInsets.fromLTRB(0, 100, 0, 0),
|
widget.gameSession.isGameFinished,
|
||||||
child: SfCartesianChart(
|
replacement: Column(
|
||||||
legend: const Legend(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
overflowMode: LegendItemOverflowMode.wrap,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
isVisible: true,
|
children: [
|
||||||
position: LegendPosition.bottom),
|
const Center(
|
||||||
primaryXAxis: const NumericAxis(
|
child: Icon(CupertinoIcons.chart_bar_alt_fill, size: 60),
|
||||||
interval: 1,
|
),
|
||||||
decimalPlaces: 0,
|
const SizedBox(height: 10),
|
||||||
),
|
Padding(
|
||||||
primaryYAxis: const NumericAxis(
|
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
interval: 1,
|
child: Text(
|
||||||
decimalPlaces: 0,
|
AppLocalizations.of(context).empty_graph_text,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
series: getCumulativeScores(),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: Column(
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.fromLTRB(0, 100, 0, 0),
|
||||||
const Center(
|
child: SfCartesianChart(
|
||||||
child: Icon(CupertinoIcons.chart_bar_alt_fill, size: 60),
|
enableAxisAnimation: true,
|
||||||
),
|
legend: const Legend(
|
||||||
const SizedBox(height: 10),
|
overflowMode: LegendItemOverflowMode.wrap,
|
||||||
Padding(
|
isVisible: true,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
position: LegendPosition.bottom),
|
||||||
child: Text(
|
primaryXAxis: const NumericAxis(
|
||||||
AppLocalizations.of(context).empty_graph_text,
|
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.center,
|
interval: 1,
|
||||||
style: const TextStyle(fontSize: 16),
|
decimalPlaces: 0,
|
||||||
),
|
),
|
||||||
),
|
primaryYAxis: NumericAxis(
|
||||||
],
|
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
));
|
labelAlignment: LabelAlignment.center,
|
||||||
|
labelPosition: ChartDataLabelPosition.inside,
|
||||||
|
interval: 1,
|
||||||
|
decimalPlaces: 0,
|
||||||
|
axisLabelFormatter: (AxisLabelRenderDetails details) {
|
||||||
|
if (details.value == 0) {
|
||||||
|
return ChartAxisLabel('', const TextStyle());
|
||||||
|
}
|
||||||
|
return ChartAxisLabel(
|
||||||
|
'${details.value.toInt()}', const TextStyle());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
series: getCumulativeScores(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of LineSeries representing the cumulative scores of each player.
|
/// Returns a list of LineSeries representing the cumulative scores of each player.
|
||||||
|
|||||||
@@ -58,162 +58,167 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
listenable: gameManager,
|
listenable: gameManager,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) => const SettingsView(),
|
|
||||||
),
|
|
||||||
).then((_) {
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
|
||||||
middle: const Text('Cabo Counter'),
|
|
||||||
trailing: IconButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
builder: (context) => const CreateGameView(),
|
builder: (context) => const SettingsView(),
|
||||||
),
|
),
|
||||||
|
).then((_) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
||||||
|
middle: Text(AppLocalizations.of(context).app_name),
|
||||||
|
trailing: IconButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => CreateGameView(
|
||||||
|
gameMode: ConfigService.getGameMode()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: const Icon(CupertinoIcons.add)),
|
||||||
|
),
|
||||||
|
child: CupertinoPageScaffold(
|
||||||
|
child: SafeArea(
|
||||||
|
child: Visibility(
|
||||||
|
visible: _isLoading,
|
||||||
|
replacement: Visibility(
|
||||||
|
visible: gameManager.gameList.isEmpty,
|
||||||
|
replacement: ListView.separated(
|
||||||
|
itemCount: gameManager.gameList.length,
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
height: 1,
|
||||||
|
thickness: 0.5,
|
||||||
|
color: CustomTheme.white.withAlpha(50),
|
||||||
|
indent: 50,
|
||||||
|
endIndent: 50,
|
||||||
),
|
),
|
||||||
icon: const Icon(CupertinoIcons.add)),
|
itemBuilder: (context, index) {
|
||||||
),
|
final session = gameManager.gameList[index];
|
||||||
child: CupertinoPageScaffold(
|
return ListenableBuilder(
|
||||||
child: SafeArea(
|
listenable: session,
|
||||||
child: _isLoading
|
builder: (context, _) {
|
||||||
? const Center(child: CupertinoActivityIndicator())
|
return Dismissible(
|
||||||
: gameManager.gameList.isEmpty
|
key: Key(session.id),
|
||||||
? Column(
|
background: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
color: CupertinoColors.destructiveRed,
|
||||||
children: [
|
alignment: Alignment.centerRight,
|
||||||
const SizedBox(height: 30),
|
padding: const EdgeInsets.only(right: 20.0),
|
||||||
Center(
|
child: const Icon(
|
||||||
child: GestureDetector(
|
CupertinoIcons.delete,
|
||||||
onTap: () => Navigator.push(
|
color: CupertinoColors.white,
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
const CreateGameView(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Icon(
|
direction: DismissDirection.endToStart,
|
||||||
CupertinoIcons.plus,
|
confirmDismiss: (direction) async {
|
||||||
size: 60,
|
return await _showDeleteGamePopup(
|
||||||
color: CustomTheme.primaryColor,
|
context, session.gameTitle);
|
||||||
),
|
},
|
||||||
)),
|
onDismissed: (direction) {
|
||||||
const SizedBox(height: 10),
|
gameManager.removeGameSessionById(session.id);
|
||||||
Padding(
|
},
|
||||||
padding:
|
dismissThresholds: const {
|
||||||
const EdgeInsets.symmetric(horizontal: 70),
|
DismissDirection.startToEnd: 0.6
|
||||||
child: Text(
|
},
|
||||||
'${AppLocalizations.of(context).empty_text_1}\n${AppLocalizations.of(context).empty_text_2}',
|
child: Padding(
|
||||||
textAlign: TextAlign.center,
|
padding: const EdgeInsets.symmetric(
|
||||||
style: const TextStyle(fontSize: 16),
|
vertical: 10.0),
|
||||||
),
|
child: CupertinoListTile(
|
||||||
),
|
backgroundColorActivated:
|
||||||
],
|
CustomTheme.backgroundColor,
|
||||||
)
|
title: Text(session.gameTitle),
|
||||||
: ListView.builder(
|
subtitle: Visibility(
|
||||||
itemCount: gameManager.gameList.length,
|
visible: session.isGameFinished,
|
||||||
itemBuilder: (context, index) {
|
replacement: Text(
|
||||||
final session = gameManager.gameList[index];
|
'${AppLocalizations.of(context).mode}: ${_translateGameMode(session.isPointsLimitEnabled)}',
|
||||||
return ListenableBuilder(
|
style: const TextStyle(fontSize: 14),
|
||||||
listenable: session,
|
|
||||||
builder: (context, _) {
|
|
||||||
return Dismissible(
|
|
||||||
key: Key(session.gameTitle),
|
|
||||||
background: Container(
|
|
||||||
color: CupertinoColors.destructiveRed,
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(right: 20.0),
|
|
||||||
child: const Icon(
|
|
||||||
CupertinoIcons.delete,
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
),
|
),
|
||||||
),
|
child: Text(
|
||||||
direction: DismissDirection.endToStart,
|
'\u{1F947} ${session.winner}',
|
||||||
confirmDismiss: (direction) async {
|
style: const TextStyle(fontSize: 14),
|
||||||
final String gameTitle = gameManager
|
)),
|
||||||
.gameList[index].gameTitle;
|
trailing: Row(
|
||||||
return await _showDeleteGamePopup(
|
children: [
|
||||||
context, gameTitle);
|
const SizedBox(
|
||||||
},
|
width: 5,
|
||||||
onDismissed: (direction) {
|
|
||||||
gameManager
|
|
||||||
.removeGameSessionByIndex(index);
|
|
||||||
},
|
|
||||||
dismissThresholds: const {
|
|
||||||
DismissDirection.startToEnd: 0.6
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 10.0),
|
|
||||||
child: CupertinoListTile(
|
|
||||||
backgroundColorActivated:
|
|
||||||
CustomTheme.backgroundColor,
|
|
||||||
title: Text(session.gameTitle),
|
|
||||||
subtitle:
|
|
||||||
session.isGameFinished == true
|
|
||||||
? Text(
|
|
||||||
'\u{1F947} ${session.winner}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
'${AppLocalizations.of(context).mode}: ${_translateGameMode(session.isPointsLimitEnabled)}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
children: [
|
|
||||||
Text('${session.roundNumber}'),
|
|
||||||
const SizedBox(width: 3),
|
|
||||||
const Icon(CupertinoIcons
|
|
||||||
.arrow_2_circlepath_circle_fill),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
Text('${session.players.length}'),
|
|
||||||
const SizedBox(width: 3),
|
|
||||||
const Icon(
|
|
||||||
CupertinoIcons.person_2_fill),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
final session =
|
|
||||||
gameManager.gameList[index];
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
ActiveGameView(
|
|
||||||
gameSession: session),
|
|
||||||
),
|
|
||||||
).then((_) {
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
Text('${session.roundNumber}'),
|
||||||
);
|
const SizedBox(width: 3),
|
||||||
});
|
const Icon(CupertinoIcons
|
||||||
},
|
.arrow_2_circlepath_circle_fill),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Text('${session.players.length}'),
|
||||||
|
const SizedBox(width: 3),
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.person_2_fill),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
final session =
|
||||||
|
gameManager.gameList[index];
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => ActiveGameView(
|
||||||
|
gameSession: session),
|
||||||
|
),
|
||||||
|
).then((_) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => CreateGameView(
|
||||||
|
gameMode: ConfigService.getGameMode()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Icon(
|
||||||
),
|
CupertinoIcons.plus,
|
||||||
);
|
size: 60,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 70),
|
||||||
|
child: Text(
|
||||||
|
'${AppLocalizations.of(context).empty_text_1}\n${AppLocalizations.of(context).empty_text_2}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Center(child: CupertinoActivityIndicator()),
|
||||||
|
),
|
||||||
|
)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates the game mode boolean into the corresponding String.
|
/// Translates the game mode boolean into the corresponding String.
|
||||||
/// 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 isPointLimitEnabled) {
|
||||||
if (pointLimit) {
|
if (isPointLimitEnabled) {
|
||||||
return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}';
|
return '${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}';
|
||||||
}
|
}
|
||||||
return AppLocalizations.of(context).unlimited;
|
return AppLocalizations.of(context).unlimited;
|
||||||
}
|
}
|
||||||
@@ -237,7 +242,7 @@ class _MainMenuViewState extends State<MainMenuView> {
|
|||||||
BadRatingDialogDecision badRatingDecision = BadRatingDialogDecision.cancel;
|
BadRatingDialogDecision badRatingDecision = BadRatingDialogDecision.cancel;
|
||||||
|
|
||||||
// so that the bad rating dialog is not shown immediately
|
// so that the bad rating dialog is not shown immediately
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: Constants.popUpDelay));
|
||||||
|
|
||||||
switch (preRatingDecision) {
|
switch (preRatingDecision) {
|
||||||
case PreRatingDialogDecision.yes:
|
case PreRatingDialogDecision.yes:
|
||||||
|
|||||||
@@ -2,9 +2,17 @@ import 'package:cabo_counter/core/custom_theme.dart';
|
|||||||
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
enum GameMode {
|
||||||
|
none,
|
||||||
|
pointLimit,
|
||||||
|
unlimited,
|
||||||
|
}
|
||||||
|
|
||||||
class ModeSelectionMenu extends StatelessWidget {
|
class ModeSelectionMenu extends StatelessWidget {
|
||||||
final int pointLimit;
|
final int pointLimit;
|
||||||
const ModeSelectionMenu({super.key, required this.pointLimit});
|
final bool showDeselection;
|
||||||
|
const ModeSelectionMenu(
|
||||||
|
{super.key, required this.pointLimit, required this.showDeselection});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -26,12 +34,12 @@ class ModeSelectionMenu extends StatelessWidget {
|
|||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, GameMode.pointLimit);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.fromLTRB(0, 16, 0, 0),
|
||||||
child: CupertinoListTile(
|
child: CupertinoListTile(
|
||||||
title: Text(AppLocalizations.of(context).unlimited,
|
title: Text(AppLocalizations.of(context).unlimited,
|
||||||
style: CustomTheme.modeTitle),
|
style: CustomTheme.modeTitle),
|
||||||
@@ -41,10 +49,27 @@ class ModeSelectionMenu extends StatelessWidget {
|
|||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, false);
|
Navigator.pop(context, GameMode.unlimited);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: showDeselection,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 16, 0, 0),
|
||||||
|
child: CupertinoListTile(
|
||||||
|
title: Text(AppLocalizations.of(context).no_default_mode,
|
||||||
|
style: CustomTheme.modeTitle),
|
||||||
|
subtitle: Text(
|
||||||
|
AppLocalizations.of(context).no_default_description,
|
||||||
|
style: CustomTheme.modeDescription,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context, GameMode.none);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
141
lib/presentation/views/points_view.dart
Normal file
141
lib/presentation/views/points_view.dart
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
|
import 'package:cabo_counter/data/game_session.dart';
|
||||||
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PointsView extends StatefulWidget {
|
||||||
|
final GameSession gameSession;
|
||||||
|
|
||||||
|
const PointsView({super.key, required this.gameSession});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PointsView> createState() => _PointsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PointsViewState extends State<PointsView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CupertinoPageScaffold(
|
||||||
|
navigationBar: CupertinoNavigationBar(
|
||||||
|
middle: Text(AppLocalizations.of(context).point_overview),
|
||||||
|
previousPageTitle: AppLocalizations.of(context).back,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 100, 0, 0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: DataTable(
|
||||||
|
dataRowMinHeight: 60,
|
||||||
|
dataRowMaxHeight: 60,
|
||||||
|
dividerThickness: 0.5,
|
||||||
|
columnSpacing: 20,
|
||||||
|
columns: [
|
||||||
|
const DataColumn(
|
||||||
|
numeric: true,
|
||||||
|
headingRowAlignment: MainAxisAlignment.center,
|
||||||
|
label: Text(
|
||||||
|
'#',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
columnWidth: IntrinsicColumnWidth(flex: 0.5)),
|
||||||
|
...widget.gameSession.players.map(
|
||||||
|
(player) => DataColumn(
|
||||||
|
label: FittedBox(
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
child: Text(
|
||||||
|
player,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
)),
|
||||||
|
headingRowAlignment: MainAxisAlignment.center,
|
||||||
|
columnWidth: const IntrinsicColumnWidth(flex: 1)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: [
|
||||||
|
...List<DataRow>.generate(
|
||||||
|
widget.gameSession.roundList.length,
|
||||||
|
(roundIndex) {
|
||||||
|
final round = widget.gameSession.roundList[roundIndex];
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'${roundIndex + 1}',
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
...List.generate(widget.gameSession.players.length,
|
||||||
|
(playerIndex) {
|
||||||
|
final int score = round.scores[playerIndex];
|
||||||
|
final int update = round.scoreUpdates[playerIndex];
|
||||||
|
final bool saidCabo =
|
||||||
|
round.caboPlayerIndex == playerIndex;
|
||||||
|
return DataCell(
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: update <= 0
|
||||||
|
? CustomTheme.pointLossColor
|
||||||
|
: CustomTheme.pointGainColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${update >= 0 ? '+' : ''}$update',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text('$score',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: saidCabo
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DataRow(
|
||||||
|
cells: [
|
||||||
|
const DataCell(Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'Σ',
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
...widget.gameSession.playerScores.map(
|
||||||
|
(score) => DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'$score',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:cabo_counter/core/custom_theme.dart';
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
import 'package:cabo_counter/data/game_session.dart';
|
import 'package:cabo_counter/data/game_session.dart';
|
||||||
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:cabo_counter/presentation/widgets/custom_button.dart';
|
||||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -74,21 +75,22 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
transitionBetweenRoutes: true,
|
transitionBetweenRoutes: true,
|
||||||
leading: CupertinoButton(
|
leading: CupertinoButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () =>
|
onPressed: () => {
|
||||||
{LocalStorageService.saveGameSessions(), Navigator.pop(context)},
|
LocalStorageService.saveGameSessions(),
|
||||||
child: Text(AppLocalizations.of(context).cancel),
|
Navigator.pop(context)
|
||||||
),
|
},
|
||||||
middle: Text(AppLocalizations.of(context).results),
|
child: Text(AppLocalizations.of(context).cancel),
|
||||||
trailing: widget.gameSession.isGameFinished
|
),
|
||||||
? const Icon(
|
middle: Text(AppLocalizations.of(context).results),
|
||||||
|
trailing: Visibility(
|
||||||
|
visible: widget.gameSession.isGameFinished,
|
||||||
|
child: const Icon(
|
||||||
CupertinoIcons.lock,
|
CupertinoIcons.lock,
|
||||||
size: 25,
|
size: 25,
|
||||||
)
|
))),
|
||||||
: null,
|
|
||||||
),
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
@@ -114,9 +116,10 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
vertical: 10,
|
vertical: 10,
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 60,
|
||||||
child: CupertinoSegmentedControl<int>(
|
child: CupertinoSegmentedControl<int>(
|
||||||
unselectedColor: CustomTheme.backgroundTintColor,
|
unselectedColor:
|
||||||
|
CustomTheme.mainElementBackgroundColor,
|
||||||
selectedColor: CustomTheme.primaryColor,
|
selectedColor: CustomTheme.primaryColor,
|
||||||
groupValue: _caboPlayerIndex,
|
groupValue: _caboPlayerIndex,
|
||||||
children: Map.fromEntries(widget.gameSession.players
|
children: Map.fromEntries(widget.gameSession.players
|
||||||
@@ -130,7 +133,7 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 6,
|
horizontal: 6,
|
||||||
vertical: 6,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
@@ -154,27 +157,6 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: CupertinoListTile(
|
|
||||||
title: Text(AppLocalizations.of(context).player),
|
|
||||||
trailing: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 100,
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context).points))),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: 80,
|
|
||||||
child: Center(
|
|
||||||
child: Text(AppLocalizations.of(context)
|
|
||||||
.kamikaze))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
@@ -182,13 +164,15 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final originalIndex = originalIndices[index];
|
final originalIndex = originalIndices[index];
|
||||||
final name = rotatedPlayers[index];
|
final name = rotatedPlayers[index];
|
||||||
|
bool shouldShowMedal =
|
||||||
|
index == 0 && widget.roundNumber > 1;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 10, horizontal: 20),
|
vertical: 10, horizontal: 20),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: CupertinoListTile(
|
child: CupertinoListTile(
|
||||||
backgroundColor: CupertinoColors.secondaryLabel,
|
backgroundColor: CustomTheme.playerTileColor,
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
@@ -197,95 +181,70 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: index == 0,
|
visible: shouldShowMedal,
|
||||||
child: const SizedBox(width: 10),
|
child: const SizedBox(width: 10),
|
||||||
),
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: index == 0,
|
visible: shouldShowMedal,
|
||||||
child: const Icon(FontAwesomeIcons.medal,
|
child: const Icon(FontAwesomeIcons.crown,
|
||||||
size: 15))
|
size: 15))
|
||||||
]))
|
]))
|
||||||
]),
|
]),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${widget.gameSession.playerScores[originalIndex]}'
|
'${widget.gameSession.playerScores[originalIndex]}'
|
||||||
' ${AppLocalizations.of(context).points}'),
|
' ${AppLocalizations.of(context).points}'),
|
||||||
trailing: Row(
|
trailing: SizedBox(
|
||||||
children: [
|
width: 100,
|
||||||
SizedBox(
|
child: CupertinoTextField(
|
||||||
width: 100,
|
maxLength: 3,
|
||||||
child: CupertinoTextField(
|
focusNode: _focusNodeList[originalIndex],
|
||||||
maxLength: 3,
|
keyboardType:
|
||||||
focusNode: _focusNodeList[originalIndex],
|
const TextInputType.numberWithOptions(
|
||||||
keyboardType:
|
signed: true,
|
||||||
const TextInputType.numberWithOptions(
|
decimal: false,
|
||||||
signed: true,
|
|
||||||
decimal: false,
|
|
||||||
),
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
textInputAction: index ==
|
|
||||||
widget.gameSession.players
|
|
||||||
.length -
|
|
||||||
1
|
|
||||||
? TextInputAction.done
|
|
||||||
: TextInputAction.next,
|
|
||||||
controller:
|
|
||||||
_scoreControllerList[originalIndex],
|
|
||||||
placeholder:
|
|
||||||
AppLocalizations.of(context).points,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
onSubmitted: (_) =>
|
|
||||||
_focusNextTextfield(originalIndex),
|
|
||||||
onChanged: (_) => setState(() {}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 50),
|
inputFormatters: [
|
||||||
GestureDetector(
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
onTap: () {
|
],
|
||||||
setState(() {
|
textInputAction: index ==
|
||||||
_kamikazePlayerIndex =
|
widget.gameSession.players.length - 1
|
||||||
(_kamikazePlayerIndex ==
|
? TextInputAction.done
|
||||||
originalIndex)
|
: TextInputAction.next,
|
||||||
? null
|
controller:
|
||||||
: originalIndex;
|
_scoreControllerList[originalIndex],
|
||||||
});
|
placeholder:
|
||||||
},
|
AppLocalizations.of(context).points,
|
||||||
child: Container(
|
textAlign: TextAlign.center,
|
||||||
width: 24,
|
onSubmitted: (_) =>
|
||||||
height: 24,
|
_focusNextTextfield(originalIndex),
|
||||||
decoration: BoxDecoration(
|
onChanged: (_) => setState(() {}),
|
||||||
shape: BoxShape.circle,
|
),
|
||||||
color: _kamikazePlayerIndex ==
|
|
||||||
originalIndex
|
|
||||||
? CupertinoColors.systemRed
|
|
||||||
: CupertinoColors
|
|
||||||
.tertiarySystemFill,
|
|
||||||
border: Border.all(
|
|
||||||
color: _kamikazePlayerIndex ==
|
|
||||||
originalIndex
|
|
||||||
? CupertinoColors.systemRed
|
|
||||||
: CupertinoColors.systemGrey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _kamikazePlayerIndex ==
|
|
||||||
originalIndex
|
|
||||||
? const Icon(
|
|
||||||
CupertinoIcons.exclamationmark,
|
|
||||||
size: 16,
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 22),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||||
|
child: Center(
|
||||||
|
heightFactor: 1,
|
||||||
|
child: CustomButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (await _showKamikazeSheet(context)) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
_endOfRoundNavigation(context, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).kamikaze,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CupertinoColors.destructiveRed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -300,21 +259,14 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 80,
|
height: 80,
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
color: CustomTheme.backgroundTintColor,
|
color: CustomTheme.mainElementBackgroundColor,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
onPressed: _areRoundInputsValid()
|
onPressed: _areRoundInputsValid()
|
||||||
? () async {
|
? () {
|
||||||
List<int> bonusPlayersIndices = _finishRound();
|
_endOfRoundNavigation(context, false);
|
||||||
if (bonusPlayersIndices.isNotEmpty) {
|
|
||||||
await _showBonusPopup(
|
|
||||||
context, bonusPlayersIndices);
|
|
||||||
}
|
|
||||||
LocalStorageService.saveGameSessions();
|
|
||||||
if (!context.mounted) return;
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Text(AppLocalizations.of(context).done),
|
child: Text(AppLocalizations.of(context).done),
|
||||||
@@ -322,21 +274,8 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
if (!widget.gameSession.isGameFinished)
|
if (!widget.gameSession.isGameFinished)
|
||||||
CupertinoButton(
|
CupertinoButton(
|
||||||
onPressed: _areRoundInputsValid()
|
onPressed: _areRoundInputsValid()
|
||||||
? () async {
|
? () {
|
||||||
List<int> bonusPlayersIndices =
|
_endOfRoundNavigation(context, true);
|
||||||
_finishRound();
|
|
||||||
if (bonusPlayersIndices.isNotEmpty) {
|
|
||||||
await _showBonusPopup(
|
|
||||||
context, bonusPlayersIndices);
|
|
||||||
}
|
|
||||||
LocalStorageService.saveGameSessions();
|
|
||||||
if (widget.gameSession.isGameFinished &&
|
|
||||||
context.mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
} else if (context.mounted) {
|
|
||||||
Navigator.pop(
|
|
||||||
context, widget.roundNumber + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Text(AppLocalizations.of(context).next_round),
|
child: Text(AppLocalizations.of(context).next_round),
|
||||||
@@ -399,6 +338,37 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a Cupertino action sheet to select the player who has Kamikaze.
|
||||||
|
/// It returns true if a player was selected, false if the action was cancelled.
|
||||||
|
Future<bool> _showKamikazeSheet(BuildContext context) async {
|
||||||
|
return await showCupertinoModalPopup<bool?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CupertinoActionSheet(
|
||||||
|
title: Text(AppLocalizations.of(context).kamikaze),
|
||||||
|
message: Text(AppLocalizations.of(context).who_has_kamikaze),
|
||||||
|
actions: widget.gameSession.players.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final name = entry.value;
|
||||||
|
return CupertinoActionSheetAction(
|
||||||
|
onPressed: () {
|
||||||
|
_kamikazePlayerIndex = index;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: Text(name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: Text(AppLocalizations.of(context).cancel),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Focuses the next text field in the list of text fields.
|
/// Focuses the next text field in the list of text fields.
|
||||||
/// [index] is the index of the current text field.
|
/// [index] is the index of the current text field.
|
||||||
void _focusNextTextfield(int index) {
|
void _focusNextTextfield(int index) {
|
||||||
@@ -469,10 +439,9 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
return bonusPlayers;
|
return bonusPlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a popup dialog with the bonus information.
|
/// Shows a popup dialog with the information which player received the bonus points.
|
||||||
Future<void> _showBonusPopup(
|
Future<void> _showBonusPopup(
|
||||||
BuildContext context, List<int> bonusPlayers) async {
|
BuildContext context, List<int> bonusPlayers) async {
|
||||||
print('Bonus Popup wird angezeigt');
|
|
||||||
int pointLimit = widget.gameSession.pointLimit;
|
int pointLimit = widget.gameSession.pointLimit;
|
||||||
int bonusPoints = (pointLimit / 2).round();
|
int bonusPoints = (pointLimit / 2).round();
|
||||||
|
|
||||||
@@ -519,6 +488,37 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
return resultText;
|
return resultText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles the navigation for the end of the round.
|
||||||
|
/// It checks for bonus players and shows a popup, saves the game session,
|
||||||
|
/// and navigates to the next round or back to the previous screen.
|
||||||
|
/// It takes the BuildContext [context] and a boolean [navigateToNextRound] to determine
|
||||||
|
/// if it should navigate to the next round or not.
|
||||||
|
Future<void> _endOfRoundNavigation(
|
||||||
|
BuildContext context, bool navigateToNextRound) async {
|
||||||
|
List<int> bonusPlayersIndices = _finishRound();
|
||||||
|
if (bonusPlayersIndices.isNotEmpty) {
|
||||||
|
await _showBonusPopup(context, bonusPlayersIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalStorageService.saveGameSessions();
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
// If the game is finished, pop the context and return to the previous screen.
|
||||||
|
if (widget.gameSession.isGameFinished) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If navigateToNextRound is false, pop the context and return to the previous screen.
|
||||||
|
if (!navigateToNextRound) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If navigateToNextRound is true and the game isn't finished yet,
|
||||||
|
// pop the context and navigate to the next round.
|
||||||
|
Navigator.pop(context, widget.roundNumber + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
for (final controller in _scoreControllerList) {
|
for (final controller in _scoreControllerList) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:cabo_counter/core/constants.dart';
|
import 'package:cabo_counter/core/constants.dart';
|
||||||
import 'package:cabo_counter/core/custom_theme.dart';
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
|
||||||
import 'package:cabo_counter/presentation/widgets/custom_form_row.dart';
|
import 'package:cabo_counter/presentation/widgets/custom_form_row.dart';
|
||||||
import 'package:cabo_counter/presentation/widgets/custom_stepper.dart';
|
import 'package:cabo_counter/presentation/widgets/custom_stepper.dart';
|
||||||
import 'package:cabo_counter/services/config_service.dart';
|
import 'package:cabo_counter/services/config_service.dart';
|
||||||
@@ -20,6 +21,7 @@ class SettingsView extends StatefulWidget {
|
|||||||
class _SettingsViewState extends State<SettingsView> {
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
UniqueKey _stepperKey1 = UniqueKey();
|
UniqueKey _stepperKey1 = UniqueKey();
|
||||||
UniqueKey _stepperKey2 = UniqueKey();
|
UniqueKey _stepperKey2 = UniqueKey();
|
||||||
|
GameMode defaultMode = ConfigService.getGameMode();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -55,14 +57,13 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
prefixIcon: CupertinoIcons.bolt_fill,
|
prefixIcon: CupertinoIcons.bolt_fill,
|
||||||
suffixWidget: CustomStepper(
|
suffixWidget: CustomStepper(
|
||||||
key: _stepperKey1,
|
key: _stepperKey1,
|
||||||
initialValue: ConfigService.caboPenalty,
|
initialValue: ConfigService.getCaboPenalty(),
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 50,
|
maxValue: 50,
|
||||||
step: 1,
|
step: 1,
|
||||||
onChanged: (newCaboPenalty) {
|
onChanged: (newCaboPenalty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
ConfigService.setCaboPenalty(newCaboPenalty);
|
ConfigService.setCaboPenalty(newCaboPenalty);
|
||||||
ConfigService.caboPenalty = newCaboPenalty;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -72,18 +73,51 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
prefixIcon: FontAwesomeIcons.bullseye,
|
prefixIcon: FontAwesomeIcons.bullseye,
|
||||||
suffixWidget: CustomStepper(
|
suffixWidget: CustomStepper(
|
||||||
key: _stepperKey2,
|
key: _stepperKey2,
|
||||||
initialValue: ConfigService.pointLimit,
|
initialValue: ConfigService.getPointLimit(),
|
||||||
minValue: 30,
|
minValue: 30,
|
||||||
maxValue: 1000,
|
maxValue: 1000,
|
||||||
step: 10,
|
step: 10,
|
||||||
onChanged: (newPointLimit) {
|
onChanged: (newPointLimit) {
|
||||||
setState(() {
|
setState(() {
|
||||||
ConfigService.setPointLimit(newPointLimit);
|
ConfigService.setPointLimit(newPointLimit);
|
||||||
ConfigService.pointLimit = newPointLimit;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
CustomFormRow(
|
||||||
|
prefixText: AppLocalizations.of(context).standard_mode,
|
||||||
|
prefixIcon: CupertinoIcons.square_stack,
|
||||||
|
suffixWidget: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
defaultMode == GameMode.none
|
||||||
|
? AppLocalizations.of(context).no_default_mode
|
||||||
|
: (defaultMode == GameMode.pointLimit
|
||||||
|
? '${ConfigService.getPointLimit()} ${AppLocalizations.of(context).points}'
|
||||||
|
: AppLocalizations.of(context).unlimited),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
const CupertinoListTileChevron()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final selectedMode = await Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
builder: (context) => ModeSelectionMenu(
|
||||||
|
pointLimit: ConfigService.getPointLimit(),
|
||||||
|
showDeselection: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
defaultMode = selectedMode ?? GameMode.none;
|
||||||
|
});
|
||||||
|
ConfigService.setGameMode(defaultMode);
|
||||||
|
},
|
||||||
|
),
|
||||||
CustomFormRow(
|
CustomFormRow(
|
||||||
prefixText:
|
prefixText:
|
||||||
AppLocalizations.of(context).reset_to_default,
|
AppLocalizations.of(context).reset_to_default,
|
||||||
@@ -93,6 +127,7 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_stepperKey1 = UniqueKey();
|
_stepperKey1 = UniqueKey();
|
||||||
_stepperKey2 = UniqueKey();
|
_stepperKey2 = UniqueKey();
|
||||||
|
defaultMode = ConfigService.getGameMode();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class _TabViewState extends State<TabView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CupertinoTabScaffold(
|
return CupertinoTabScaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
tabBar: CupertinoTabBar(
|
tabBar: CupertinoTabBar(
|
||||||
backgroundColor: CustomTheme.backgroundTintColor,
|
backgroundColor: CustomTheme.mainElementBackgroundColor,
|
||||||
iconSize: 27,
|
iconSize: 27,
|
||||||
height: 55,
|
height: 55,
|
||||||
items: <BottomNavigationBarItem>[
|
items: <BottomNavigationBarItem>[
|
||||||
|
|||||||
19
lib/presentation/widgets/custom_button.dart
Normal file
19
lib/presentation/widgets/custom_button.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:cabo_counter/core/custom_theme.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class CustomButton extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
const CustomButton({super.key, required this.child, this.onPressed});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CupertinoButton(
|
||||||
|
sizeStyle: CupertinoButtonSize.medium,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: CustomTheme.buttonBackgroundColor,
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:cabo_counter/presentation/views/mode_selection_view.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.
|
||||||
@@ -6,53 +7,101 @@ 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 String _keyGameMode = 'gameMode';
|
||||||
// Actual values used in the app
|
// Actual values used in the app
|
||||||
static int pointLimit = 100;
|
static int _pointLimit = 100;
|
||||||
static int caboPenalty = 5;
|
static int _caboPenalty = 5;
|
||||||
|
static int _gameMode = -1;
|
||||||
// Default values
|
// Default values
|
||||||
static const int _defaultPointLimit = 100;
|
static const int _defaultPointLimit = 100;
|
||||||
static const int _defaultCaboPenalty = 5;
|
static const int _defaultCaboPenalty = 5;
|
||||||
|
static const int _defaultGameMode = -1;
|
||||||
|
|
||||||
static Future<void> initConfig() async {
|
static Future<void> initConfig() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
// Default values only set if they are not already set
|
// Initialize pointLimit, caboPenalty, and gameMode from SharedPreferences
|
||||||
prefs.setInt(
|
// If they are not set, use the default values
|
||||||
_keyPointLimit, prefs.getInt(_keyPointLimit) ?? _defaultPointLimit);
|
_pointLimit = prefs.getInt(_keyPointLimit) ?? _defaultPointLimit;
|
||||||
prefs.setInt(
|
_caboPenalty = prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty;
|
||||||
_keyCaboPenalty, prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty);
|
_gameMode = prefs.getInt(_keyGameMode) ?? _defaultGameMode;
|
||||||
|
|
||||||
|
// Save the initial values to SharedPreferences
|
||||||
|
prefs.setInt(_keyPointLimit, _pointLimit);
|
||||||
|
prefs.setInt(_keyCaboPenalty, _caboPenalty);
|
||||||
|
prefs.setInt(_keyGameMode, _gameMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Getter for the point limit.
|
/// Retrieves the current game mode.
|
||||||
static Future<int> getPointLimit() async {
|
///
|
||||||
final prefs = await SharedPreferences.getInstance();
|
/// The game mode is determined based on the stored integer value:
|
||||||
return prefs.getInt(_keyPointLimit) ?? _defaultPointLimit;
|
/// - `0`: [GameMode.pointLimit]
|
||||||
|
/// - `1`: [GameMode.unlimited]
|
||||||
|
/// - Any other value: [GameMode.none] (-1 is used as a default for no mode)
|
||||||
|
///
|
||||||
|
/// Returns the corresponding [GameMode] enum value.
|
||||||
|
static GameMode getGameMode() {
|
||||||
|
switch (_gameMode) {
|
||||||
|
case 0:
|
||||||
|
return GameMode.pointLimit;
|
||||||
|
case 1:
|
||||||
|
return GameMode.unlimited;
|
||||||
|
default:
|
||||||
|
return GameMode.none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the game mode for the application.
|
||||||
|
///
|
||||||
|
/// [newGameMode] is the new game mode to be set. It can be one of the following:
|
||||||
|
/// - `GameMode.pointLimit`: The game ends when a pleayer reaches the point limit.
|
||||||
|
/// - `GameMode.unlimited`: Every game goes for infinity until you end it.
|
||||||
|
/// - `GameMode.none`: No default mode set.
|
||||||
|
///
|
||||||
|
/// This method updates the `_gameMode` field and persists the value in `SharedPreferences`.
|
||||||
|
static Future<void> setGameMode(GameMode newGameMode) async {
|
||||||
|
int gameMode;
|
||||||
|
switch (newGameMode) {
|
||||||
|
case GameMode.pointLimit:
|
||||||
|
gameMode = 0;
|
||||||
|
break;
|
||||||
|
case GameMode.unlimited:
|
||||||
|
gameMode = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gameMode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setInt(_keyGameMode, gameMode);
|
||||||
|
_gameMode = gameMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getPointLimit() => _pointLimit;
|
||||||
|
|
||||||
/// Setter for the point limit.
|
/// Setter for the point limit.
|
||||||
/// [newPointLimit] is the new point limit to be set.
|
/// [newPointLimit] is the new point limit to be set.
|
||||||
static Future<void> setPointLimit(int newPointLimit) async {
|
static Future<void> setPointLimit(int newPointLimit) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setInt(_keyPointLimit, newPointLimit);
|
await prefs.setInt(_keyPointLimit, newPointLimit);
|
||||||
|
_pointLimit = newPointLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Getter for the cabo penalty.
|
static int getCaboPenalty() => _caboPenalty;
|
||||||
static Future<int> getCaboPenalty() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
return prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setter for the cabo penalty.
|
/// Setter for the cabo penalty.
|
||||||
/// [newCaboPenalty] is the new cabo penalty to be set.
|
/// [newCaboPenalty] is the new cabo penalty to be set.
|
||||||
static Future<void> setCaboPenalty(int newCaboPenalty) async {
|
static Future<void> setCaboPenalty(int newCaboPenalty) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setInt(_keyCaboPenalty, newCaboPenalty);
|
await prefs.setInt(_keyCaboPenalty, newCaboPenalty);
|
||||||
|
_caboPenalty = newCaboPenalty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets the configuration to default values.
|
/// Resets the configuration to default values.
|
||||||
static Future<void> resetConfig() async {
|
static Future<void> resetConfig() async {
|
||||||
ConfigService.pointLimit = _defaultPointLimit;
|
ConfigService._pointLimit = _defaultPointLimit;
|
||||||
ConfigService.caboPenalty = _defaultCaboPenalty;
|
ConfigService._caboPenalty = _defaultCaboPenalty;
|
||||||
|
ConfigService._gameMode = _defaultGameMode;
|
||||||
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);
|
||||||
|
|||||||
@@ -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.4.7+506
|
version: 0.5.3+594
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -28,6 +28,9 @@ dependencies:
|
|||||||
syncfusion_flutter_charts: ^30.1.37
|
syncfusion_flutter_charts: ^30.1.37
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
rate_my_app: ^2.3.2
|
rate_my_app: ^2.3.2
|
||||||
|
reorderables: ^0.4.2
|
||||||
|
collection: ^1.18.0
|
||||||
|
confetti: ^0.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ void main() {
|
|||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
session = GameSession(
|
session = GameSession(
|
||||||
|
id: '1',
|
||||||
createdAt: testDate,
|
createdAt: testDate,
|
||||||
gameTitle: testTitle,
|
gameTitle: testTitle,
|
||||||
players: testPlayers,
|
players: testPlayers,
|
||||||
|
|||||||
Reference in New Issue
Block a user