Implemented basic game choosing functionality

This commit is contained in:
2026-04-21 20:17:54 +02:00
parent 2c2bb582fd
commit 4322e75811
2 changed files with 50 additions and 77 deletions

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tallee/core/common.dart'; import 'package:tallee/core/common.dart';
import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/core/enums.dart'; import 'package:tallee/data/models/game.dart';
import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
@@ -13,14 +13,14 @@ class ChooseGameView extends StatefulWidget {
const ChooseGameView({ const ChooseGameView({
super.key, super.key,
required this.games, required this.games,
required this.initialGameIndex, required this.initialGameId,
}); });
/// A list of tuples containing the game name, description and ruleset /// A list of tuples containing the game name, description and ruleset
final List<(String, String, Ruleset)> games; final List<Game> games;
/// The index of the initially selected game /// The id of the initially selected game
final int initialGameIndex; final String initialGameId;
@override @override
State<ChooseGameView> createState() => _ChooseGameViewState(); State<ChooseGameView> createState() => _ChooseGameViewState();
@@ -31,11 +31,11 @@ class _ChooseGameViewState extends State<ChooseGameView> {
final TextEditingController searchBarController = TextEditingController(); final TextEditingController searchBarController = TextEditingController();
/// Currently selected game index /// Currently selected game index
late int selectedGameIndex; late String selectedGameId;
@override @override
void initState() { void initState() {
selectedGameIndex = widget.initialGameIndex; selectedGameId = widget.initialGameId;
super.initState(); super.initState();
} }
@@ -49,7 +49,13 @@ class _ChooseGameViewState extends State<ChooseGameView> {
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios), icon: const Icon(Icons.arrow_back_ios),
onPressed: () { onPressed: () {
Navigator.of(context).pop(selectedGameIndex); Navigator.of(context).pop(
selectedGameId == ''
? null
: widget.games.firstWhere(
(game) => game.id == selectedGameId,
),
);
}, },
), ),
title: Text(loc.choose_game), title: Text(loc.choose_game),
@@ -62,7 +68,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
if (didPop) { if (didPop) {
return; return;
} }
Navigator.of(context).pop(selectedGameIndex); Navigator.of(context).pop(widget.initialGameId);
}, },
child: Column( child: Column(
children: [ children: [
@@ -79,19 +85,19 @@ class _ChooseGameViewState extends State<ChooseGameView> {
itemCount: widget.games.length, itemCount: widget.games.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return TitleDescriptionListTile( return TitleDescriptionListTile(
title: widget.games[index].$1, title: widget.games[index].name,
description: widget.games[index].$2, description: widget.games[index].description,
badgeText: translateRulesetToString( badgeText: translateRulesetToString(
widget.games[index].$3, widget.games[index].ruleset,
context, context,
), ),
isHighlighted: selectedGameIndex == index, isHighlighted: selectedGameId == widget.games[index].id,
onPressed: () async { onPressed: () async {
setState(() { setState(() {
if (selectedGameIndex == index) { if (selectedGameId != widget.games[index].id) {
selectedGameIndex = -1; selectedGameId = widget.games[index].id;
} else { } else {
selectedGameIndex = index; selectedGameId = '';
} }
}); });
}, },

View File

@@ -20,7 +20,9 @@ import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
class CreateMatchView extends StatefulWidget { class CreateMatchView extends StatefulWidget {
/// A view that allows creating a new match /// A view that allows creating a new match
/// [onWinnerChanged]: Optional callback invoked when the winner is changed /// - [onWinnerChanged]: Optional callback invoked when the winner is changed
/// - [matchToEdit]: An optional match to prefill the fields for editing.
/// - [onMatchUpdated]: Optional callback invoked when the match is updated (only in
const CreateMatchView({ const CreateMatchView({
super.key, super.key,
this.onWinnerChanged, this.onWinnerChanged,
@@ -28,13 +30,11 @@ class CreateMatchView extends StatefulWidget {
this.onMatchUpdated, this.onMatchUpdated,
}); });
/// Optional callback invoked when the winner is changed
final VoidCallback? onWinnerChanged; final VoidCallback? onWinnerChanged;
/// Optional callback invoked when the match is updated
final void Function(Match)? onMatchUpdated; final void Function(Match)? onMatchUpdated;
/// An optional match to prefill the fields /// An optional match to prefill the fields for editing.
final Match? matchToEdit; final Match? matchToEdit;
@override @override
@@ -50,20 +50,12 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// Hint text for the match name input field /// Hint text for the match name input field
String? hintText; String? hintText;
/// List of all groups from the database
List<Group> groupsList = []; List<Group> groupsList = [];
/// List of all players from the database
List<Player> playerList = []; List<Player> playerList = [];
List<Game> gamesList = [];
/// The currently selected group
Group? selectedGroup; Group? selectedGroup;
Game? selectedGame;
/// The index of the currently selected game in [games] to mark it in
/// the [ChooseGameView]
int selectedGameIndex = -1;
/// The currently selected players
List<Player> selectedPlayers = []; List<Player> selectedPlayers = [];
/// GlobalKey for ScaffoldMessenger to show snackbars /// GlobalKey for ScaffoldMessenger to show snackbars
@@ -81,12 +73,14 @@ class _CreateMatchViewState extends State<CreateMatchView> {
Future.wait([ Future.wait([
db.groupDao.getAllGroups(), db.groupDao.getAllGroups(),
db.playerDao.getAllPlayers(), db.playerDao.getAllPlayers(),
db.gameDao.getAllGames(),
]).then((result) async { ]).then((result) async {
groupsList = result[0] as List<Group>; groupsList = result[0] as List<Group>;
playerList = result[1] as List<Player>; playerList = result[1] as List<Player>;
gamesList = (result[2] as List<Game>);
// If a match is provided, prefill the fields // If a match is provided, prefill the fields
if (widget.matchToEdit != null) { if (isEditMode()) {
prefillMatchDetails(); prefillMatchDetails();
} }
}); });
@@ -105,20 +99,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
hintText ??= loc.match_name; hintText ??= loc.match_name;
} }
List<(String, String, Ruleset)> games = [
('Example Game 1', 'This is a description', Ruleset.lowestScore),
('Example Game 2', '', Ruleset.singleWinner),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
final buttonText = widget.matchToEdit != null final buttonText = isEditMode() ? loc.save_changes : loc.create_match;
? loc.save_changes final viewTitle = isEditMode() ? loc.edit_match : loc.create_new_match;
: loc.create_match;
final viewTitle = widget.matchToEdit != null
? loc.edit_match
: loc.create_new_match;
return ScaffoldMessenger( return ScaffoldMessenger(
key: _scaffoldMessengerKey, key: _scaffoldMessengerKey,
@@ -140,21 +125,21 @@ class _CreateMatchViewState extends State<CreateMatchView> {
), ),
ChooseTile( ChooseTile(
title: loc.game, title: loc.game,
trailingText: selectedGameIndex == -1 trailingText: selectedGame == null
? loc.none ? loc.none_group
: games[selectedGameIndex].$1, : selectedGame!.name,
onPressed: () async { onPressed: () async {
selectedGameIndex = await Navigator.of(context).push( selectedGame = await Navigator.of(context).push(
adaptivePageRoute( adaptivePageRoute(
builder: (context) => ChooseGameView( builder: (context) => ChooseGameView(
games: games, games: gamesList,
initialGameIndex: selectedGameIndex, initialGameId: selectedGame?.id ?? '',
), ),
), ),
); );
setState(() { setState(() {
if (selectedGameIndex != -1) { if (selectedGame != null) {
hintText = games[selectedGameIndex].$1; hintText = selectedGame!.name;
} else { } else {
hintText = loc.match_name; hintText = loc.match_name;
} }
@@ -225,6 +210,10 @@ class _CreateMatchViewState extends State<CreateMatchView> {
); );
} }
bool isEditMode() {
return widget.matchToEdit != null;
}
/// Determines whether the "Create Match" button should be enabled. /// Determines whether the "Create Match" button should be enabled.
/// ///
/// Returns `true` if: /// Returns `true` if:
@@ -232,7 +221,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// - 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() { bool _enableCreateGameButton() {
return (selectedGroup != null || return (selectedGroup != null ||
(selectedPlayers.length > 1) && selectedGameIndex != -1); (selectedPlayers.length > 1) && selectedGame != null);
} }
// If a match was provided to the view, it updates the match in the database // If a match was provided to the view, it updates the match in the database
@@ -240,7 +229,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
// If no match was provided, it creates a new match in the database and // If no match was provided, it creates a new match in the database and
// navigates to the MatchResultView for the newly created match. // navigates to the MatchResultView for the newly created match.
void buttonNavigation(BuildContext context) async { void buttonNavigation(BuildContext context) async {
if (widget.matchToEdit != null) { if (isEditMode()) {
await updateMatch(); await updateMatch();
if (context.mounted) { if (context.mounted) {
Navigator.pop(context); Navigator.pop(context);
@@ -266,9 +255,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// Updates attributes of the existing match in the database based on the /// Updates attributes of the existing match in the database based on the
/// changes made in the edit view. /// changes made in the edit view.
Future<void> updateMatch() async { Future<void> updateMatch() async {
//TODO: Remove when Games implemented
final tempGame = await getTemporaryGame();
final updatedMatch = Match( final updatedMatch = Match(
id: widget.matchToEdit!.id, id: widget.matchToEdit!.id,
name: _matchNameController.text.isEmpty name: _matchNameController.text.isEmpty
@@ -276,7 +262,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
: _matchNameController.text.trim(), : _matchNameController.text.trim(),
group: selectedGroup, group: selectedGroup,
players: selectedPlayers, players: selectedPlayers,
game: tempGame, game: widget.matchToEdit!.game,
createdAt: widget.matchToEdit!.createdAt, createdAt: widget.matchToEdit!.createdAt,
endedAt: widget.matchToEdit!.endedAt, endedAt: widget.matchToEdit!.endedAt,
notes: widget.matchToEdit!.notes, notes: widget.matchToEdit!.notes,
@@ -322,8 +308,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
// Creates a new match and adds it to the database. // Creates a new match and adds it to the database.
// Returns the created match. // Returns the created match.
Future<Match> createMatch() async { Future<Match> createMatch() async {
final tempGame = await getTemporaryGame();
Match match = Match( Match match = Match(
name: _matchNameController.text.isEmpty name: _matchNameController.text.isEmpty
? (hintText ?? '') ? (hintText ?? '')
@@ -331,35 +315,18 @@ class _CreateMatchViewState extends State<CreateMatchView> {
createdAt: DateTime.now(), createdAt: DateTime.now(),
group: selectedGroup, group: selectedGroup,
players: selectedPlayers, players: selectedPlayers,
game: tempGame, game: selectedGame!,
); );
await db.matchDao.addMatch(match: match); await db.matchDao.addMatch(match: match);
return match; return match;
} }
// TODO: Remove when games fully implemented
Future<Game> getTemporaryGame() async {
Game? game;
final selectedGame = games[selectedGameIndex];
game = Game(
name: selectedGame.$1,
description: selectedGame.$2,
ruleset: selectedGame.$3,
color: GameColor.blue,
icon: '',
);
await db.gameDao.addGame(game: game);
return game;
}
// If a match was provided to the view, this method prefills the input fields // If a match was provided to the view, this method prefills the input fields
void prefillMatchDetails() { void prefillMatchDetails() {
final match = widget.matchToEdit!; final match = widget.matchToEdit!;
_matchNameController.text = match.name; _matchNameController.text = match.name;
selectedPlayers = match.players; selectedPlayers = match.players;
selectedGameIndex = 0; selectedGame = match.game;
if (match.group != null) { if (match.group != null) {
selectedGroup = match.group; selectedGroup = match.group;