Implementierung der Games #203
@@ -1,4 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/models/match.dart';
|
||||
import 'package:tallee/data/models/player.dart';
|
||||
@@ -21,6 +21,51 @@ String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Translates a [GameColor] enum value to its corresponding localized string.
|
||||
String translateGameColorToString(GameColor color, BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (color) {
|
||||
case GameColor.red:
|
||||
return loc.color_red;
|
||||
case GameColor.blue:
|
||||
return loc.color_blue;
|
||||
case GameColor.green:
|
||||
return loc.color_green;
|
||||
case GameColor.yellow:
|
||||
return loc.color_yellow;
|
||||
case GameColor.purple:
|
||||
return loc.color_purple;
|
||||
case GameColor.orange:
|
||||
return loc.color_orange;
|
||||
case GameColor.pink:
|
||||
return loc.color_pink;
|
||||
case GameColor.teal:
|
||||
return loc.color_teal;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [Color] object corresponding to a [GameColor] enum value.
|
||||
Color getColorFromGameColor(GameColor color) {
|
||||
switch (color) {
|
||||
case GameColor.red:
|
||||
return Colors.red;
|
||||
case GameColor.blue:
|
||||
return Colors.blue;
|
||||
case GameColor.green:
|
||||
return Colors.green;
|
||||
case GameColor.yellow:
|
||||
return Colors.yellow;
|
||||
case GameColor.purple:
|
||||
return Colors.purple;
|
||||
case GameColor.orange:
|
||||
return Colors.orange;
|
||||
case GameColor.pink:
|
||||
return Colors.pink;
|
||||
case GameColor.teal:
|
||||
return Colors.teal;
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts how many players in the match are not part of the group
|
||||
/// Returns the count as a string, or an empty string if there is no group
|
||||
String getExtraPlayerCount(Match match) {
|
||||
|
||||
@@ -19,4 +19,7 @@ class Constants {
|
||||
|
||||
/// Maximum length for team names
|
||||
static const int MAX_TEAM_NAME_LENGTH = 32;
|
||||
|
||||
/// Maximum length for game descriptions
|
||||
static const int MAX_GAME_DESCRIPTION_LENGTH = 256;
|
||||
}
|
||||
|
||||
@@ -12,16 +12,17 @@ class Game {
|
||||
final String icon;
|
||||
|
||||
Game({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
required this.name,
|
||||
required this.ruleset,
|
||||
String? description,
|
||||
required this.color,
|
||||
required this.icon,
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
String? description,
|
||||
String? icon,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
createdAt = createdAt ?? clock.now(),
|
||||
description = description ?? '';
|
||||
description = description ?? '',
|
||||
icon = icon ?? '';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
||||
@@ -6,10 +6,21 @@
|
||||
"app_name": "Tallee",
|
||||
"best_player": "Beste:r Spieler:in",
|
||||
"cancel": "Abbrechen",
|
||||
"choose_color": "Farbe wählen",
|
||||
"choose_game": "Spielvorlage wählen",
|
||||
"choose_group": "Gruppe wählen",
|
||||
"choose_ruleset": "Regelwerk wählen",
|
||||
"color": "Farbe",
|
||||
"color_blue": "Blau",
|
||||
"color_green": "Grün",
|
||||
"color_orange": "Orange",
|
||||
"color_pink": "Rosa",
|
||||
"color_purple": "Lila",
|
||||
"color_red": "Rot",
|
||||
"color_teal": "Türkis",
|
||||
"color_yellow": "Gelb",
|
||||
"could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden",
|
||||
"create_game": "Spielvorlage erstellen",
|
||||
"create_group": "Gruppe erstellen",
|
||||
"create_match": "Spiel erstellen",
|
||||
"create_new_group": "Neue Gruppe erstellen",
|
||||
@@ -22,13 +33,17 @@
|
||||
"days_ago": "vor {count} Tagen",
|
||||
"delete": "Löschen",
|
||||
"delete_all_data": "Alle Daten löschen",
|
||||
"delete_game": "Spielvorlage löschen",
|
||||
"delete_group": "Gruppe löschen",
|
||||
"delete_match": "Spiel löschen",
|
||||
"description": "Beschreibung",
|
||||
"edit_game": "Spielvorlage bearbeiten",
|
||||
"edit_group": "Gruppe bearbeiten",
|
||||
"edit_match": "Gruppe bearbeiten",
|
||||
"enter_points": "Punkte eingeben",
|
||||
"enter_results": "Ergebnisse eintragen",
|
||||
|
sneeex marked this conversation as resolved
|
||||
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||
"error_deleting_game": "Fehler beim Löschen der Spielvorlage, bitte erneut versuchen",
|
||||
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
||||
"error_editing_group": "Fehler beim Bearbeiten der Gruppe, bitte erneut versuchen",
|
||||
"error_reading_file": "Fehler beim Lesen der Datei",
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
"@cancel": {
|
||||
"description": "Cancel button text"
|
||||
},
|
||||
"@choose_color": {
|
||||
"description": "Label for choosing a color"
|
||||
},
|
||||
"@choose_game": {
|
||||
"description": "Label for choosing a game"
|
||||
},
|
||||
@@ -27,9 +30,15 @@
|
||||
"@choose_ruleset": {
|
||||
"description": "Label for choosing a ruleset"
|
||||
},
|
||||
"@color": {
|
||||
"description": "Color label"
|
||||
},
|
||||
"@could_not_add_player": {
|
||||
"description": "Error message when adding a player fails"
|
||||
},
|
||||
"@create_game": {
|
||||
"description": "Button text to create a game"
|
||||
},
|
||||
"@create_group": {
|
||||
"description": "Button text to create a group"
|
||||
},
|
||||
@@ -71,12 +80,21 @@
|
||||
"@delete_all_data": {
|
||||
"description": "Confirmation dialog for deleting all data"
|
||||
},
|
||||
"@delete_game": {
|
||||
"description": "Button text to delete a game"
|
||||
},
|
||||
"@delete_group": {
|
||||
"description": "Confirmation dialog for deleting a group"
|
||||
},
|
||||
"@delete_match": {
|
||||
"description": "Button text to delete a match"
|
||||
},
|
||||
"@description": {
|
||||
"description": "Description label"
|
||||
},
|
||||
"@edit_game": {
|
||||
"description": "Button text to edit a game"
|
||||
},
|
||||
"@edit_group": {
|
||||
"description": "Button & Appbar label for editing a group"
|
||||
},
|
||||
@@ -92,6 +110,9 @@
|
||||
"@error_creating_group": {
|
||||
"description": "Error message when group creation fails"
|
||||
},
|
||||
"@error_deleting_game": {
|
||||
"description": "Error message when game deletion fails"
|
||||
},
|
||||
"@error_deleting_group": {
|
||||
"description": "Error message when group deletion fails"
|
||||
},
|
||||
@@ -340,10 +361,21 @@
|
||||
"app_name": "Tallee",
|
||||
"best_player": "Best Player",
|
||||
"cancel": "Cancel",
|
||||
"choose_color": "Choose Color",
|
||||
"choose_game": "Choose Game",
|
||||
"choose_group": "Choose Group",
|
||||
"choose_ruleset": "Choose Ruleset",
|
||||
"color": "Color",
|
||||
"color_blue": "Blue",
|
||||
"color_green": "Green",
|
||||
"color_orange": "Orange",
|
||||
"color_pink": "Pink",
|
||||
"color_purple": "Purple",
|
||||
"color_red": "Red",
|
||||
"color_teal": "Teal",
|
||||
"color_yellow": "Yellow",
|
||||
"could_not_add_player": "Could not add player",
|
||||
"create_game": "Create Game",
|
||||
"create_group": "Create Group",
|
||||
"create_match": "Create match",
|
||||
"create_new_group": "Create new group",
|
||||
@@ -356,13 +388,17 @@
|
||||
"days_ago": "{count} days ago",
|
||||
"delete": "Delete",
|
||||
"delete_all_data": "Delete all data",
|
||||
"delete_game": "Delete Game",
|
||||
"delete_group": "Delete Group",
|
||||
|
flixcoo marked this conversation as resolved
Outdated
gelbeinhalb
commented
hinter hinter `other` ein space vergessen?
Kenne mich nicht mit den templates aus
flixcoo
commented
Das ist richtig so, wenn count =1 dann wird Das ist richtig so, wenn count =1 dann wird `1 match` zurückgegeben, und in allen anderen fällen `{count} matches`.
|
||||
"delete_match": "Delete Match",
|
||||
"description": "Description",
|
||||
"edit_game": "Edit Game",
|
||||
"edit_group": "Edit Group",
|
||||
"edit_match": "Edit Match",
|
||||
"enter_points": "Enter points",
|
||||
"enter_results": "Enter Results",
|
||||
"error_creating_group": "Error while creating group, please try again",
|
||||
"error_deleting_game": "Error while deleting game, please try again",
|
||||
"error_deleting_group": "Error while deleting group, please try again",
|
||||
"error_editing_group": "Error while editing group, please try again",
|
||||
"error_reading_file": "Error reading file",
|
||||
|
||||
@@ -134,6 +134,12 @@ abstract class AppLocalizations {
|
||||
/// **'Cancel'**
|
||||
String get cancel;
|
||||
|
||||
/// Label for choosing a color
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose Color'**
|
||||
String get choose_color;
|
||||
|
||||
/// Label for choosing a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -152,12 +158,72 @@ abstract class AppLocalizations {
|
||||
/// **'Choose Ruleset'**
|
||||
String get choose_ruleset;
|
||||
|
||||
/// Color label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Color'**
|
||||
String get color;
|
||||
|
||||
/// No description provided for @color_blue.
|
||||
|
flixcoo marked this conversation as resolved
gelbeinhalb
commented
sind die descriptions hier nicht mies unnötig? sind die descriptions hier nicht mies unnötig?
flixcoo
commented
ja, ich entfern die mal ja, ich entfern die mal
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Blue'**
|
||||
String get color_blue;
|
||||
|
||||
/// No description provided for @color_green.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Green'**
|
||||
String get color_green;
|
||||
|
||||
/// No description provided for @color_orange.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Orange'**
|
||||
String get color_orange;
|
||||
|
||||
/// No description provided for @color_pink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Pink'**
|
||||
String get color_pink;
|
||||
|
||||
/// No description provided for @color_purple.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Purple'**
|
||||
String get color_purple;
|
||||
|
||||
/// No description provided for @color_red.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Red'**
|
||||
String get color_red;
|
||||
|
||||
/// No description provided for @color_teal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Teal'**
|
||||
String get color_teal;
|
||||
|
||||
/// No description provided for @color_yellow.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yellow'**
|
||||
String get color_yellow;
|
||||
|
||||
/// Error message when adding a player fails
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not add player'**
|
||||
String could_not_add_player(Object playerName);
|
||||
|
||||
/// Button text to create a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create Game'**
|
||||
String get create_game;
|
||||
|
||||
/// Button text to create a group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -230,6 +296,12 @@ abstract class AppLocalizations {
|
||||
/// **'Delete all data'**
|
||||
String get delete_all_data;
|
||||
|
||||
/// Button text to delete a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Game'**
|
||||
String get delete_game;
|
||||
|
||||
/// Confirmation dialog for deleting a group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -242,6 +314,18 @@ abstract class AppLocalizations {
|
||||
/// **'Delete Match'**
|
||||
String get delete_match;
|
||||
|
||||
/// Description label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Description'**
|
||||
String get description;
|
||||
|
||||
/// Button text to edit a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit Game'**
|
||||
String get edit_game;
|
||||
|
||||
/// Button & Appbar label for editing a group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -272,6 +356,12 @@ abstract class AppLocalizations {
|
||||
/// **'Error while creating group, please try again'**
|
||||
String get error_creating_group;
|
||||
|
||||
/// Error message when game deletion fails
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error while deleting game, please try again'**
|
||||
String get error_deleting_game;
|
||||
|
||||
/// Error message when group deletion fails
|
||||
|
flixcoo marked this conversation as resolved
Outdated
gelbeinhalb
commented
bitte weniger vibe coden felix 🤓 🫵 bitte weniger vibe coden felix 🤓 🫵
flixcoo
commented
hä was meinst du? :D hä was meinst du? :D
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -26,6 +26,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get cancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get choose_color => 'Farbe wählen';
|
||||
|
||||
@override
|
||||
String get choose_game => 'Spielvorlage wählen';
|
||||
|
||||
@@ -35,11 +38,41 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get choose_ruleset => 'Regelwerk wählen';
|
||||
|
||||
@override
|
||||
String get color => 'Farbe';
|
||||
|
||||
@override
|
||||
String get color_blue => 'Blau';
|
||||
|
||||
@override
|
||||
String get color_green => 'Grün';
|
||||
|
||||
@override
|
||||
String get color_orange => 'Orange';
|
||||
|
||||
@override
|
||||
String get color_pink => 'Rosa';
|
||||
|
||||
@override
|
||||
String get color_purple => 'Lila';
|
||||
|
||||
@override
|
||||
String get color_red => 'Rot';
|
||||
|
||||
@override
|
||||
String get color_teal => 'Türkis';
|
||||
|
||||
@override
|
||||
String get color_yellow => 'Gelb';
|
||||
|
||||
@override
|
||||
String could_not_add_player(Object playerName) {
|
||||
return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
|
||||
}
|
||||
|
||||
@override
|
||||
String get create_game => 'Spielvorlage erstellen';
|
||||
|
||||
@override
|
||||
String get create_group => 'Gruppe erstellen';
|
||||
|
||||
@@ -78,12 +111,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get delete_all_data => 'Alle Daten löschen';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Spielvorlage löschen';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Gruppe löschen';
|
||||
|
||||
@override
|
||||
String get delete_match => 'Spiel löschen';
|
||||
|
||||
@override
|
||||
String get description => 'Beschreibung';
|
||||
|
||||
@override
|
||||
String get edit_game => 'Spielvorlage bearbeiten';
|
||||
|
||||
@override
|
||||
String get edit_group => 'Gruppe bearbeiten';
|
||||
|
||||
@@ -100,6 +142,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get error_creating_group =>
|
||||
'Fehler beim Erstellen der Gruppe, bitte erneut versuchen';
|
||||
|
||||
@override
|
||||
String get error_deleting_game =>
|
||||
'Fehler beim Löschen der Spielvorlage, bitte erneut versuchen';
|
||||
|
||||
@override
|
||||
String get error_deleting_group =>
|
||||
'Fehler beim Löschen der Gruppe, bitte erneut versuchen';
|
||||
|
||||
@@ -26,6 +26,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get choose_color => 'Choose Color';
|
||||
|
||||
@override
|
||||
String get choose_game => 'Choose Game';
|
||||
|
||||
@@ -35,11 +38,41 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get choose_ruleset => 'Choose Ruleset';
|
||||
|
||||
@override
|
||||
String get color => 'Color';
|
||||
|
||||
@override
|
||||
String get color_blue => 'Blue';
|
||||
|
||||
@override
|
||||
String get color_green => 'Green';
|
||||
|
||||
@override
|
||||
String get color_orange => 'Orange';
|
||||
|
||||
@override
|
||||
String get color_pink => 'Pink';
|
||||
|
||||
@override
|
||||
String get color_purple => 'Purple';
|
||||
|
||||
@override
|
||||
String get color_red => 'Red';
|
||||
|
||||
@override
|
||||
String get color_teal => 'Teal';
|
||||
|
||||
@override
|
||||
String get color_yellow => 'Yellow';
|
||||
|
||||
@override
|
||||
String could_not_add_player(Object playerName) {
|
||||
return 'Could not add player';
|
||||
}
|
||||
|
||||
@override
|
||||
String get create_game => 'Create Game';
|
||||
|
||||
@override
|
||||
String get create_group => 'Create Group';
|
||||
|
||||
@@ -78,12 +111,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get delete_all_data => 'Delete all data';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Delete Game';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Delete Group';
|
||||
|
||||
@override
|
||||
String get delete_match => 'Delete Match';
|
||||
|
||||
@override
|
||||
String get description => 'Description';
|
||||
|
||||
@override
|
||||
String get edit_game => 'Edit Game';
|
||||
|
||||
@override
|
||||
String get edit_group => 'Edit Group';
|
||||
|
||||
@@ -100,6 +142,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get error_creating_group =>
|
||||
'Error while creating group, please try again';
|
||||
|
||||
@override
|
||||
String get error_deleting_game =>
|
||||
'Error while deleting game, please try again';
|
||||
|
||||
@override
|
||||
String get error_deleting_group =>
|
||||
'Error while deleting group, please try again';
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game/create_game_view.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
|
||||
class ChooseGameView extends StatefulWidget {
|
||||
/// A view that allows the user to choose a game from a list of available games
|
||||
/// - [games]: A list of tuples containing the game name, description and ruleset
|
||||
/// - [initialGameIndex]: The index of the initially selected game
|
||||
/// - [games]: The list of available games
|
||||
/// - [initialGameId]: The id of the initially selected game
|
||||
/// - [onGamesUpdated]: Optional callback invoked when the games are updated
|
||||
const ChooseGameView({
|
||||
super.key,
|
||||
required this.games,
|
||||
required this.initialGameId,
|
||||
this.onGamesUpdated,
|
||||
});
|
||||
|
||||
/// A list of tuples containing the game name, description and ruleset
|
||||
@@ -22,6 +26,9 @@ class ChooseGameView extends StatefulWidget {
|
||||
/// The id of the initially selected game
|
||||
final String initialGameId;
|
||||
|
||||
/// Optional callback invoked when the games are updated
|
||||
final VoidCallback? onGamesUpdated;
|
||||
|
||||
@override
|
||||
State<ChooseGameView> createState() => _ChooseGameViewState();
|
||||
}
|
||||
@@ -33,9 +40,16 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
/// Currently selected game index
|
||||
late String selectedGameId;
|
||||
|
||||
/// Games filtered according to the current search query
|
||||
late List<Game> filteredGames;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedGameId = widget.initialGameId;
|
||||
|
||||
// Start with all games visible
|
||||
filteredGames = List<Game>.from(widget.games);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -58,6 +72,30 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) => CreateGameView(
|
||||
onGameChanged: () {
|
||||
widget.onGamesUpdated?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (result != null && result.game != null) {
|
||||
setState(() {
|
||||
widget.games.insert(0, result.game);
|
||||
});
|
||||
_refreshFromSource();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
title: Text(loc.choose_game),
|
||||
),
|
||||
body: PopScope(
|
||||
@@ -77,30 +115,63 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
child: CustomSearchBar(
|
||||
controller: searchBarController,
|
||||
hintText: loc.game_name,
|
||||
onChanged: (value) {
|
||||
_applySearchFilter(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: widget.games.length,
|
||||
itemCount: filteredGames.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final game = filteredGames[index];
|
||||
return TitleDescriptionListTile(
|
||||
title: widget.games[index].name,
|
||||
description: widget.games[index].description,
|
||||
badgeText: translateRulesetToString(
|
||||
widget.games[index].ruleset,
|
||||
context,
|
||||
),
|
||||
isHighlighted: selectedGameId == widget.games[index].id,
|
||||
onPressed: () async {
|
||||
title: game.name,
|
||||
description: game.description,
|
||||
badgeText: translateRulesetToString(game.ruleset, context),
|
||||
isHighlighted: selectedGameId == game.id,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
if (selectedGameId != widget.games[index].id) {
|
||||
selectedGameId = widget.games[index].id;
|
||||
} else {
|
||||
if (selectedGameId == game.id) {
|
||||
selectedGameId = '';
|
||||
} else {
|
||||
selectedGameId = game.id;
|
||||
}
|
||||
});
|
||||
},
|
||||
onLongPress: () async {
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) => CreateGameView(
|
||||
gameToEdit: game,
|
||||
onGameChanged: () {
|
||||
widget.onGamesUpdated?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (result != null && result.game != null) {
|
||||
// Find the index in the original list to mutate
|
||||
final originalIndex = widget.games.indexWhere(
|
||||
(g) => g.id == game.id,
|
||||
);
|
||||
if (originalIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (result.delete) {
|
||||
setState(() {
|
||||
widget.games.removeAt(originalIndex);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
widget.games[originalIndex] = result.game;
|
||||
});
|
||||
}
|
||||
_refreshFromSource();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -110,4 +181,28 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Applies the search filter to the games list based on [query].
|
||||
void _applySearchFilter(String query) {
|
||||
final q = query.toLowerCase().trim();
|
||||
if (q.isEmpty) {
|
||||
setState(() {
|
||||
filteredGames = List<Game>.from(widget.games);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
filteredGames = widget.games.where((game) {
|
||||
final name = game.name.toLowerCase();
|
||||
final description = game.description.toLowerCase();
|
||||
return name.contains(q) || description.contains(q);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
/// Re-applies the current filter after the underlying games list changed.
|
||||
void _refreshFromSource() {
|
||||
_applySearchFilter(searchBarController.text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
|
||||
class ChooseColorView extends StatefulWidget {
|
||||
/// A view that allows the user to choose a color from a list of available game colors
|
||||
/// - [initialColor]: The initially selected color
|
||||
const ChooseColorView({super.key, this.initialColor});
|
||||
|
||||
/// The initially selected color
|
||||
final GameColor? initialColor;
|
||||
|
||||
@override
|
||||
State<ChooseColorView> createState() => _ChooseColorViewState();
|
||||
}
|
||||
|
||||
class _ChooseColorViewState extends State<ChooseColorView> {
|
||||
/// Currently selected color
|
||||
GameColor? selectedColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedColor = widget.initialColor;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
const colors = GameColor.values;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(selectedColor);
|
||||
},
|
||||
),
|
||||
title: Text(loc.choose_color),
|
||||
),
|
||||
body: PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||
if (didPop) return;
|
||||
Navigator.of(context).pop(selectedColor);
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: colors.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final color = colors[index];
|
||||
return TitleDescriptionListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedColor == color) {
|
||||
selectedColor = null;
|
||||
} else {
|
||||
selectedColor = color;
|
||||
}
|
||||
});
|
||||
},
|
||||
title: translateGameColorToString(color, context),
|
||||
description: '',
|
||||
isHighlighted: selectedColor == color,
|
||||
badgeText: ' ', //Breite für Color Badge
|
||||
badgeColor: getColorFromGameColor(color),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
|
||||
class ChooseRulesetView extends StatefulWidget {
|
||||
/// A view that allows the user to choose a ruleset from a list of available rulesets
|
||||
/// - [rulesets]: A list of tuples containing the ruleset and its description
|
||||
/// - [initialRulesetIndex]: The index of the initially selected ruleset
|
||||
const ChooseRulesetView({
|
||||
super.key,
|
||||
required this.rulesets,
|
||||
required this.initialRulesetIndex,
|
||||
});
|
||||
|
||||
/// A list of tuples containing the ruleset and its description
|
||||
final List<(Ruleset, String)> rulesets;
|
||||
|
||||
/// The index of the initially selected ruleset
|
||||
final int initialRulesetIndex;
|
||||
@override
|
||||
State<ChooseRulesetView> createState() => _ChooseRulesetViewState();
|
||||
}
|
||||
|
||||
class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||
/// Currently selected ruleset index
|
||||
late int selectedRulesetIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedRulesetIndex = widget.initialRulesetIndex;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
initialIndex: 0,
|
||||
child: Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
selectedRulesetIndex == -1
|
||||
? null
|
||||
: widget.rulesets[selectedRulesetIndex].$1,
|
||||
);
|
||||
},
|
||||
),
|
||||
title: Text(loc.choose_ruleset),
|
||||
),
|
||||
body: PopScope(
|
||||
// This fixes that the Android Back Gesture didn't return the
|
||||
// selectedRulesetIndex and therefore the selected Ruleset wasn't saved
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(
|
||||
selectedRulesetIndex == -1
|
||||
? null
|
||||
: widget.rulesets[selectedRulesetIndex].$1,
|
||||
);
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: widget.rulesets.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return TitleDescriptionListTile(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
if (selectedRulesetIndex == index) {
|
||||
selectedRulesetIndex = -1;
|
||||
} else {
|
||||
selectedRulesetIndex = index;
|
||||
}
|
||||
});
|
||||
},
|
||||
title: translateRulesetToString(
|
||||
widget.rulesets[index].$1,
|
||||
context,
|
||||
),
|
||||
description: widget.rulesets[index].$2,
|
||||
isHighlighted: selectedRulesetIndex == index,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game/choose_color_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game/choose_ruleset_view.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
|
||||
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
||||
|
||||
/// A stateful widget for creating or editing a game.
|
||||
/// - [gameToEdit] An optional game to prefill the fields
|
||||
/// - [onGameChanged] Callback to invoke when the game is created or edited
|
||||
class CreateGameView extends StatefulWidget {
|
||||
const CreateGameView({
|
||||
super.key,
|
||||
this.gameToEdit,
|
||||
required this.onGameChanged,
|
||||
});
|
||||
|
||||
/// An optional game to prefill the fields
|
||||
final Game? gameToEdit;
|
||||
|
||||
/// Callback to invoke when the game is created or edited
|
||||
final VoidCallback onGameChanged;
|
||||
|
||||
@override
|
||||
State<CreateGameView> createState() => _CreateGameViewState();
|
||||
}
|
||||
|
||||
class _CreateGameViewState extends State<CreateGameView> {
|
||||
/// GlobalKey for ScaffoldMessenger to show snackbars
|
||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
|
||||
/// The database instance for accessing game data.
|
||||
late final AppDatabase db;
|
||||
|
||||
/// The currently selected ruleset for the game.
|
||||
Ruleset? selectedRuleset;
|
||||
|
||||
/// The index of the currently selected ruleset.
|
||||
int selectedRulesetIndex = -1;
|
||||
|
||||
/// A list of available rulesets and their localized names.
|
||||
late List<(Ruleset, String)> _rulesets;
|
||||
|
||||
/// The currently selected color for the game.
|
||||
GameColor? selectedColor;
|
||||
|
||||
/// Controller for the game name input field.
|
||||
final _gameNameController = TextEditingController();
|
||||
|
||||
/// Controller for the game description input field.
|
||||
final _descriptionController = TextEditingController();
|
||||
|
||||
/// The ID of the currently selected group.
|
||||
late String selectedGroupId;
|
||||
|
||||
/// A controller for the search bar input field.
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
/// A list of groups filtered based on the search query.
|
||||
late final List<Group> filteredGroups;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_gameNameController.addListener(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_rulesets = [
|
||||
(
|
||||
Ruleset.singleWinner,
|
||||
translateRulesetToString(Ruleset.singleWinner, context),
|
||||
),
|
||||
(
|
||||
Ruleset.singleLoser,
|
||||
translateRulesetToString(Ruleset.singleLoser, context),
|
||||
),
|
||||
(
|
||||
Ruleset.highestScore,
|
||||
translateRulesetToString(Ruleset.highestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.lowestScore,
|
||||
translateRulesetToString(Ruleset.lowestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.multipleWinners,
|
||||
translateRulesetToString(Ruleset.multipleWinners, context),
|
||||
),
|
||||
];
|
||||
|
||||
if (widget.gameToEdit != null) {
|
||||
_gameNameController.text = widget.gameToEdit!.name;
|
||||
_descriptionController.text = widget.gameToEdit!.description;
|
||||
selectedRuleset = widget.gameToEdit!.ruleset;
|
||||
selectedColor = widget.gameToEdit!.color;
|
||||
|
||||
selectedRulesetIndex = _rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_gameNameController.dispose();
|
||||
_descriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var loc = AppLocalizations.of(context);
|
||||
final isEditing = widget.gameToEdit != null;
|
||||
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text(isEditing ? loc.edit_game : loc.create_game),
|
||||
actions: widget.gameToEdit == null
|
||||
? []
|
||||
: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
if (widget.gameToEdit != null) {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => CustomAlertDialog(
|
||||
title: loc.delete_game,
|
||||
content: Text(loc.this_cannot_be_undone),
|
||||
actions: [
|
||||
CustomDialogAction(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(false),
|
||||
text: loc.cancel,
|
||||
),
|
||||
CustomDialogAction(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(true),
|
||||
text: loc.delete,
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((confirmed) async {
|
||||
if (confirmed == true && context.mounted) {
|
||||
bool success = await db.gameDao.deleteGame(
|
||||
gameId: widget.gameToEdit!.id,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
widget.onGameChanged.call();
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop((game: widget.gameToEdit, delete: true));
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
showSnackbar(message: loc.error_deleting_game);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: CustomTheme.tileMargin,
|
||||
child: TextInputField(
|
||||
controller: _gameNameController,
|
||||
maxLength: Constants.MAX_MATCH_NAME_LENGTH,
|
||||
hintText: loc.game_name,
|
||||
),
|
||||
),
|
||||
ChooseTile(
|
||||
title: loc.ruleset,
|
||||
trailingText: selectedRuleset == null
|
||||
? loc.none
|
||||
: translateRulesetToString(selectedRuleset!, context),
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push<Ruleset?>(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseRulesetView(
|
||||
rulesets: _rulesets,
|
||||
initialRulesetIndex: selectedRulesetIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
selectedRuleset = result;
|
||||
selectedRulesetIndex = result == null
|
||||
? -1
|
||||
: _rulesets.indexWhere((r) => r.$1 == result);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
ChooseTile(
|
||||
title: loc.color,
|
||||
trailingText: selectedColor == null
|
||||
? loc.none
|
||||
: translateGameColorToString(selectedColor!, context),
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push<GameColor?>(
|
||||
adaptivePageRoute(
|
||||
builder: (context) =>
|
||||
ChooseColorView(initialColor: selectedColor),
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
selectedColor = result;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
Container(
|
||||
margin: CustomTheme.tileMargin,
|
||||
child: TextInputField(
|
||||
controller: _descriptionController,
|
||||
hintText: loc.description,
|
||||
minLines: 6,
|
||||
maxLines: 6,
|
||||
maxLength: Constants.MAX_GAME_DESCRIPTION_LENGTH,
|
||||
showCounterText: true,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: CustomWidthButton(
|
||||
text: isEditing ? loc.edit_game : loc.create_game,
|
||||
sizeRelativeToWidth: 1,
|
||||
buttonType: ButtonType.primary,
|
||||
onPressed:
|
||||
_gameNameController.text.trim().isNotEmpty &&
|
||||
selectedRulesetIndex != -1 &&
|
||||
selectedColor != null
|
||||
? () async {
|
||||
Game newGame = Game(
|
||||
name: _gameNameController.text.trim(),
|
||||
description: _descriptionController.text.trim(),
|
||||
ruleset: selectedRuleset!,
|
||||
color: selectedColor!,
|
||||
);
|
||||
if (isEditing) {
|
||||
await handleGameUpdate(newGame);
|
||||
} else {
|
||||
await handleGameCreation(newGame);
|
||||
}
|
||||
widget.onGameChanged.call();
|
||||
if (context.mounted) {
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop((game: newGame, delete: false));
|
||||
}
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles updating an existing game in the database.
|
||||
///
|
||||
/// [newGame] The updated game object.
|
||||
Future<void> handleGameUpdate(Game newGame) async {
|
||||
final oldGame = widget.gameToEdit!;
|
||||
|
||||
if (oldGame.name != newGame.name) {
|
||||
await db.gameDao.updateGameName(
|
||||
gameId: oldGame.id,
|
||||
newName: newGame.name,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldGame.description != newGame.description) {
|
||||
await db.gameDao.updateGameDescription(
|
||||
gameId: oldGame.id,
|
||||
newDescription: newGame.description,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldGame.ruleset != newGame.ruleset) {
|
||||
await db.gameDao.updateGameRuleset(
|
||||
gameId: oldGame.id,
|
||||
newRuleset: newGame.ruleset,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldGame.color != newGame.color) {
|
||||
await db.gameDao.updateGameColor(
|
||||
gameId: oldGame.id,
|
||||
newColor: newGame.color,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldGame.icon != newGame.icon) {
|
||||
await db.gameDao.updateGameIcon(
|
||||
gameId: oldGame.id,
|
||||
newIcon: newGame.icon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles creating a new game in the database.
|
||||
///
|
||||
/// [newGame] The game object to be created.
|
||||
Future<void> handleGameCreation(Game newGame) async {
|
||||
await db.gameDao.addGame(game: newGame);
|
||||
}
|
||||
|
||||
/// Displays a snackbar with the given message and optional action.
|
||||
///
|
||||
/// [message] The message to display in the snackbar.
|
||||
void showSnackbar({required String message}) {
|
||||
final messenger = _scaffoldMessengerKey.currentState;
|
||||
if (messenger != null) {
|
||||
messenger.hideCurrentSnackBar();
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: CustomTheme.boxColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
// Match name input field.
|
||||
Container(
|
||||
margin: CustomTheme.tileMargin,
|
||||
child: TextInputField(
|
||||
@@ -123,6 +124,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
maxLength: Constants.MAX_MATCH_NAME_LENGTH,
|
||||
),
|
||||
),
|
||||
|
||||
// Game selection tile.
|
||||
ChooseTile(
|
||||
title: loc.game,
|
||||
trailingText: selectedGame == null
|
||||
@@ -146,6 +149,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
// Group selection tile.
|
||||
ChooseTile(
|
||||
title: loc.group,
|
||||
trailingText: selectedGroup == null
|
||||
@@ -181,6 +186,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
// Player selection widget.
|
||||
Expanded(
|
||||
child: PlayerSelection(
|
||||
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||
@@ -193,6 +200,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Create or save button.
|
||||
CustomWidthButton(
|
||||
text: buttonText,
|
||||
sizeRelativeToWidth: 0.95,
|
||||
@@ -218,16 +227,16 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
///
|
||||
/// Returns `true` if:
|
||||
/// - A ruleset is selected AND
|
||||
/// - Either a group is selected OR at least 2 players are selected
|
||||
/// - Either a group is selected OR at least 2 players are selected.
|
||||
bool _enableCreateGameButton() {
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers.length > 1) && selectedGame != null);
|
||||
}
|
||||
|
||||
// If a match was provided to the view, it updates the match in the database
|
||||
// and navigates back to the previous screen.
|
||||
// If no match was provided, it creates a new match in the database and
|
||||
// navigates to the MatchResultView for the newly created match.
|
||||
/// Handles navigation when the create or save button is pressed.
|
||||
///
|
||||
/// If a match is being edited, updates the match in the database.
|
||||
/// Otherwise, creates a new match and navigates to the MatchResultView.
|
||||
void buttonNavigation(BuildContext context) async {
|
||||
if (isEditMode()) {
|
||||
await updateMatch();
|
||||
@@ -252,8 +261,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates attributes of the existing match in the database based on the
|
||||
/// changes made in the edit view.
|
||||
/// Updates the existing match in the database.
|
||||
Future<void> updateMatch() async {
|
||||
final updatedMatch = Match(
|
||||
id: widget.matchToEdit!.id,
|
||||
@@ -262,7 +270,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
: _matchNameController.text.trim(),
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
game: widget.matchToEdit!.game,
|
||||
game: selectedGame!,
|
||||
createdAt: widget.matchToEdit!.createdAt,
|
||||
endedAt: widget.matchToEdit!.endedAt,
|
||||
notes: widget.matchToEdit!.notes,
|
||||
@@ -282,6 +290,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.matchToEdit!.game.id != updatedMatch.game.id) {
|
||||
await db.matchDao.updateMatchGame(
|
||||
matchId: widget.matchToEdit!.id,
|
||||
gameId: updatedMatch.game.id,
|
||||
);
|
||||
}
|
||||
|
||||
// Add players who are in updatedMatch but not in the original match
|
||||
for (var player in updatedMatch.players) {
|
||||
if (!widget.matchToEdit!.players.any((p) => p.id == player.id)) {
|
||||
|
||||
@@ -8,12 +8,18 @@ class TextInputField extends StatelessWidget {
|
||||
/// - [onChanged]: Optional callback invoked when the text in the field changes.
|
||||
/// - [hintText]: The hint text displayed in the text input field when it is empty
|
||||
/// - [maxLength]: Optional parameter for maximum length of the input text.
|
||||
/// - [maxLines]: The maximum number of lines for the text input field. Defaults to 1.
|
||||
/// - [minLines]: The minimum number of lines for the text input field. Defaults to 1.
|
||||
/// - [showCounterText]: Whether to show the counter text in the text input field. Defaults to false.
|
||||
const TextInputField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.hintText,
|
||||
this.onChanged,
|
||||
this.maxLength,
|
||||
this.maxLines = 1,
|
||||
this.minLines = 1,
|
||||
this.showCounterText = false,
|
||||
});
|
||||
|
||||
/// The controller for the text input field.
|
||||
@@ -28,6 +34,15 @@ class TextInputField extends StatelessWidget {
|
||||
/// Optional parameter for maximum length of the input text.
|
||||
final int? maxLength;
|
||||
|
||||
/// The maximum number of lines for the text input field.
|
||||
final int? maxLines;
|
||||
|
||||
/// The minimum number of lines for the text input field.
|
||||
final int? minLines;
|
||||
|
||||
/// Whether to show the counter text in the text input field.
|
||||
final bool showCounterText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
@@ -35,13 +50,15 @@ class TextInputField extends StatelessWidget {
|
||||
onChanged: onChanged,
|
||||
maxLength: maxLength,
|
||||
maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||
maxLines: maxLines,
|
||||
minLines: minLines,
|
||||
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: CustomTheme.boxColor,
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(fontSize: 18),
|
||||
// Hides the character counter
|
||||
counterText: '',
|
||||
counterText: showCounterText ? null : '',
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: CustomTheme.boxBorderColor),
|
||||
|
||||
@@ -5,7 +5,8 @@ class TitleDescriptionListTile extends StatelessWidget {
|
||||
/// A list tile widget that displays a title and description, with optional highlighting and badge.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [description]: The description text displayed below the title.
|
||||
/// - [onPressed]: The callback invoked when the tile is tapped.
|
||||
/// - [onTap]: The callback invoked when the tile is tapped.
|
||||
/// - [onLongPress]: The callback invoked when the tile is tapped.
|
||||
/// - [isHighlighted]: A boolean to determine if the tile should be highlighted.
|
||||
/// - [badgeText]: Optional text to display in a badge on the right side of the title.
|
||||
/// - [badgeColor]: Optional color for the badge background.
|
||||
@@ -13,7 +14,8 @@ class TitleDescriptionListTile extends StatelessWidget {
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
this.onPressed,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.isHighlighted = false,
|
||||
this.badgeText,
|
||||
this.badgeColor,
|
||||
@@ -26,7 +28,10 @@ class TitleDescriptionListTile extends StatelessWidget {
|
||||
final String description;
|
||||
|
||||
/// The callback invoked when the tile is tapped.
|
||||
final VoidCallback? onPressed;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// The callback invoked when the tile is long-pressed.
|
||||
final VoidCallback? onLongPress;
|
||||
|
||||
/// A boolean to determine if the tile should be highlighted.
|
||||
final bool isHighlighted;
|
||||
@@ -40,7 +45,8 @@ class TitleDescriptionListTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
child: AnimatedContainer(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
|
||||
Reference in New Issue
Block a user
im singular: werden 1 spiel gelöscht? dat passt ja net oder
fixed