From 975679b048fab162d10fdadf5eeca57308c3efb9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 08:24:35 +0100 Subject: [PATCH 1/7] Updated dependencie --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e13f0cc..6192380 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: build_runner: ^2.5.4 dart_pubspec_licenses: ^3.0.14 drift_dev: ^2.27.0 - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true -- 2.49.1 From 494dec8c614b98591ca05eb8a1532018cab775c3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 08:29:15 +0100 Subject: [PATCH 2/7] Added localizations --- lib/l10n/arb/app_de.arb | 4 +++- lib/l10n/arb/app_en.arb | 10 +++++++++- lib/l10n/generated/app_localizations.dart | 14 +++++++++++++- lib/l10n/generated/app_localizations_de.dart | 9 ++++++++- lib/l10n/generated/app_localizations_en.dart | 9 ++++++++- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index e0efc2c..0a114a6 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -24,6 +24,7 @@ "delete_all_data": "Alle Daten löschen", "delete_group": "Gruppe löschen", "edit_group": "Gruppe bearbeiten", + "enter_points": "Punkte eingeben", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_reading_file": "Fehler beim Lesen der Datei", "export_canceled": "Export abgebrochen", @@ -77,7 +78,8 @@ "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", - "select_winner": "Gewinner:in wählen:", + "select_winner": "Gewinner:in wählen", + "select_loser": "Verlierer:in wählen", "selected_players": "Ausgewählte Spieler:innen", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 6a64a1b..ecc5384 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -77,6 +77,9 @@ "@edit_group": { "description": "Button text to edit a group" }, + "@enter_points": { + "description": "Label to enter players points" + }, "@error_creating_group": { "description": "Error message when group creation fails" }, @@ -244,6 +247,9 @@ "@select_winner": { "description": "Label to select the winner" }, + "@select_loser": { + "description": "Label to select the loser" + }, "@selected_players": { "description": "Shows the number of selected players" }, @@ -322,6 +328,7 @@ "delete_all_data": "Delete all data", "delete_group": "Delete Group", "edit_group": "Edit Group", + "enter_points": "Enter points", "error_creating_group": "Error while creating group, please try again", "error_reading_file": "Error reading file", "export_canceled": "Export canceled", @@ -375,7 +382,8 @@ "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", "search_for_groups": "Search for groups", "search_for_players": "Search for players", - "select_winner": "Select Winner:", + "select_winner": "Select Winner", + "select_loser": "Select Loser", "selected_players": "Selected players", "settings": "Settings", "single_loser": "Single Loser", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 87fab99..71a2805 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -242,6 +242,12 @@ abstract class AppLocalizations { /// **'Edit Group'** String get edit_group; + /// Label to enter players points + /// + /// In en, this message translates to: + /// **'Enter points'** + String get enter_points; + /// Error message when group creation fails /// /// In en, this message translates to: @@ -563,9 +569,15 @@ abstract class AppLocalizations { /// Label to select the winner /// /// In en, this message translates to: - /// **'Select Winner:'** + /// **'Select Winner'** String get select_winner; + /// Label to select the loser + /// + /// In en, this message translates to: + /// **'Select Loser'** + String get select_loser; + /// Shows the number of selected players /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 54f3bc7..67a68d6 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -84,6 +85,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get edit_group => 'Gruppe bearbeiten'; + @override + String get enter_points => 'Punkte eingeben'; + @override String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @@ -252,7 +256,10 @@ class AppLocalizationsDe extends AppLocalizations { String get search_for_players => 'Nach Spieler:innen suchen'; @override - String get select_winner => 'Gewinner:in wählen:'; + String get select_winner => 'Gewinner:in wählen'; + + @override + String get select_loser => 'Verlierer:in wählen'; @override String get selected_players => 'Ausgewählte Spieler:innen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 440ac10..93ed947 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -84,6 +85,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get edit_group => 'Edit Group'; + @override + String get enter_points => 'Enter points'; + @override String get error_creating_group => 'Error while creating group, please try again'; @@ -252,7 +256,10 @@ class AppLocalizationsEn extends AppLocalizations { String get search_for_players => 'Search for players'; @override - String get select_winner => 'Select Winner:'; + String get select_winner => 'Select Winner'; + + @override + String get select_loser => 'Select Loser'; @override String get selected_players => 'Selected players'; -- 2.49.1 From c50ad288fa06b0c5b469421b4b4645aaaa6185eb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 08:29:45 +0100 Subject: [PATCH 3/7] Implemented basic structure --- .../match_view/match_result_view.dart | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 4f3f0c0..e6b55e5 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.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/dto/match.dart'; import 'package:tallee/data/dto/player.dart'; @@ -11,11 +12,18 @@ class MatchResultView extends StatefulWidget { /// A view that allows selecting and saving the winner of a match /// [match]: The match for which the winner is to be selected /// [onWinnerChanged]: Optional callback invoked when the winner is changed - const MatchResultView({super.key, required this.match, this.onWinnerChanged}); + const MatchResultView({ + super.key, + required this.match, + this.ruleset = Ruleset.singleWinner, + this.onWinnerChanged, + }); /// The match for which the winner is to be selected final Match match; + final Ruleset ruleset; + /// Optional callback invoked when the winner is changed final VoidCallback? onWinnerChanged; @@ -47,6 +55,7 @@ class _MatchResultViewState extends State { @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); + return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -82,7 +91,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - loc.select_winner, + '${getTitleForRuleset(loc)}:', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -96,7 +105,7 @@ class _MatchResultViewState extends State { setState(() { _selectedPlayer = value; }); - await _handleWinnerSaving(); + await _handleSaving(); }, child: ListView.builder( itemCount: allPlayers.length, @@ -115,7 +124,7 @@ class _MatchResultViewState extends State { (_selectedPlayer = value); } }); - await _handleWinnerSaving(); + await _handleSaving(); }, ); }, @@ -134,16 +143,42 @@ class _MatchResultViewState extends State { /// Handles saving or removing the winner in the database /// based on the current selection. - Future _handleWinnerSaving() async { + Future _handleSaving() async { + if (widget.ruleset == Ruleset.singleWinner) { + await _handleWinner(); + } else if (widget.ruleset == Ruleset.singleLoser) { + await _handleLoser(); + } else if (widget.ruleset == Ruleset.lowestScore || + widget.ruleset == Ruleset.highestScore) { + await _handleScores(); + } + + widget.onWinnerChanged?.call(); + } + + Future _handleWinner() async { if (_selectedPlayer == null) { - await db.matchDao.removeWinner(matchId: widget.match.id); + return await db.matchDao.removeWinner(matchId: widget.match.id); } else { - await db.matchDao.setWinner( + return await db.matchDao.setWinner( matchId: widget.match.id, winnerId: _selectedPlayer!.id, ); } - widget.onWinnerChanged?.call(); + } + + Future _handleLoser() async { + if (_selectedPlayer == null) { + //TODO: removeLoser() method + return false; + } else { + //TODO: setLoser() method + return false; + } + } + + Future _handleScores() async { + return false; } /// Retrieves all players associated with the given [match]. @@ -162,4 +197,15 @@ class _MatchResultViewState extends State { players.sort((a, b) => a.name.compareTo(b.name)); return players; } + + String getTitleForRuleset(AppLocalizations loc) { + switch (widget.ruleset) { + case Ruleset.singleWinner: + return loc.select_winner; + case Ruleset.singleLoser: + return loc.select_loser; + default: + return loc.enter_points; + } + } } -- 2.49.1 From 84b8541822122538b0cc1d500bd2bdbbff041dce Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 11:00:42 +0100 Subject: [PATCH 4/7] Fixed match edit error with game --- .../main_menu/match_view/create_match/create_match_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 1bf732c..8138957 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -363,6 +363,7 @@ class _CreateMatchViewState extends State { final match = widget.matchToEdit!; _matchNameController.text = match.name; selectedPlayers = match.players; + selectedGameIndex = 0; if (match.group != null) { selectedGroup = match.group; -- 2.49.1 From d5a7bb320f9d0ff776cd64fa639e4074b72a6c18 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 22:25:23 +0100 Subject: [PATCH 5/7] Implememented different result tiles in match detail view for different rulesets --- lib/core/custom_theme.dart | 12 +- lib/l10n/arb/app_de.arb | 2 + lib/l10n/arb/app_en.arb | 5 + lib/l10n/generated/app_localizations.dart | 12 ++ lib/l10n/generated/app_localizations_de.dart | 6 + lib/l10n/generated/app_localizations_en.dart | 6 + .../match_view/match_detail_view.dart | 120 +++++++++++++----- 7 files changed, 126 insertions(+), 37 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index d1b158e..3274db9 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -85,21 +85,21 @@ class CustomTheme { ); static const SearchBarThemeData searchBarTheme = SearchBarThemeData( - textStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.textColor)), - hintStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.hintColor)), + textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)), + hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)), ); static final RadioThemeData radioTheme = RadioThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { - return CustomTheme.primaryColor; + return primaryColor; } - return CustomTheme.textColor; + return textColor; }), ); static const InputDecorationTheme inputDecorationTheme = InputDecorationTheme( - labelStyle: TextStyle(color: CustomTheme.textColor), - hintStyle: TextStyle(color: CustomTheme.hintColor), + labelStyle: TextStyle(color: textColor), + hintStyle: TextStyle(color: hintColor), ); } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 2046dde..47a092f 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -75,6 +75,7 @@ "player_name": "Spieler:innenname", "players": "Spieler:innen", "players_count": "{count} Spieler", + "points": "Punkte", "privacy_policy": "Datenschutzerklärung", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", @@ -95,6 +96,7 @@ "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", "highest_score": "Höchste Punkte", + "loser": "Verlierer:in", "lowest_score": "Niedrigste Punkte", "multiple_winners": "Mehrere Gewinner:innen", "statistics": "Statistiken", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index c992a01..a30d376 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -235,6 +235,9 @@ } } }, + "@points": { + "description": "Points label" + }, "@privacy_policy": { "description": "Privacy policy menu item" }, @@ -406,6 +409,7 @@ "player_name": "Player name", "players": "Players", "players_count": "{count} Players", + "points": "Points", "privacy_policy": "Privacy Policy", "quick_create": "Quick Create", "recent_matches": "Recent Matches", @@ -425,6 +429,7 @@ "single_loser": "Single Loser", "single_winner": "Single Winner", "highest_score": "Highest Score", + "loser": "Loser", "lowest_score": "Lowest Score", "multiple_winners": "Multiple Winners", "statistics": "Statistics", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index f5ba224..456a6fc 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -548,6 +548,12 @@ abstract class AppLocalizations { /// **'{count} Players'** String players_count(int count); + /// Points label + /// + /// In en, this message translates to: + /// **'Points'** + String get points; + /// Privacy policy menu item /// /// In en, this message translates to: @@ -662,6 +668,12 @@ abstract class AppLocalizations { /// **'Highest Score'** String get highest_score; + /// No description provided for @loser. + /// + /// In en, this message translates to: + /// **'Loser'** + String get loser; + /// No description provided for @lowest_score. /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 7a574c0..a3a1b26 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -243,6 +243,9 @@ class AppLocalizationsDe extends AppLocalizations { return '$count Spieler'; } + @override + String get points => 'Punkte'; + @override String get privacy_policy => 'Datenschutzerklärung'; @@ -304,6 +307,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get highest_score => 'Höchste Punkte'; + @override + String get loser => 'Verlierer:in'; + @override String get lowest_score => 'Niedrigste Punkte'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 48bc6ab..61b8934 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -243,6 +243,9 @@ class AppLocalizationsEn extends AppLocalizations { return '$count Players'; } + @override + String get points => 'Points'; + @override String get privacy_policy => 'Privacy Policy'; @@ -304,6 +307,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get highest_score => 'Highest Score'; + @override + String get loser => 'Loser'; + @override String get lowest_score => 'Lowest Score'; diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 1deba18..eab3899 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.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/core/enums.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/dto/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; @@ -175,37 +176,7 @@ class _MatchDetailViewState extends State { vertical: 4, horizontal: 8, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - /// TODO: Implement different ruleset results display - if (match.winner != null) ...[ - Text( - loc.winner, - style: const TextStyle( - fontSize: 16, - color: CustomTheme.textColor, - ), - ), - Text( - match.winner!.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CustomTheme.primaryColor, - ), - ), - ] else ...[ - Text( - loc.no_results_entered_yet, - style: const TextStyle( - fontSize: 14, - color: CustomTheme.textColor, - ), - ), - ], - ], - ), + child: getResultWidget(loc), ), ), ], @@ -264,4 +235,91 @@ class _MatchDetailViewState extends State { }); widget.onMatchUpdate.call(); } + + /// Returns the widget to be displayed in the result [InfoTile] + /// TODO: Update when score logic is overhauled + Widget getResultWidget(AppLocalizations loc) { + if (isSingleRowResult()) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: getResultRow(loc), + ); + } else { + return Column( + children: [ + for (var player in match.players) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + player.name, + style: const TextStyle( + fontSize: 16, + color: CustomTheme.textColor, + ), + ), + Text( + '0 ${loc.points}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), + ), + ], + ), + ], + ); + } + } + + /// Returns the result row for single winner/loser rulesets or a placeholder + /// if no result is entered yet + /// TODO: Update when score logic is overhauled + List getResultRow(AppLocalizations loc) { + if (match.winner != null && match.game.ruleset == Ruleset.singleWinner) { + return [ + Text( + loc.winner, + style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), + ), + Text( + match.winner!.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), + ), + ]; + } else if (match.game.ruleset == Ruleset.singleLoser) { + return [ + Text( + loc.loser, + style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), + ), + Text( + match.winner!.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), + ), + ]; + } else { + return [ + Text( + loc.no_results_entered_yet, + style: const TextStyle(fontSize: 14, color: CustomTheme.textColor), + ), + ]; + } + } + + // Returns if the result can be displayed in a single row + bool isSingleRowResult() { + return match.game.ruleset == Ruleset.singleWinner || + match.game.ruleset == Ruleset.singleLoser; + } } -- 2.49.1 From f0c575d2c9eed6001dc73b8bff39ce469f193b55 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 22:25:55 +0100 Subject: [PATCH 6/7] Implemented different result view depending on ruleset --- .../match_view/match_result_view.dart | 128 +++++++++++++----- .../widgets/tiles/score_list_tile.dart | 91 +++++++++++++ 2 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 lib/presentation/widgets/tiles/score_list_tile.dart diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index a3904b7..94e392b 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -6,7 +6,9 @@ import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/dto/match.dart'; import 'package:tallee/data/dto/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/score_list_tile.dart'; class MatchResultView extends StatefulWidget { /// A view that allows selecting and saving the winner of a match @@ -22,6 +24,8 @@ class MatchResultView extends StatefulWidget { /// The match for which the winner is to be selected final Match match; + /// The ruleset of the match, determines how the winner is selected or how + /// scores are entered final Ruleset ruleset; /// Optional callback invoked when the winner is changed @@ -37,6 +41,9 @@ class _MatchResultViewState extends State { /// List of all players who participated in the match late final List allPlayers; + /// List of text controllers for score entry, one for each player + late final List controller; + /// Currently selected winner player Player? _selectedPlayer; @@ -47,10 +54,19 @@ class _MatchResultViewState extends State { allPlayers = widget.match.players; allPlayers.sort((a, b) => a.name.compareTo(b.name)); + controller = List.generate( + allPlayers.length, + (index) => TextEditingController(), + ); + if (widget.match.winner != null) { - _selectedPlayer = allPlayers.firstWhere( - (p) => p.id == widget.match.winner!.id, - ); + if (rulesetSupportsWinnerSelection()) { + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.match.winner!.id, + ); + } else if (rulesetSupportsScoreEntry()) { + /// TODO: Update when score logic is overhauled + } } super.initState(); } @@ -101,43 +117,70 @@ class _MatchResultViewState extends State { ), ), const SizedBox(height: 10), - Expanded( - child: RadioGroup( - groupValue: _selectedPlayer, - onChanged: (Player? value) async { - setState(() { - _selectedPlayer = value; - }); - await _handleSaving(); - }, - child: ListView.builder( + if (rulesetSupportsWinnerSelection()) + Expanded( + child: RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) async { + setState(() { + _selectedPlayer = value; + }); + }, + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) async { + setState(() { + // Check if the already selected player is the same as the newly tapped player. + if (_selectedPlayer == value) { + // If yes deselected the player by setting it to null. + _selectedPlayer = null; + } else { + // If no assign the newly tapped player to the selected player. + (_selectedPlayer = value); + } + }); + }, + ); + }, + ), + ), + ), + if (rulesetSupportsScoreEntry()) + Expanded( + child: ListView.separated( itemCount: allPlayers.length, itemBuilder: (context, index) { - return CustomRadioListTile( + print(allPlayers[index].name); + return ScoreListTile( text: allPlayers[index].name, - value: allPlayers[index], - onContainerTap: (value) async { - setState(() { - // Check if the already selected player is the same as the newly tapped player. - if (_selectedPlayer == value) { - // If yes deselected the player by setting it to null. - _selectedPlayer = null; - } else { - // If no assign the newly tapped player to the selected player. - (_selectedPlayer = value); - } - }); - await _handleSaving(); - }, + controller: controller[index], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(indent: 20), ); }, ), ), - ), ], ), ), ), + CustomWidthButton( + text: loc.save_changes, + sizeRelativeToWidth: 0.95, + onPressed: () async { + await _handleSaving(); + if (!context.mounted) return; + Navigator.of(context).pop(_selectedPlayer); + }, + ), ], ), ), @@ -172,15 +215,28 @@ class _MatchResultViewState extends State { Future _handleLoser() async { if (_selectedPlayer == null) { - //TODO: removeLoser() method + /// TODO: Update when score logic is overhauled return false; } else { - //TODO: setLoser() method + /// TODO: Update when score logic is overhauled return false; } } + /// Handles saving the scores for each player in the database. Future _handleScores() async { + for (int i = 0; i < allPlayers.length; i++) { + var text = controller[i].text; + if (text.isEmpty) { + text = '0'; + } + final score = int.parse(text); + await db.playerMatchDao.updatePlayerScore( + matchId: widget.match.id, + playerId: allPlayers[i].id, + newScore: score, + ); + } return false; } @@ -194,4 +250,14 @@ class _MatchResultViewState extends State { return loc.enter_points; } } + + bool rulesetSupportsWinnerSelection() { + return widget.ruleset == Ruleset.singleWinner || + widget.ruleset == Ruleset.singleLoser; + } + + bool rulesetSupportsScoreEntry() { + return widget.ruleset == Ruleset.lowestScore || + widget.ruleset == Ruleset.highestScore; + } } diff --git a/lib/presentation/widgets/tiles/score_list_tile.dart b/lib/presentation/widgets/tiles/score_list_tile.dart new file mode 100644 index 0000000..d6aafe3 --- /dev/null +++ b/lib/presentation/widgets/tiles/score_list_tile.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; + +class ScoreListTile extends StatelessWidget { + /// A custom list tile widget that has a text field for inputting a score. + /// - [text]: The leading text to be displayed. + /// - [controller]: The controller for the text field to input the score. + const ScoreListTile({ + super.key, + required this.text, + required this.controller, + /* + required this.onContainerTap, +*/ + }); + + /// The text to display next to the radio button. + final String text; + + final TextEditingController controller; + + /// The callback invoked when the container is tapped. + /* + final ValueChanged onContainerTap; +*/ + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 20), + decoration: const BoxDecoration(color: CustomTheme.boxColor), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + ), + SizedBox( + width: 100, + height: 40, + child: TextField( + controller: controller, + keyboardType: TextInputType.number, + maxLength: 4, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: CustomTheme.textColor, + ), + cursorColor: CustomTheme.textColor, + decoration: InputDecoration( + hintText: loc.points, + counterText: '', + filled: true, + fillColor: CustomTheme.onBoxColor, + contentPadding: const EdgeInsets.symmetric( + horizontal: 0, + vertical: 0, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: CustomTheme.textColor.withAlpha(100), + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide( + color: CustomTheme.primaryColor, + width: 2, + ), + ), + ), + ), + ), + ], + ), + ); + } +} -- 2.49.1 From 9b2fcf18608f2be8138c15734163177faae5907f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 8 Mar 2026 22:27:27 +0100 Subject: [PATCH 7/7] Fix: Linter exclude --- analysis_options.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analysis_options.yaml b/analysis_options.yaml index 04172d4..dc1e1c5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,9 @@ include: package:flutter_lints/flutter.yaml +analyzer: + exclude: + - lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart + linter: rules: avoid_print: false -- 2.49.1