From a78614851bdeac36e363957ffd80bf939953a09d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:02:09 +0100 Subject: [PATCH 01/19] Added constants class --- lib/core/constants.dart | 8 ++++++-- .../views/main_menu/group_view/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 2 +- .../views/main_menu/match_view/match_view.dart | 2 +- lib/presentation/views/main_menu/statistics_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 075b1ab..8d3c8cc 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,6 @@ -/// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +class Constants { + Constants._(); // Private constructor to prevent instantiation + + /// Minimum duration of all app skeletons + static Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +} diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 3505a3c..c641dde 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -101,7 +101,7 @@ class _GroupsViewState extends State { void loadGroups() { Future.wait([ db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { loadedGroups = results[0] as List; setState(() { diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 96280ce..072699e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -47,7 +47,7 @@ class _HomeViewState extends State { db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 73f596f..bea2f14 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -120,7 +120,7 @@ class _MatchViewState extends State { void loadGames() { Future.wait([ db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { if (mounted) { setState(() { diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6c30483..1125118 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -31,7 +31,7 @@ class _StatisticsViewState extends State { Future.wait([ db.matchDao.getAllMatches(), db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) async { if (!mounted) return; final matches = results[0] as List; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eac4480..9eb005a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -52,7 +52,7 @@ class _PlayerSelectionState extends State { void loadPlayerList() { _allPlayersFuture = Future.wait([ db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) => results[0] as List); if (mounted) { _allPlayersFuture.then((loadedPlayers) { From 6e45e9435b5ef9c7bc1606922eae2bd68fbf3f70 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:27:39 +0100 Subject: [PATCH 02/19] Refactored views --- .../main_menu/custom_navigation_bar.dart | 5 ++ .../group_view/create_group_view.dart | 6 +- .../main_menu/group_view/groups_view.dart | 4 + .../views/main_menu/home_view.dart | 69 +++++++++++------- .../create_match/choose_game_view.dart | 6 +- .../create_match/choose_group_view.dart | 3 +- .../create_match/choose_ruleset_view.dart | 1 + .../create_match/create_match_view.dart | 1 - .../match_view/match_result_view.dart | 12 ++- .../main_menu/match_view/match_view.dart | 4 +- .../views/main_menu/statistics_view.dart | 73 +++++++++++-------- 11 files changed, 117 insertions(+), 67 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 1e38808..a8b18c8 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -17,7 +17,10 @@ class CustomNavigationBar extends StatefulWidget { class _CustomNavigationBarState extends State with SingleTickerProviderStateMixin { + /// Currently selected tab index int currentIndex = 0; + + /// Key count to force rebuild of tab views int tabKeyCount = 0; @override @@ -119,12 +122,14 @@ class _CustomNavigationBarState extends State ); } + /// Handles tab tap events. Updates the current [index] state. void onTabTapped(int index) { setState(() { currentIndex = index; }); } + /// Returns the title of the current tab based on [currentIndex]. String _currentTabTitle(context) { final loc = AppLocalizations.of(context); switch (currentIndex) { diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index cba22ef..022a4b5 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -18,15 +18,17 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - final _groupNameController = TextEditingController(); late final AppDatabase db; + /// Controller for the group name input field + final _groupNameController = TextEditingController(); + + /// List of currently selected players List selectedPlayers = []; @override void initState() { super.initState(); - db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c641dde..57d05a4 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -21,7 +21,11 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late final AppDatabase db; + + /// Loaded groups from the database late List loadedGroups; + + /// Loading state bool isLoading = true; List groups = List.filled( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 072699e..170adb4 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -21,9 +21,17 @@ class HomeView extends StatefulWidget { class _HomeViewState extends State { bool isLoading = true; + + /// Amount of matches in the database int matchCount = 0; + + /// Amount of groups in the database int groupCount = 0; + + /// Loaded recent matches from the database List loadedRecentMatches = []; + + /// Recent matches to display, initially filled with skeleton matches List recentMatches = List.filled( 2, Match( @@ -42,32 +50,7 @@ class _HomeViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); - Future.wait([ - db.matchDao.getMatchCount(), - db.groupDao.getGroupCount(), - db.matchDao.getAllMatches(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) { - matchCount = results[0] as int; - groupCount = results[1] as int; - loadedRecentMatches = results[2] as List; - recentMatches = - (loadedRecentMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) - .toList(); - if (loadedRecentMatches.length < 2) { - recentMatches.add( - Match(name: 'Dummy Match', winner: null, group: null, players: null), - ); - } - if (mounted) { - setState(() { - isLoading = false; - }); - } - }); + loadHomeViewData(); } @override @@ -230,6 +213,40 @@ class _HomeViewState extends State { ); } + /// Loads the data for the HomeView from the database. + /// This includes the match count, group count, and recent matches. + void loadHomeViewData() { + final db = Provider.of(context, listen: false); + Future.wait([ + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + loadedRecentMatches = results[2] as List; + recentMatches = + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: 'Dummy Match', winner: null, group: null, players: null), + ); + } + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + + /// Generates a text representation of the players in the match. + /// If the match has a group, it returns the group name and the number of additional players. + /// If there is no group, it returns the count of players. String _getPlayerText(Match game, context) { final loc = AppLocalizations.of(context); if (game.group == null) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 18e1e9d..01981e2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -20,10 +20,12 @@ class ChooseGameView extends StatefulWidget { } class _ChooseGameViewState extends State { - late int selectedGameIndex; - + /// Controller for the search bar final TextEditingController searchBarController = TextEditingController(); + /// Currently selected game index + late int selectedGameIndex; + @override void initState() { selectedGameIndex = widget.initialGameIndex; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 5101db6..011b819 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -136,8 +136,7 @@ class _ChooseGroupViewState extends State { ); } - /// Filters the groups based on the search query. - /// TODO: Maybe implement also targetting player names? + /// Filters the groups based on the search [query]. void filterGroups(String query) { setState(() { if (query.isEmpty) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 7a41417..73be471 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -19,6 +19,7 @@ class ChooseRulesetView extends StatefulWidget { } class _ChooseRulesetViewState extends State { + /// Currently selected ruleset index late int selectedRulesetIndex; @override 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 d3a23ae..775f29d 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 @@ -26,7 +26,6 @@ class CreateMatchView extends StatefulWidget { } class _CreateMatchViewState extends State { - /// Reference to the app database late final AppDatabase db; /// Controller for the match name input field 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 5c455f6..93ebbc6 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 @@ -18,8 +18,12 @@ class MatchResultView extends StatefulWidget { } class _MatchResultViewState extends State { - late final List allPlayers; late final AppDatabase db; + + /// List of all players who participated in the match + late final List allPlayers; + + /// Currently selected winner player Player? _selectedPlayer; @override @@ -132,6 +136,8 @@ class _MatchResultViewState extends State { ); } + /// Handles saving or removing the winner in the database + /// based on the current selection. Future _handleWinnerSaving() async { if (_selectedPlayer == null) { await db.matchDao.removeWinner(matchId: widget.match.id); @@ -144,6 +150,10 @@ class _MatchResultViewState extends State { widget.onWinnerChanged?.call(); } + /// Retrieves all players associated with the given [match]. + /// This includes players directly assigned to the match + /// as well as members of the group (if any). + /// The returned list is sorted alphabetically by player name. List getAllPlayers(Match match) { List players = []; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index bea2f14..45b957f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -28,6 +28,8 @@ class _MatchViewState extends State { late final AppDatabase db; bool isLoading = true; + /// Loaded matches from the database, + /// initially filled with skeleton matches List matches = List.filled( 4, Match( @@ -44,7 +46,6 @@ class _MatchViewState extends State { @override void initState() { super.initState(); - db = Provider.of(context, listen: false); loadGames(); } @@ -117,6 +118,7 @@ class _MatchViewState extends State { ); } + /// Loads the games from the database and sorts them by creation date. void loadGames() { Future.wait([ db.matchDao.getAllMatches(), diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 1125118..a60b854 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,28 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); - - final db = Provider.of(context, listen: false); - - Future.wait([ - db.matchDao.getAllMatches(), - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) async { - if (!mounted) return; - final matches = results[0] as List; - final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players, context); - matchCounts = _calculateMatchAmountsForAllPlayers( - matches, - players, - context, - ); - winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - setState(() { - isLoading = false; - }); - }); + loadStatisticData(); } @override @@ -118,13 +97,43 @@ class _StatisticsViewState extends State { ); } + /// Loads matches and players from the database + /// and calculates statistics for each player + void loadStatisticData() { + final db = Provider.of(context, listen: false); + + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) async { + if (!mounted) return; + final matches = results[0] as List; + final players = results[1] as List; + winCounts = _calculateWinsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); + setState(() { + isLoading = false; + }); + }); + } + /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) - List<(String, int)> _calculateWinsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateWinsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> winCounts = []; final loc = AppLocalizations.of(context); @@ -169,11 +178,11 @@ class _StatisticsViewState extends State { /// Calculates the number of matches played for each player /// and returns a sorted list of tuples (playerName, matchCount) - List<(String, int)> _calculateMatchAmountsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateMatchAmountsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> matchCounts = []; final loc = AppLocalizations.of(context); From 21c74b74bc16b8ea54e297ffdaa41a00ca35580f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:05:19 +0100 Subject: [PATCH 03/19] Refactored components --- lib/core/enums.dart | 3 + lib/presentation/widgets/app_skeleton.dart | 17 +- .../widgets/buttons/custom_width_button.dart | 13 ++ .../widgets/buttons/quick_create_button.dart | 11 +- lib/presentation/widgets/navbar_item.dart | 27 ++- .../widgets/player_selection.dart | 198 ++++++++++-------- .../widgets/text_input/custom_search_bar.dart | 42 +++- .../widgets/text_input/text_input_field.dart | 17 +- .../widgets/tiles/choose_tile.dart | 16 +- .../widgets/tiles/custom_radio_list_tile.dart | 17 +- .../widgets/tiles/group_tile.dart | 6 + lib/presentation/widgets/tiles/info_tile.dart | 31 ++- .../widgets/tiles/match_tile.dart | 35 +++- .../widgets/tiles/quick_info_tile.dart | 31 ++- .../widgets/tiles/settings_list_tile.dart | 23 +- .../widgets/tiles/statistics_tile.dart | 18 ++ .../widgets/tiles/text_icon_list_tile.dart | 19 +- .../widgets/tiles/text_icon_tile.dart | 19 +- .../tiles/title_description_list_tile.dart | 32 ++- .../widgets/top_centered_message.dart | 13 +- 20 files changed, 429 insertions(+), 159 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index ce06f85..17a01f6 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; /// Button types used for styling the [CustomWidthButton] +/// - [ButtonType.primary]: Primary button style. +/// - [ButtonType.secondary]: Secondary button style. +/// - [ButtonType.tertiary]: Tertiary button style. enum ButtonType { primary, secondary, tertiary } /// Result types for import operations in the [SettingsView] diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart index 209f1d8..1d74456 100644 --- a/lib/presentation/widgets/app_skeleton.dart +++ b/lib/presentation/widgets/app_skeleton.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; +/// A widget that provides a skeleton loading effect to its child widget tree. +/// - [child]: The widget tree to apply the skeleton effect to. +/// - [enabled]: A boolean to enable or disable the skeleton effect. +/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher. class AppSkeleton extends StatefulWidget { - final Widget child; - final bool enabled; - final bool fixLayoutBuilder; - const AppSkeleton({ super.key, required this.child, @@ -13,6 +13,15 @@ class AppSkeleton extends StatefulWidget { this.fixLayoutBuilder = false, }); + /// The widget tree to apply the skeleton effect to. + final Widget child; + + /// A boolean to enable or disable the skeleton effect. + final bool enabled; + + /// A boolean to fix the layout builder for AnimatedSwitcher. + final bool fixLayoutBuilder; + @override State createState() => _AppSkeletonState(); } diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 17c9dc5..e27f009 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -2,6 +2,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +/// A custom button widget that is designed to have a width relative to the screen size. +/// It supports three types of buttons: primary, secondary, and text buttons. +/// - [text]: The text to display on the button. +/// - [buttonType]: The type of button to display. Defaults to [ButtonType.primary]. +/// - [sizeRelativeToWidth]: The size of the button relative to the width of the screen. +/// - [onPressed]: The callback to be invoked when the button is pressed. class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, @@ -11,9 +17,16 @@ class CustomWidthButton extends StatelessWidget { this.onPressed, }); + /// The text to display on the button. final String text; + + /// The size of the button relative to the width of the screen. final double sizeRelativeToWidth; + + /// The callback to be invoked when the button is pressed. final VoidCallback? onPressed; + + /// The type of button to display. Depends on the enum [ButtonType]. final ButtonType buttonType; @override diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 3860f1c..2f61805 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -1,15 +1,22 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A button widget designed for quick creating matches in the [HomeView] +/// - [text]: The text to display on the button. +/// - [onPressed]: The callback to be invoked when the button is pressed. class QuickCreateButton extends StatefulWidget { - final String text; - final VoidCallback? onPressed; const QuickCreateButton({ super.key, required this.text, required this.onPressed, }); + /// The text to display on the button. + final String text; + + /// The callback to be invoked when the button is pressed. + final VoidCallback? onPressed; + @override State createState() => _QuickCreateButtonState(); } diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index b249571..13a8d4d 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; +/// A navigation bar item widget that represents a single tab in a navigation bar. +/// - [index]: The index of the tab. +/// - [isSelected]: A boolean indicating whether the tab is currently selected. +/// - [icon]: The icon to display for the tab. +/// - [label]: The label to display for the tab. +/// - [onTabTapped]: The callback to be invoked when the tab is tapped. class NavbarItem extends StatefulWidget { - final int index; - final bool isSelected; - final IconData icon; - final String label; - final Function(int) onTabTapped; - const NavbarItem({ super.key, required this.index, @@ -16,6 +16,21 @@ class NavbarItem extends StatefulWidget { required this.onTabTapped, }); + /// The index of the tab. + final int index; + + /// A boolean indicating whether the tab is currently selected. + final bool isSelected; + + /// The icon to display for the tab. + final IconData icon; + + /// The label to display for the tab. + final String label; + + /// The callback to be invoked when the tab is tapped. + final Function(int) onTabTapped; + @override State createState() => _NavbarItemState(); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9eb005a..99b1e1c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,31 +11,55 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +/// A widget that allows users to select players from a list, +/// with search functionality and the ability to add new players. +/// - [availablePlayers]: An optional list of players to choose from. If null, all +/// players from the database are used. +/// - [initialSelectedPlayers]: An optional list of players that should be pre-selected. +/// - [onChanged]: A callback function that is invoked whenever the selection changes, +/// providing the updated list of selected players. class PlayerSelection extends StatefulWidget { - final Function(List value) onChanged; - final List? availablePlayers; - final List? initialSelectedPlayers; - const PlayerSelection({ super.key, - required this.onChanged, this.availablePlayers, this.initialSelectedPlayers, + required this.onChanged, }); + /// An optional list of players to choose from. If null, all players from the database are used. + final List? availablePlayers; + + /// An optional list of players that should be pre-selected. + final List? initialSelectedPlayers; + + /// A callback function that is invoked whenever the selection changes, + final Function(List value) onChanged; + @override State createState() => _PlayerSelectionState(); } class _PlayerSelectionState extends State { - List selectedPlayers = []; - List suggestedPlayers = []; - List allPlayers = []; + late final AppDatabase db; bool isLoading = true; + + /// Future that loads all players from the database. + late Future> _allPlayersFuture; + + /// The complete list of all available players. + List allPlayers = []; + + /// The list of players suggested based on the search input. + List suggestedPlayers = []; + + /// The list of currently selected players. + List selectedPlayers = []; + + /// Controller for the search bar input. late final TextEditingController _searchBarController = TextEditingController(); - late final AppDatabase db; - late Future> _allPlayersFuture; + + /// Skeleton data used while loading players. late final List skeletonData = List.filled( 7, Player(name: 'Player 0'), @@ -49,42 +73,6 @@ class _PlayerSelectionState extends State { loadPlayerList(); } - void loadPlayerList() { - _allPlayersFuture = Future.wait([ - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) => results[0] as List); - if (mounted) { - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided (even if empty), use that list. - if (widget.availablePlayers != null) { - widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers!]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers!.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - isLoading = false; - }); - }); - } - } - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -227,53 +215,97 @@ class _PlayerSelectionState extends State { ); } + /// Loads the list of players from the database or uses the provided available players. + /// Sets the loading state and updates the player lists accordingly. + void loadPlayerList() { + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) => results[0] as List); + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { + setState(() { + // If a list of available players is provided (even if empty), use that list. + if (widget.availablePlayers != null) { + widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers!]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers!.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } + isLoading = false; + }); + }); + } + } + /// Adds a new player to the database from the search bar input. /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. - void addNewPlayerFromSearch({required BuildContext context}) async { + Future addNewPlayerFromSearch({required BuildContext context}) async { final loc = AppLocalizations.of(context); - String playerName = _searchBarController.text.trim(); - Player createdPlayer = Player(name: playerName); - bool success = await db.playerDao.addPlayer(player: createdPlayer); + final playerName = _searchBarController.text.trim(); + + final createdPlayer = Player(name: playerName); + final success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { - selectedPlayers.insert(0, createdPlayer); - widget.onChanged([...selectedPlayers]); - allPlayers.add(createdPlayer); - setState(() { - _searchBarController.clear(); - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - AppLocalizations.of( - context, - ).successfully_added_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + _handleSuccessfulPlayerCreation(createdPlayer); + showSnackBarMessage(loc.successfully_added_player(playerName)); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - loc.could_not_add_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + showSnackBarMessage(loc.could_not_add_player(playerName)); } } + /// Updates the state after successfully adding a new player. + void _handleSuccessfulPlayerCreation(Player player) { + selectedPlayers.insert(0, player); + widget.onChanged([...selectedPlayers]); + allPlayers.add(player); + + setState(() { + _searchBarController.clear(); + _updateSuggestedPlayers(); + }); + } + + /// Updates the suggested players list based on current selection. + void _updateSuggestedPlayers() { + suggestedPlayers = allPlayers + .where((player) => !selectedPlayers.contains(player)) + .toList(); + } + + /// Displays a snackbar message at the bottom of the screen. + /// [message] - The message to display in the snackbar. + void showSnackBarMessage(String message) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text(message, style: const TextStyle(color: Colors.white)), + ), + ), + ); + } + /// Determines the appropriate info text to display when no players /// are available in the suggested players list. String _getInfoText(BuildContext context) { diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index 35c11e1..bf7971a 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom search bar widget that encapsulates a [SearchBar] with additional customization options. +/// - [controller]: The controller for the search bar's text input. +/// - [hintText]: The hint text displayed in the search bar when it is empty. +/// - [trailingButtonShown]: Whether to show the trailing button. +/// - [trailingButtonicon]: The icon for the trailing button. +/// - [trailingButtonEnabled]: Whether the trailing button is in enabled state. +/// - [onTrailingButtonPressed]: The callback invoked when the trailing button is pressed. +/// - [onChanged]: The callback invoked when the text in the search bar changes. +/// - [constraints]: The constraints for the search bar. class CustomSearchBar extends StatelessWidget { - final TextEditingController controller; - final String hintText; - final ValueChanged? onChanged; - final BoxConstraints? constraints; - final bool trailingButtonShown; - final bool trailingButtonEnabled; - final VoidCallback? onTrailingButtonPressed; - final IconData trailingButtonicon; - const CustomSearchBar({ super.key, required this.controller, @@ -23,6 +23,30 @@ class CustomSearchBar extends StatelessWidget { this.constraints, }); + /// The controller for the search bar's text input. + final TextEditingController controller; + + /// The hint text displayed in the search bar when it is empty. + final String hintText; + + /// Whether to show the trailing button. + final bool trailingButtonShown; + + /// The icon for the trailing button. + final IconData trailingButtonicon; + + /// Whether the trailing button is in enabled state. + final bool trailingButtonEnabled; + + /// The callback invoked when the trailing button is pressed. + final VoidCallback? onTrailingButtonPressed; + + /// The callback invoked when the text in the search bar changes. + final ValueChanged? onChanged; + + /// The constraints for the search bar. + final BoxConstraints? constraints; + @override Widget build(BuildContext context) { return SearchBar( diff --git a/lib/presentation/widgets/text_input/text_input_field.dart b/lib/presentation/widgets/text_input/text_input_field.dart index 6cd9d75..a409c68 100644 --- a/lib/presentation/widgets/text_input/text_input_field.dart +++ b/lib/presentation/widgets/text_input/text_input_field.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom text input field widget that encapsulates a [TextField] with specific styling. +/// - [controller]: The controller for the text input field. +/// - [onChanged]: The callback invoked when the text in the field changes. +/// - [hintText]: The hint text displayed in the text input field when it is empty class TextInputField extends StatelessWidget { - final TextEditingController controller; - final ValueChanged? onChanged; - final String hintText; - const TextInputField({ super.key, required this.controller, @@ -13,6 +13,15 @@ class TextInputField extends StatelessWidget { this.onChanged, }); + /// The controller for the text input field. + final TextEditingController controller; + + /// The callback invoked when the text in the field changes. + final ValueChanged? onChanged; + + /// The hint text displayed in the text input field when it is empty. + final String hintText; + @override Widget build(BuildContext context) { return TextField( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index 10a695d..ba12c3d 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that allows users to choose an option by tapping on it. +/// - [title]: The title text displayed on the tile. +/// - [trailingText]: Optional trailing text displayed on the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class ChooseTile extends StatefulWidget { - final String title; - final VoidCallback? onPressed; - final String? trailingText; const ChooseTile({ super.key, required this.title, @@ -12,6 +13,15 @@ class ChooseTile extends StatefulWidget { this.onPressed, }); + /// The title text displayed on the tile. + final String title; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// Optional trailing text displayed on the tile. + final String? trailingText; + @override State createState() => _ChooseTileState(); } diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 11e8b40..d76cf3f 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality. +/// - [text]: The text to display next to the radio button. +/// - [value]: The value associated with the radio button. +/// - [onContainerTap]: The callback invoked when the container is tapped. class CustomRadioListTile extends StatelessWidget { - final String text; - final T value; - final ValueChanged onContainerTap; - const CustomRadioListTile({ super.key, required this.text, @@ -13,6 +13,15 @@ class CustomRadioListTile extends StatelessWidget { required this.onContainerTap, }); + /// The text to display next to the radio button. + final String text; + + /// The value associated with the radio button. + final T value; + + /// The callback invoked when the container is tapped. + final ValueChanged onContainerTap; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 5f870de..8dee1cd 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -3,10 +3,16 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +/// A tile widget that displays information about a group, including its name and members. +/// - [group]: The group data to be displayed. +/// - [isHighlighted]: Whether the tile should be highlighted. class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group, this.isHighlighted = false}); + /// The group data to be displayed. final Group group; + + /// Whether the tile should be highlighted. final bool isHighlighted; @override diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index ff73e59..3e11679 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and some content below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [content]: The content widget displayed below the title. +/// - [padding]: Optional padding for the tile content. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. class InfoTile extends StatefulWidget { - final String title; - final IconData icon; - final Widget content; - final EdgeInsets? padding; - final double? height; - final double? width; const InfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class InfoTile extends StatefulWidget { this.width, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The content widget displayed below the title. + final Widget content; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + @override State createState() => _InfoTileState(); } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index c455949..d764dd9 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -1,26 +1,41 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; +/// A tile widget that displays information about a match, including its name, +/// creation date, associated group, winner, and players. +/// - [match]: The match data to be displayed. +/// - [onTap]: The callback invoked when the tile is tapped. class MatchTile extends StatefulWidget { - final Match match; - final VoidCallback onTap; - const MatchTile({super.key, required this.match, required this.onTap}); + /// The match data to be displayed. + final Match match; + + /// The callback invoked when the tile is tapped. + final VoidCallback onTap; + @override State createState() => _MatchTileState(); } class _MatchTileState extends State { + late final List _allPlayers; + + @override + void initState() { + super.initState(); + _allPlayers = _getCombinedPlayers(); + } + @override Widget build(BuildContext context) { final group = widget.match.group; final winner = widget.match.winner; - final allPlayers = _getAllPlayers(); final loc = AppLocalizations.of(context); return GestureDetector( @@ -114,7 +129,7 @@ class _MatchTileState extends State { const SizedBox(height: 12), ], - if (allPlayers.isNotEmpty) ...[ + if (_allPlayers.isNotEmpty) ...[ Text( loc.players, style: const TextStyle( @@ -127,7 +142,7 @@ class _MatchTileState extends State { Wrap( spacing: 6, runSpacing: 6, - children: allPlayers.map((player) { + children: _allPlayers.map((player) { return TextIconTile(text: player.name, iconEnabled: false); }).toList(), ), @@ -138,6 +153,8 @@ class _MatchTileState extends State { ); } + /// Formats the given [dateTime] into a human-readable string based on its + /// difference from the current date. String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); @@ -158,8 +175,10 @@ class _MatchTileState extends State { } } - List _getAllPlayers() { - final allPlayers = []; + /// Retrieves all unique players associated with the match, + /// combining players from both the match and its group. + List _getCombinedPlayers() { + final allPlayers = []; final playerIds = {}; // Add players from game.players diff --git a/lib/presentation/widgets/tiles/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart index d360aba..839f6c3 100644 --- a/lib/presentation/widgets/tiles/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and a numeric value below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [value]: The numeric value displayed below the title. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. +/// - [padding]: Optional padding for the tile content. class QuickInfoTile extends StatefulWidget { - final String title; - final IconData icon; - final int value; - final double? height; - final double? width; - final EdgeInsets? padding; const QuickInfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class QuickInfoTile extends StatefulWidget { this.padding, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The numeric value displayed below the title. + final int value; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + @override State createState() => _QuickInfoTileState(); } diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 6b43557..7fb0f80 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,19 +1,32 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. +/// - [icon]: The icon displayed on the left side of the tile. +/// - [title]: The title text displayed next to the icon. +/// - [suffixWidget]: An optional widget displayed on the right side of the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class SettingsListTile extends StatelessWidget { - final VoidCallback? onPressed; - final IconData icon; - final String title; - final Widget? suffixWidget; const SettingsListTile({ super.key, - required this.title, required this.icon, + required this.title, this.suffixWidget, this.onPressed, }); + /// The icon displayed on the left side of the tile. + final IconData icon; + + /// The title text displayed next to the icon. + final String title; + + /// An optional widget displayed on the right side of the tile. + final Widget? suffixWidget; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Padding( diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 598fad0..57ceb04 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -4,6 +4,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +/// A tile widget that displays statistical data using horizontal bars. +/// - [icon]: The icon displayed next to the title. +/// - [title]: The title text displayed on the tile. +/// - [width]: The width of the tile. +/// - [values]: A list of tuples containing labels and their corresponding numeric values. +/// - [itemCount]: The maximum number of items to display. +/// - [barColor]: The color of the bars representing the values. class StatisticsTile extends StatelessWidget { const StatisticsTile({ super.key, @@ -15,11 +22,22 @@ class StatisticsTile extends StatelessWidget { required this.barColor, }); + /// The icon displayed next to the title. final IconData icon; + + /// The title text displayed on the tile. final String title; + + /// The width of the tile. final double width; + + /// A list of tuples containing labels and their corresponding numeric values. final List<(String, num)> values; + + /// The maximum number of items to display. final int itemCount; + + /// The color of the bars representing the values. final Color barColor; @override diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index b23ef75..7d3fe1c 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A list tile widget that displays text with an optional icon button. +/// - [text]: The text to display in the tile. +/// - [onPressed]: The callback to be invoked when the icon is pressed. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. class TextIconListTile extends StatelessWidget { - final String text; - final VoidCallback? onPressed; - final bool iconEnabled; - const TextIconListTile({ super.key, required this.text, - this.onPressed, this.iconEnabled = true, + this.onPressed, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is pressed. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 2544837..7142b27 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays text with an optional icon that can be tapped. +/// - [text]: The text to display in the tile. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. +/// - [onIconTap]: The callback to be invoked when the icon is tapped. class TextIconTile extends StatelessWidget { - final String text; - final bool iconEnabled; - final VoidCallback? onIconTap; - const TextIconTile({ super.key, required this.text, - this.onIconTap, this.iconEnabled = true, + this.onIconTap, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is tapped. + final VoidCallback? onIconTap; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 465c94d..781149e 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// 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. +/// - [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. class TitleDescriptionListTile extends StatelessWidget { - final String title; - final String description; - final VoidCallback? onPressed; - final bool isHighlighted; - final String? badgeText; - final Color? badgeColor; - const TitleDescriptionListTile({ super.key, required this.title, @@ -19,6 +19,24 @@ class TitleDescriptionListTile extends StatelessWidget { this.badgeColor, }); + /// The title text displayed on the tile. + final String title; + + /// The description text displayed below the title. + final String description; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// A boolean to determine if the tile should be highlighted. + final bool isHighlighted; + + /// Optional text to display in a badge on the right side of the title. + final String? badgeText; + + /// Optional color for the badge background. + final Color? badgeColor; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index a5deea2..c15c93d 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +/// A widget that displays a message centered at the top of the screen with an icon, title, and message. +/// - [icon]: The icon to display above the title. +/// - [title]: The title text to display. +/// - [message]: The message text to display below the title. class TopCenteredMessage extends StatelessWidget { const TopCenteredMessage({ super.key, @@ -8,10 +12,15 @@ class TopCenteredMessage extends StatelessWidget { required this.message, }); - final String title; - final String message; + /// The icon to display above the title. final IconData icon; + /// The title text to display. + final String title; + + /// The message text to display below the title. + final String message; + @override Widget build(BuildContext context) { return Container( From 0f824bb30a3feb48804f5f00342f7e465b9810c4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:10:09 +0100 Subject: [PATCH 04/19] Optimized statistics tile --- .../widgets/tiles/statistics_tile.dart | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 57ceb04..2c0ced0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -42,7 +42,6 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { - final maxBarWidth = MediaQuery.of(context).size.width * 0.65; final loc = AppLocalizations.of(context); return InfoTile( @@ -57,38 +56,56 @@ class StatisticsTile extends StatelessWidget { heightFactor: 4, child: Text(loc.no_data_available), ), - child: Column( - children: List.generate(min(values.length, itemCount), (index) { - /// The maximum wins among all players - final maxMatches = values.isNotEmpty ? values[0].$2 : 0; + child: LayoutBuilder( + builder: (context, constraints) { + final maxBarWidth = constraints.maxWidth * 0.65; + return Column( + children: List.generate(min(values.length, itemCount), (index) { + /// The maximum wins among all players + final maxMatches = values.isNotEmpty ? values[0].$2 : 0; - /// Fraction of wins - final double fraction = (maxMatches > 0) - ? (values[index].$2 / maxMatches) - : 0.0; + /// Fraction of wins + final double fraction = (maxMatches > 0) + ? (values[index].$2 / maxMatches) + : 0.0; - /// Calculated width for current the bar - final double barWidth = maxBarWidth * fraction; + /// Calculated width for current the bar + final double barWidth = maxBarWidth * fraction; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Stack( + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Container( - height: 24, - width: barWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: barColor, - ), + Stack( + children: [ + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barColor, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + values[index].$1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), - Padding( - padding: const EdgeInsets.only(left: 4.0), + const Spacer(), + Center( child: Text( - values[index].$1, + values[index].$2 <= 1 && values[index].$2 is double + ? values[index].$2.toStringAsFixed(2) + : values[index].$2.toString(), + textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -97,23 +114,10 @@ class StatisticsTile extends StatelessWidget { ), ], ), - const Spacer(), - Center( - child: Text( - values[index].$2 <= 1 && values[index].$2 is double - ? values[index].$2.toStringAsFixed(2) - : values[index].$2.toString(), - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), + ); + }), ); - }), + }, ), ), ), From 4b1d3923a094cdc977db2011dcbb55fbe2022d30 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:11:14 +0100 Subject: [PATCH 05/19] Optimized ruleset touples --- .../create_match/create_match_view.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 775f29d..2bc56e6 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 @@ -67,6 +67,9 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; + /// List of available rulesets with their localized string representations + late final List<(Ruleset, String)> _rulesets; + @override void initState() { super.initState(); @@ -88,9 +91,11 @@ class _CreateMatchViewState extends State { }); } - List<(Ruleset, String)> _getRulesets(BuildContext context) { + @override + void didChangeDependencies() { + super.didChangeDependencies(); final loc = AppLocalizations.of(context); - return [ + _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), (Ruleset.mostPoints, loc.ruleset_most_points), @@ -147,9 +152,9 @@ class _CreateMatchViewState extends State { if (selectedGameIndex != -1) { hintText = games[selectedGameIndex].$1; selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + selectedRulesetIndex = _rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); } else { hintText = 'Match Name'; selectedRuleset = null; @@ -163,17 +168,16 @@ class _CreateMatchViewState extends State { ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { - final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); if (!mounted) return; - selectedRulesetIndex = rulesets.indexWhere( + selectedRulesetIndex = _rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); selectedGameIndex = -1; From 2811ea892e013359e4a1f4572437feafa621f189 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:15:23 +0100 Subject: [PATCH 06/19] Added dispose & formatting --- .../match_view/create_match/create_match_view.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 2bc56e6..fe1e8f5 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 @@ -91,6 +91,12 @@ class _CreateMatchViewState extends State { }); } + @override + void dispose() { + _matchNameController.dispose(); + super.dispose(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -262,8 +268,8 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1) && - selectedRuleset != null; + return (selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.length > 1)) && + selectedRuleset != null; } } From aa936a938d71a8b6d9df6392d73b9f8ceacd45ca Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:23:47 +0100 Subject: [PATCH 07/19] Corrected translation --- lib/l10n/arb/app_de.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 89354bd..14e3213 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:in: {count}", + "selected_players": "Ausgewählte Spieler:innen: {count}", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", From bc3beac866f6ec95bea480a58ebf6646db2e068e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:25:14 +0100 Subject: [PATCH 08/19] Replaced Matches with Spiele --- lib/l10n/arb/app_de.arb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 14e3213..de7db12 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2,16 +2,16 @@ "@@locale": "de", "all_players": "Alle Spieler:innen:", "all_players_selected": "Alle Spieler:innen ausgewählt", - "amount_of_matches": "Anzahl der Matches", + "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", "choose_ruleset": "Regelwerk wählen", "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", "create_group": "Gruppe erstellen", - "create_match": "Match erstellen", + "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", - "create_new_match": "Neues Match erstellen", + "create_new_match": "Neues Spiel erstellen", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_imported": "Daten erfolgreich importiert", @@ -34,19 +34,19 @@ "info": "Info", "invalid_schema": "Ungültiges Schema", "least_points": "Niedrigste Punkte", - "match_in_progress": "Match läuft...", - "match_name": "Matchname", - "matches": "Matches", + "match_in_progress": "Spiel läuft...", + "match_name": "Spieltitel", + "matches": "Spiele", "menu": "Menü", "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", - "no_matches_created_yet": "Noch keine Matches erstellt", + "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "no_players_selected": "Keine Spieler:in ausgewählt", - "no_recent_matches_available": "Keine letzten Matches verfügbar", - "no_second_match_available": "Kein zweites Match verfügbar", + "no_recent_matches_available": "Keine letzten Spiele verfügbar", + "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", "none": "Kein", "none_group": "Keine", @@ -55,7 +55,7 @@ "players": "Spieler:in", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", - "recent_matches": "Letzte Matches", + "recent_matches": "Letzte Spiele", "ruleset": "Regelwerk", "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", From e196d6e5ef12a69d15fde17d105b52b857ff1960 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:32:34 +0100 Subject: [PATCH 09/19] Corrected strings --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/generated/app_localizations.dart | 732 +++++++++---------- lib/l10n/generated/app_localizations_de.dart | 306 ++++---- lib/l10n/generated/app_localizations_en.dart | 388 +++++----- 4 files changed, 713 insertions(+), 717 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index de7db12..f55668e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -52,7 +52,7 @@ "none_group": "Keine", "not_available": "Nicht verfügbar", "player_name": "Spieler:innenname", - "players": "Spieler:in", + "players": "Spieler:innen", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner:in: {winnerName}", + "winner": "Gewinner*in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 951ff22..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -98,23 +98,29 @@ abstract class AppLocalizations { Locale('en'), ]; - /// Label for choosing a group + /// Label for all players list /// /// In en, this message translates to: - /// **'Choose Group'** - String get choose_group; + /// **'All players:'** + String get all_players; - /// Button text to create a new match + /// Message when all players are added to selection /// /// In en, this message translates to: - /// **'Create new match'** - String get create_new_match; + /// **'All players selected'** + String get all_players_selected; - /// Label for choosing a ruleset + /// Label for amount of matches statistic /// /// In en, this message translates to: - /// **'Choose Ruleset'** - String get choose_ruleset; + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; /// Label for choosing a game /// @@ -122,11 +128,125 @@ abstract class AppLocalizations { /// **'Choose Game'** String get choose_game; - /// Label to select the winner + /// Label for choosing a group /// /// In en, this message translates to: - /// **'Select Winner:'** - String get select_winner; + /// **'Choose Group'** + String get choose_group; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_creating_group; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; /// App Name /// @@ -134,6 +254,126 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get game_tracker; + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data_available; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -146,294 +386,12 @@ abstract class AppLocalizations { /// **'No second match available'** String get no_second_match_available; - /// Confirmation dialog for deleting all data - /// - /// In en, this message translates to: - /// **'Delete all data?'** - String get delete_all_data; - - /// Cancel button text - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Delete button text - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// Button text to create a new group - /// - /// In en, this message translates to: - /// **'Create new group'** - String get create_new_group; - - /// Error message when group creation fails - /// - /// In en, this message translates to: - /// **'Error while creating group, please try again'** - String get error_creating_group; - - /// Shows the number of selected players - /// - /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); - - /// Message when no players are selected - /// - /// In en, this message translates to: - /// **'No players selected'** - String get no_players_selected; - - /// Label for all players list - /// - /// In en, this message translates to: - /// **'All players:'** - String get all_players; - - /// Success message when adding a player - /// - /// In en, this message translates to: - /// **'Successfully added player {playerName}'** - String successfully_added_player(String playerName); - - /// Error message when adding a player fails - /// - /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); - - /// Shows the winner's name - /// - /// In en, this message translates to: - /// **'Winner: {winnerName}'** - String winner(String winnerName); - - /// Players label - /// - /// In en, this message translates to: - /// **'Players'** - String get players; - /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: /// **'No statistics available'** String get no_statistics_available; - /// Message when no data in the statistic tiles is given - /// - /// In en, this message translates to: - /// **'No data available'** - String get no_data_available; - - /// Label for matches - /// - /// In en, this message translates to: - /// **'Matches'** - String get matches; - - /// Label for groups - /// - /// In en, this message translates to: - /// **'Groups'** - String get groups; - - /// Title for recent matches section - /// - /// In en, this message translates to: - /// **'Recent Matches'** - String get recent_matches; - - /// Title for quick create section - /// - /// In en, this message translates to: - /// **'Quick Create'** - String get quick_create; - - /// Message when match is in progress - /// - /// In en, this message translates to: - /// **'Match in progress...'** - String get match_in_progress; - - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Export data menu item - /// - /// In en, this message translates to: - /// **'Export data'** - String get export_data; - - /// Import data menu item - /// - /// In en, this message translates to: - /// **'Import data'** - String get import_data; - - /// Warning message for irreversible actions - /// - /// In en, this message translates to: - /// **'This can\'t be undone'** - String get this_cannot_be_undone; - - /// Success message after deleting data - /// - /// In en, this message translates to: - /// **'Data successfully deleted'** - String get data_successfully_deleted; - - /// Success message after importing data - /// - /// In en, this message translates to: - /// **'Data successfully imported'** - String get data_successfully_imported; - - /// Error message for invalid schema - /// - /// In en, this message translates to: - /// **'Invalid Schema'** - String get invalid_schema; - - /// Error message when file cannot be read - /// - /// In en, this message translates to: - /// **'Error reading file'** - String get error_reading_file; - - /// Message when import is canceled - /// - /// In en, this message translates to: - /// **'Import canceled'** - String get import_canceled; - - /// Error message for format exceptions - /// - /// In en, this message translates to: - /// **'Format Exception (see console)'** - String get format_exception; - - /// Error message for unknown exceptions - /// - /// In en, this message translates to: - /// **'Unknown Exception (see console)'** - String get unknown_exception; - - /// Success message after exporting data - /// - /// In en, this message translates to: - /// **'Data successfully exported'** - String get data_successfully_exported; - - /// Message when export is canceled - /// - /// In en, this message translates to: - /// **'Export canceled'** - String get export_canceled; - - /// Undo button text - /// - /// In en, this message translates to: - /// **'Undo'** - String get undo; - - /// Label for wins statistic - /// - /// In en, this message translates to: - /// **'Wins'** - String get wins; - - /// Label for winrate statistic - /// - /// In en, this message translates to: - /// **'Winrate'** - String get winrate; - - /// Label for amount of matches statistic - /// - /// In en, this message translates to: - /// **'Amount of Matches'** - String get amount_of_matches; - - /// Info label - /// - /// In en, this message translates to: - /// **'Info'** - String get info; - - /// Message when no groups exist - /// - /// In en, this message translates to: - /// **'No groups created yet'** - String get no_groups_created_yet; - - /// Message when no players exist - /// - /// In en, this message translates to: - /// **'No players created yet'** - String get no_players_created_yet; - - /// Button text to create a group - /// - /// In en, this message translates to: - /// **'Create Group'** - String get create_group; - - /// Placeholder for group name input - /// - /// In en, this message translates to: - /// **'Group name'** - String get group_name; - - /// Placeholder for player name input - /// - /// In en, this message translates to: - /// **'Player name'** - String get player_name; - - /// Message when no matches exist - /// - /// In en, this message translates to: - /// **'No matches created yet'** - String get no_matches_created_yet; - - /// Placeholder for match name input - /// - /// In en, this message translates to: - /// **'Match name'** - String get match_name; - - /// Game label - /// - /// In en, this message translates to: - /// **'Game'** - String get game; - - /// Ruleset label - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset; - - /// Group label - /// - /// In en, this message translates to: - /// **'Group'** - String get group; - /// None option label /// /// In en, this message translates to: @@ -446,47 +404,113 @@ abstract class AppLocalizations { /// **'None'** String get none_group; - /// Button text to create a match + /// Abbreviation for not available /// /// In en, this message translates to: - /// **'Create match'** - String get create_match; + /// **'Not available'** + String get not_available; - /// Message when search returns no results + /// Placeholder for player name input /// /// In en, this message translates to: - /// **'No players found with that name'** - String get no_players_found_with_that_name; + /// **'Player name'** + String get player_name; - /// Message when all players are added to selection + /// Players label /// /// In en, this message translates to: - /// **'All players selected'** - String get all_players_selected; + /// **'Players'** + String get players; - /// Date format for today + /// Shows the number of players /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'{count} Players'** + String players_count(int count); - /// Date format for yesterday + /// Title for quick create section /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Quick Create'** + String get quick_create; - /// Date format for days ago + /// Title for recent matches section /// /// In en, this message translates to: - /// **'{count} days ago'** - String days_ago(int count); + /// **'Recent Matches'** + String get recent_matches; - /// Home tab label + /// Ruleset label /// /// In en, this message translates to: - /// **'Home'** - String get home; + /// **'Ruleset'** + String get ruleset; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; /// Statistics tab label /// @@ -500,11 +524,11 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; - /// Shows the number of players + /// Success message when adding a player /// /// In en, this message translates to: - /// **'{count} Players'** - String players_count(int count); + /// **'Successfully added player {playerName}'** + String successfully_added_player(String playerName); /// Message when search returns no groups /// @@ -512,77 +536,53 @@ abstract class AppLocalizations { /// **'There is no group matching your search'** String get there_is_no_group_matching_your_search; - /// Placeholder for game name search + /// Warning message for irreversible actions /// /// In en, this message translates to: - /// **'Game Name'** - String get game_name; + /// **'This can\'t be undone'** + String get this_cannot_be_undone; - /// Description for single winner ruleset + /// Date format for today /// /// In en, this message translates to: - /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner; + /// **'Today at {time}'** + String today_at(String time); - /// Description for single loser ruleset + /// Undo button text /// /// In en, this message translates to: - /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser; + /// **'Undo'** + String get undo; - /// Description for most points ruleset + /// Error message for unknown exceptions /// /// In en, this message translates to: - /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points; + /// **'Unknown Exception (see console)'** + String get unknown_exception; - /// Description for least points ruleset + /// Winner label /// /// In en, this message translates to: - /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points; + /// **'Winner'** + String get winner; - /// Title for single winner ruleset + /// Label for winrate statistic /// /// In en, this message translates to: - /// **'Single Winner'** - String get single_winner; + /// **'Winrate'** + String get winrate; - /// Title for single loser ruleset + /// Label for wins statistic /// /// In en, this message translates to: - /// **'Single Loser'** - String get single_loser; + /// **'Wins'** + String get wins; - /// Title for most points ruleset + /// Date format for yesterday /// /// In en, this message translates to: - /// **'Most Points'** - String get most_points; - - /// Title for least points ruleset - /// - /// In en, this message translates to: - /// **'Least Points'** - String get least_points; - - /// Hint text for player search input field - /// - /// In en, this message translates to: - /// **'Search for players'** - String get search_for_players; - - /// Hint text for group search input field - /// - /// In en, this message translates to: - /// **'Search for groups'** - String get search_for_groups; - - /// Abbreviation for not available - /// - /// In en, this message translates to: - /// **'Not available'** - String get not_available; + /// **'Yesterday at {time}'** + String yesterday_at(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3f3e36e..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,65 +9,25 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get choose_group => 'Gruppe wählen'; + String get all_players => 'Alle Spieler:innen:'; @override - String get create_new_match => 'Neues Match erstellen'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get choose_ruleset => 'Regelwerk wählen'; - - @override - String get choose_game => 'Spielvorlage wählen'; - - @override - String get select_winner => 'Gewinner:in wählen:'; - - @override - String get game_tracker => 'Game Tracker'; - - @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; - - @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; - - @override - String get delete_all_data => 'Alle Daten löschen?'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @override - String get delete => 'Löschen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get create_new_group => 'Neue Gruppe erstellen'; + String get choose_group => 'Gruppe wählen'; @override - String get error_creating_group => - 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; - - @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:in: $countString'; - } - - @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; - - @override - String get all_players => 'Alle Spieler:innen:'; - - @override - String successfully_added_player(String playerName) { - return 'Spieler:in $playerName erfolgreich hinzugefügt'; - } + String get choose_ruleset => 'Regelwerk wählen'; @override String could_not_add_player(String playerName) { @@ -75,121 +35,131 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String winner(String winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get create_group => 'Gruppe erstellen'; @override - String get players => 'Spieler:in'; + String get create_match => 'Spiel erstellen'; @override - String get no_statistics_available => 'Keine Statistiken verfügbar'; + String get create_new_group => 'Neue Gruppe erstellen'; @override - String get no_data_available => 'Keine Daten verfügbar'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Gruppen'; - - @override - String get recent_matches => 'Letzte Matches'; - - @override - String get quick_create => 'Schnellzugriff'; - - @override - String get match_in_progress => 'Match läuft...'; - - @override - String get menu => 'Menü'; - - @override - String get settings => 'Einstellungen'; - - @override - String get export_data => 'Daten exportieren'; - - @override - String get import_data => 'Daten importieren'; - - @override - String get this_cannot_be_undone => - 'Dies kann nicht rückgängig gemacht werden'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + @override String get data_successfully_imported => 'Daten erfolgreich importiert'; @override - String get invalid_schema => 'Ungültiges Schema'; + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get delete => 'Löschen'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get error_creating_group => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override String get error_reading_file => 'Fehler beim Lesen der Datei'; @override - String get import_canceled => 'Import abgebrochen'; + String get export_canceled => 'Export abgebrochen'; + + @override + String get export_data => 'Daten exportieren'; @override String get format_exception => 'Formatfehler (siehe Konsole)'; @override - String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + String get game => 'Spielvorlage'; @override - String get data_successfully_exported => 'Daten erfolgreich exportiert'; + String get game_name => 'Spielvorlagenname'; @override - String get export_canceled => 'Export abgebrochen'; + String get game_tracker => 'Game Tracker'; @override - String get undo => 'Rückgängig'; - - @override - String get wins => 'Siege'; - - @override - String get winrate => 'Siegquote'; - - @override - String get amount_of_matches => 'Anzahl der Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; - - @override - String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; - - @override - String get create_group => 'Gruppe erstellen'; + String get group => 'Gruppe'; @override String get group_name => 'Gruppenname'; @override - String get player_name => 'Spieler:innenname'; + String get groups => 'Gruppen'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get home => 'Startseite'; @override - String get match_name => 'Matchname'; + String get import_canceled => 'Import abgebrochen'; @override - String get game => 'Spielvorlage'; + String get import_data => 'Daten importieren'; @override - String get ruleset => 'Regelwerk'; + String get info => 'Info'; @override - String get group => 'Gruppe'; + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get least_points => 'Niedrigste Punkte'; + + @override + String get match_in_progress => 'Spiel läuft...'; + + @override + String get match_name => 'Spieltitel'; + + @override + String get matches => 'Spiele'; + + @override + String get menu => 'Menü'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler:in mit diesem Namen gefunden'; + + @override + String get no_players_selected => 'Keine Spieler:in ausgewählt'; + + @override + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; + + @override + String get no_statistics_available => 'Keine Statistiken verfügbar'; @override String get none => 'Kein'; @@ -198,32 +168,71 @@ class AppLocalizationsDe extends AppLocalizations { String get none_group => 'Keine'; @override - String get create_match => 'Match erstellen'; + String get not_available => 'Nicht verfügbar'; @override - String get no_players_found_with_that_name => - 'Keine Spieler:in mit diesem Namen gefunden'; + String get player_name => 'Spieler:innenname'; @override - String get all_players_selected => 'Alle Spieler:innen ausgewählt'; + String get players => 'Spieler:innen'; @override - String today_at(String time) { - return 'Heute um $time'; + String players_count(int count) { + return '$count Spieler'; } @override - String yesterday_at(String time) { - return 'Gestern um $time'; + String get quick_create => 'Schnellzugriff'; + + @override + String get recent_matches => 'Letzte Spiele'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + + @override + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get search_for_players => 'Nach Spieler:innen suchen'; + + @override + String get select_winner => 'Gewinner:in wählen:'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler:innen: $countString'; } @override - String days_ago(int count) { - return 'vor $count Tagen'; - } + String get settings => 'Einstellungen'; @override - String get home => 'Startseite'; + String get single_loser => 'Ein:e Verlierer:in'; + + @override + String get single_winner => 'Ein:e Gewinner:in'; @override String get statistics => 'Statistiken'; @@ -232,8 +241,8 @@ class AppLocalizationsDe extends AppLocalizations { String get stats => 'Statistiken'; @override - String players_count(int count) { - return '$count Spieler'; + String successfully_added_player(String playerName) { + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override @@ -241,42 +250,31 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielvorlagenname'; + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; @override - String get ruleset_single_winner => - 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String today_at(String time) { + return 'Heute um $time'; + } @override - String get ruleset_single_loser => - 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get undo => 'Rückgängig'; @override - String get ruleset_most_points => - 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get ruleset_least_points => - 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + String get winner => 'Gewinner*in'; @override - String get single_winner => 'Ein:e Gewinner:in'; + String get winrate => 'Siegquote'; @override - String get single_loser => 'Ein:e Verlierer:in'; + String get wins => 'Siege'; @override - String get most_points => 'Höchste Punkte'; - - @override - String get least_points => 'Niedrigste Punkte'; - - @override - String get search_for_players => 'Nach Spieler:innen suchen'; - - @override - String get search_for_groups => 'Nach Gruppen suchen'; - - @override - String get not_available => 'Nicht verfügbar'; + String yesterday_at(String time) { + return 'Gestern um $time'; + } } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 263714c..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,23 +9,149 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get choose_group => 'Choose Group'; + String get all_players => 'All players:'; @override - String get create_new_match => 'Create new match'; + String get all_players_selected => 'All players selected'; @override - String get choose_ruleset => 'Choose Ruleset'; + String get amount_of_matches => 'Amount of Matches'; + + @override + String get cancel => 'Cancel'; @override String get choose_game => 'Choose Game'; @override - String get select_winner => 'Select Winner:'; + String get choose_group => 'Choose Group'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; + } + + @override + String get create_group => 'Create Group'; + + @override + String get create_match => 'Create match'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get delete => 'Delete'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get error_creating_group => + 'Error while creating group, please try again'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get export_data => 'Export data'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get game => 'Game'; + + @override + String get game_name => 'Game Name'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Group'; + + @override + String get group_name => 'Group name'; + + @override + String get groups => 'Groups'; + + @override + String get home => 'Home'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get import_data => 'Import data'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get least_points => 'Least Points'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get match_name => 'Match name'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menu'; + + @override + String get most_points => 'Most Points'; + + @override + String get no_data_available => 'No data available'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get no_players_selected => 'No players selected'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -33,20 +159,61 @@ class AppLocalizationsEn extends AppLocalizations { String get no_second_match_available => 'No second match available'; @override - String get delete_all_data => 'Delete all data?'; + String get no_statistics_available => 'No statistics available'; @override - String get cancel => 'Cancel'; + String get none => 'None'; @override - String get delete => 'Delete'; + String get none_group => 'None'; @override - String get create_new_group => 'Create new group'; + String get not_available => 'Not available'; @override - String get error_creating_group => - 'Error while creating group, please try again'; + String get player_name => 'Player name'; + + @override + String get players => 'Players'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get quick_create => 'Quick Create'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get ruleset_least_points => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get ruleset_most_points => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_single_loser => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_single_winner => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get search_for_groups => 'Search for groups'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get select_winner => 'Select Winner:'; @override String selected_players(int count) { @@ -58,171 +225,14 @@ class AppLocalizationsEn extends AppLocalizations { return 'Selected players: $countString'; } - @override - String get no_players_selected => 'No players selected'; - - @override - String get all_players => 'All players:'; - - @override - String successfully_added_player(String playerName) { - return 'Successfully added player $playerName'; - } - - @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; - } - - @override - String winner(String winnerName) { - return 'Winner: $winnerName'; - } - - @override - String get players => 'Players'; - - @override - String get no_statistics_available => 'No statistics available'; - - @override - String get no_data_available => 'No data available'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Groups'; - - @override - String get recent_matches => 'Recent Matches'; - - @override - String get quick_create => 'Quick Create'; - - @override - String get match_in_progress => 'Match in progress...'; - - @override - String get menu => 'Menu'; - @override String get settings => 'Settings'; @override - String get export_data => 'Export data'; + String get single_loser => 'Single Loser'; @override - String get import_data => 'Import data'; - - @override - String get this_cannot_be_undone => 'This can\'t be undone'; - - @override - String get data_successfully_deleted => 'Data successfully deleted'; - - @override - String get data_successfully_imported => 'Data successfully imported'; - - @override - String get invalid_schema => 'Invalid Schema'; - - @override - String get error_reading_file => 'Error reading file'; - - @override - String get import_canceled => 'Import canceled'; - - @override - String get format_exception => 'Format Exception (see console)'; - - @override - String get unknown_exception => 'Unknown Exception (see console)'; - - @override - String get data_successfully_exported => 'Data successfully exported'; - - @override - String get export_canceled => 'Export canceled'; - - @override - String get undo => 'Undo'; - - @override - String get wins => 'Wins'; - - @override - String get winrate => 'Winrate'; - - @override - String get amount_of_matches => 'Amount of Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'No groups created yet'; - - @override - String get no_players_created_yet => 'No players created yet'; - - @override - String get create_group => 'Create Group'; - - @override - String get group_name => 'Group name'; - - @override - String get player_name => 'Player name'; - - @override - String get no_matches_created_yet => 'No matches created yet'; - - @override - String get match_name => 'Match name'; - - @override - String get game => 'Game'; - - @override - String get ruleset => 'Ruleset'; - - @override - String get group => 'Group'; - - @override - String get none => 'None'; - - @override - String get none_group => 'None'; - - @override - String get create_match => 'Create match'; - - @override - String get no_players_found_with_that_name => - 'No players found with that name'; - - @override - String get all_players_selected => 'All players selected'; - - @override - String today_at(String time) { - return 'Today at $time'; - } - - @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } - - @override - String days_ago(int count) { - return '$count days ago'; - } - - @override - String get home => 'Home'; + String get single_winner => 'Single Winner'; @override String get statistics => 'Statistics'; @@ -231,8 +241,8 @@ class AppLocalizationsEn extends AppLocalizations { String get stats => 'Stats'; @override - String players_count(int count) { - return '$count Players'; + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName'; } @override @@ -240,42 +250,30 @@ class AppLocalizationsEn extends AppLocalizations { 'There is no group matching your search'; @override - String get game_name => 'Game Name'; + String get this_cannot_be_undone => 'This can\'t be undone'; @override - String get ruleset_single_winner => - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + String today_at(String time) { + return 'Today at $time'; + } @override - String get ruleset_single_loser => - 'Exactly one loser is determined; last place receives the penalty or consequence.'; + String get undo => 'Undo'; @override - String get ruleset_most_points => - 'Traditional ruleset: the player with the most points wins.'; + String get unknown_exception => 'Unknown Exception (see console)'; @override - String get ruleset_least_points => - 'Inverse scoring: the player with the fewest points wins.'; + String get winner => 'Winner'; @override - String get single_winner => 'Single Winner'; + String get winrate => 'Winrate'; @override - String get single_loser => 'Single Loser'; + String get wins => 'Wins'; @override - String get most_points => 'Most Points'; - - @override - String get least_points => 'Least Points'; - - @override - String get search_for_players => 'Search for players'; - - @override - String get search_for_groups => 'Search for groups'; - - @override - String get not_available => 'Not available'; + String yesterday_at(String time) { + return 'Yesterday at $time'; + } } From cd2770be263ce47fafca0583f6276eaa4b3d67de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:33:52 +0100 Subject: [PATCH 10/19] Removed unnessecary setStates --- .../views/main_menu/group_view/create_group_view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 022a4b5..8037de4 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -64,9 +64,6 @@ class _CreateGroupViewState extends State { child: TextInputField( controller: _groupNameController, hintText: loc.group_name, - onChanged: (value) { - setState(() {}); - }, ), ), Expanded( @@ -111,7 +108,6 @@ class _CreateGroupViewState extends State { ), ); } - setState(() {}); }, ), const SizedBox(height: 20), From fdd0e7579ac6e486ad57cfb4fb74d108e081f7b7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:34:37 +0100 Subject: [PATCH 11/19] Updated game title process --- .../create_match/create_match_view.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 fe1e8f5..7b7deb0 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 @@ -32,7 +32,7 @@ class _CreateMatchViewState extends State { final TextEditingController _matchNameController = TextEditingController(); /// Hint text for the match name input field - String hintText = 'Match Name'; + String? hintText; /// List of all groups from the database List groupsList = []; @@ -101,6 +101,7 @@ class _CreateMatchViewState extends State { void didChangeDependencies() { super.didChangeDependencies(); final loc = AppLocalizations.of(context); + hintText ??= loc.match_name; _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), @@ -137,7 +138,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: hintText, + hintText: hintText ?? '', ), ), ChooseTile( @@ -162,7 +163,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = 'Match Name'; + hintText = AppLocalizations.of(context).match_name; selectedRuleset = null; } }); @@ -237,7 +238,7 @@ class _CreateMatchViewState extends State { ? () async { Match match = Match( name: _matchNameController.text.isEmpty - ? hintText + ? (hintText ?? '') : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, @@ -265,8 +266,11 @@ class _CreateMatchViewState extends State { ); } - /// Determines whether the "Create Game" button should be enabled based on - /// the current state of the input fields. + /// Determines whether the "Create Match" button should be enabled. + /// + /// Returns `true` if: + /// - A ruleset is selected AND + /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && From 02d79574dd024194221b0df8f8a5c7a24b18a406 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:43:13 +0100 Subject: [PATCH 12/19] Simplified app bar logic in views --- lib/core/custom_theme.dart | 3 +++ .../main_menu/group_view/create_group_view.dart | 10 +--------- .../match_view/create_match/choose_game_view.dart | 8 +------- .../match_view/create_match/choose_group_view.dart | 8 +------- .../match_view/create_match/choose_ruleset_view.dart | 8 +------- .../match_view/create_match/create_match_view.dart | 10 +--------- .../main_menu/match_view/match_result_view.dart | 12 +----------- 7 files changed, 9 insertions(+), 50 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 5930901..a9a31b2 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -25,10 +25,13 @@ class CustomTheme { backgroundColor: backgroundColor, foregroundColor: Colors.white, elevation: 0, + scrolledUnderElevation: 0, + centerTitle: true, titleTextStyle: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, ), iconTheme: const IconThemeData(color: Colors.white), ); diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 8037de4..3d09561 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,15 +46,7 @@ class _CreateGroupViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_group)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 01981e2..5976f72 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -38,19 +38,13 @@ class _ChooseGameViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop(selectedGameIndex); }, ), - title: Text( - loc.choose_game, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_game), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 011b819..97fbcef 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -38,8 +38,6 @@ class _ChooseGroupViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -52,11 +50,7 @@ class _ChooseGroupViewState extends State { ); }, ), - title: Text( - loc.choose_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_group), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 73be471..ca021af 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -37,8 +37,6 @@ class _ChooseRulesetViewState extends State { child: Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -49,11 +47,7 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: Text( - loc.choose_ruleset, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_ruleset), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the 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 7b7deb0..e99dfc1 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 @@ -121,15 +121,7 @@ class _CreateMatchViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_match, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_match)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, 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 93ebbc6..0d624f0 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 @@ -51,17 +51,7 @@ class _MatchResultViewState extends State { Navigator.of(context).pop(); }, ), - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - widget.match.name, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), - ), - centerTitle: true, + title: Text(widget.match.name), ), body: SafeArea( child: Column( From aef12bd65ab3b68140b6174e636a6a3e347a93ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:54:53 +0100 Subject: [PATCH 13/19] Refactored several theme settings into custom theme --- lib/core/custom_theme.dart | 31 ++++++++++++++++--- .../group_view/create_group_view.dart | 2 +- .../create_match/create_match_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 4 +-- .../widgets/buttons/custom_width_button.dart | 4 +-- .../widgets/buttons/quick_create_button.dart | 4 ++- .../widgets/player_selection.dart | 2 +- .../widgets/tiles/choose_tile.dart | 4 +-- .../widgets/tiles/custom_radio_list_tile.dart | 2 +- .../widgets/tiles/group_tile.dart | 2 +- .../widgets/tiles/match_tile.dart | 6 ++-- 11 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index a9a31b2..a6c6376 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,38 +1,59 @@ import 'package:flutter/material.dart'; class CustomTheme { + CustomTheme._(); // Private constructor to prevent instantiation + + // ==================== Colors ==================== static Color primaryColor = const Color(0xFF7505E4); static Color secondaryColor = const Color(0xFFAFA2FF); static Color backgroundColor = const Color(0xFF0B0B0B); static Color boxColor = const Color(0xFF101010); static Color onBoxColor = const Color(0xFF181818); static Color boxBorder = const Color(0xFF272727); + static const Color textColor = Colors.white; + // ==================== Border Radius ==================== + static const double standardBorderRadius = 12.0; + static BorderRadius get standardBorderRadiusAll => + BorderRadius.circular(standardBorderRadius); + + // ==================== Padding & Margins ==================== + static const EdgeInsets standardMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ); + static const EdgeInsets tileMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ); + + // ==================== Decorations ==================== static BoxDecoration standardBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, ); static BoxDecoration highlightedBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: primaryColor), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)], ); + // ==================== App Bar Theme ==================== static AppBarTheme appBarTheme = AppBarTheme( backgroundColor: backgroundColor, - foregroundColor: Colors.white, + foregroundColor: textColor, elevation: 0, scrolledUnderElevation: 0, centerTitle: true, titleTextStyle: const TextStyle( - color: Colors.white, + color: textColor, fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, ), - iconTheme: const IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: textColor), ); } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 3d09561..f92df0f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -52,7 +52,7 @@ class _CreateGroupViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, child: TextInputField( controller: _groupNameController, hintText: loc.group_name, 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 e99dfc1..0cc25d0 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 @@ -127,7 +127,7 @@ class _CreateMatchViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + margin: CustomTheme.tileMargin, child: TextInputField( controller: _matchNameController, hintText: hintText ?? '', diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index a60b854..53569ad 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -57,7 +57,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, - barColor: Colors.blue, + barColor: Colors.green, ), SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( @@ -75,7 +75,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, - barColor: Colors.green, + barColor: Colors.blue, ), ], ), diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index e27f009..7e52648 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -60,7 +60,7 @@ class CustomWidthButton extends StatelessWidget { 60, ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( @@ -91,7 +91,7 @@ class CustomWidthButton extends StatelessWidget { ), side: BorderSide(color: borderSideColor, width: 2), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 2f61805..40ebeab 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -29,7 +29,9 @@ class _QuickCreateButtonState extends State { style: ElevatedButton.styleFrom( minimumSize: const Size(140, 45), backgroundColor: CustomTheme.primaryColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: CustomTheme.standardBorderRadiusAll, + ), ), child: Text( widget.text, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 99b1e1c..a582427 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -77,7 +77,7 @@ class _PlayerSelectionState extends State { Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), decoration: CustomTheme.standardBoxDecoration, child: Column( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index ba12c3d..f6ec940 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -32,8 +32,8 @@ class _ChooseTileState extends State { return GestureDetector( onTap: widget.onPressed, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: CustomTheme.standardBoxDecoration, child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index d76cf3f..706aabb 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -32,7 +32,7 @@ class CustomRadioListTile extends StatelessWidget { decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 8dee1cd..64d9caa 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -18,7 +18,7 @@ class GroupTile extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedContainer( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: isHighlighted ? CustomTheme.highlightedBoxDecoration diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index d764dd9..bc349d3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -41,8 +41,8 @@ class _MatchTileState extends State { return GestureDetector( onTap: widget.onTap, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), @@ -118,7 +118,7 @@ class _MatchTileState extends State { style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white, + color: CustomTheme.textColor, ), overflow: TextOverflow.ellipsis, ), From d5ee6449b05461b87992410d851fd2c1a2ac7065 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:02:06 +0100 Subject: [PATCH 14/19] Updated localizations --- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 28 +++++++++----------- lib/l10n/generated/app_localizations_en.dart | 4 +-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index aea4457..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -564,7 +564,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Winner'** - String winner(Object winnerName); + String get winner; /// Label for winrate statistic /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index bfb9870..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -15,7 +15,7 @@ class AppLocalizationsDe extends AppLocalizations { String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get amount_of_matches => 'Anzahl der Matches'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @@ -38,13 +38,13 @@ class AppLocalizationsDe extends AppLocalizations { String get create_group => 'Gruppe erstellen'; @override - String get create_match => 'Match erstellen'; + String get create_match => 'Spiel erstellen'; @override String get create_new_group => 'Neue Gruppe erstellen'; @override - String get create_new_match => 'Neues Match erstellen'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; @@ -119,13 +119,13 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get match_in_progress => 'Match läuft...'; + String get match_in_progress => 'Spiel läuft...'; @override - String get match_name => 'Matchname'; + String get match_name => 'Spieltitel'; @override - String get matches => 'Matches'; + String get matches => 'Spiele'; @override String get menu => 'Menü'; @@ -140,7 +140,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; @override String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @@ -153,10 +153,10 @@ class AppLocalizationsDe extends AppLocalizations { String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -174,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get player_name => 'Spieler:innenname'; @override - String get players => 'Spieler:in'; + String get players => 'Spieler:innen'; @override String players_count(int count) { @@ -185,7 +185,7 @@ class AppLocalizationsDe extends AppLocalizations { String get quick_create => 'Schnellzugriff'; @override - String get recent_matches => 'Letzte Matches'; + String get recent_matches => 'Letzte Spiele'; @override String get ruleset => 'Regelwerk'; @@ -222,7 +222,7 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler:in: $countString'; + return 'Ausgewählte Spieler:innen: $countString'; } @override @@ -265,9 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String winner(Object winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get winner => 'Gewinner*in'; @override String get winrate => 'Siegquote'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 38ea20f..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -264,9 +264,7 @@ class AppLocalizationsEn extends AppLocalizations { String get unknown_exception => 'Unknown Exception (see console)'; @override - String winner(Object winnerName) { - return 'Winner'; - } + String get winner => 'Winner'; @override String get winrate => 'Winrate'; From 76121eb4fb5d02db5c9bf27fd43a38bf35d059ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:30:19 +0100 Subject: [PATCH 15/19] Updated localizations --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f55668e..4adbfa2 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner*in", + "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 4421fd1..88374a1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -265,7 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get winner => 'Gewinner*in'; + String get winner => 'Gewinner:in'; @override String get winrate => 'Siegquote'; From d741990f2fad4e11c68c2b009fd8ffc172bf6fde Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:07:30 +0100 Subject: [PATCH 16/19] Fixed navbar issue --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c1ed977..8219fcb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,8 +35,6 @@ class GameTracker extends StatelessWidget { }, debugShowCheckedModeBanner: false, onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, - darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, From cfb07bfe28b9af85b32b2b9c60ec15312b3009cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:13:24 +0100 Subject: [PATCH 17/19] Updated localizations --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/arb/app_en.arb | 712 +++++++++--------- lib/l10n/generated/app_localizations.dart | 24 +- lib/l10n/generated/app_localizations_de.dart | 16 +- lib/l10n/generated/app_localizations_en.dart | 18 +- lib/main.dart | 2 +- .../widgets/tiles/match_tile.dart | 8 +- 7 files changed, 377 insertions(+), 407 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4adbfa2..4ed0997 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -73,11 +73,11 @@ "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "today_at": "Heute um {time}", + "today_at": "Heute um", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", - "yesterday_at": "Gestern um {time}" + "yesterday_at": "Gestern um" } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d567f50..dd1e593 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,349 @@ { - "@@locale": "en", - "@all_players": { - "description": "Label for all players list" - }, - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "@cancel": { - "description": "Cancel button text" - }, - "@choose_game": { - "description": "Label for choosing a game" - }, - "@choose_group": { - "description": "Label for choosing a group" - }, - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@create_group": { - "description": "Button text to create a group" - }, - "@create_match": { - "description": "Button text to create a match" - }, - "@create_new_group": { - "description": "Button text to create a new group" - }, - "@create_new_match": { - "description": "Button text to create a new match" - }, - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@delete": { - "description": "Delete button text" - }, - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "@export_canceled": { - "description": "Message when export is canceled" - }, - "@export_data": { - "description": "Export data menu item" - }, - "@format_exception": { - "description": "Error message for format exceptions" - }, - "@game": { - "description": "Game label" - }, - "@game_name": { - "description": "Placeholder for game name search" - }, - "@game_tracker": { - "description": "App Name" - }, - "@group": { - "description": "Group label" - }, - "@group_name": { - "description": "Placeholder for group name input" - }, - "@groups": { - "description": "Label for groups" - }, - "@home": { - "description": "Home tab label" - }, - "@import_canceled": { - "description": "Message when import is canceled" - }, - "@import_data": { - "description": "Import data menu item" - }, - "@info": { - "description": "Info label" - }, - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "@least_points": { - "description": "Title for least points ruleset" - }, - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "@match_name": { - "description": "Placeholder for match name input" - }, - "@matches": { - "description": "Label for matches" - }, - "@menu": { - "description": "Menu label" - }, - "@most_points": { - "description": "Title for most points ruleset" - }, - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "@none": { - "description": "None option label" - }, - "@none_group": { - "description": "None group option label" - }, - "@not_available": { - "description": "Abbreviation for not available" - }, - "@player_name": { - "description": "Placeholder for player name input" - }, - "@players": { - "description": "Players label" - }, - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@quick_create": { - "description": "Title for quick create section" - }, - "@recent_matches": { - "description": "Title for recent matches section" - }, - "@ruleset": { - "description": "Ruleset label" - }, - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "@select_winner": { - "description": "Label to select the winner" - }, - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "@settings": { - "description": "Settings label" - }, - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "@statistics": { - "description": "Statistics tab label" - }, - "@stats": { - "description": "Stats tab label (short)" - }, - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "@undo": { - "description": "Undo button text" - }, - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "@winner": { - "description": "Winner label" - }, - "@winrate": { - "description": "Label for winrate statistic" - }, - "@wins": { - "description": "Label for wins statistic" - }, - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "all_players": "All players:", - "all_players_selected": "All players selected", - "amount_of_matches": "Amount of Matches", - "cancel": "Cancel", - "choose_game": "Choose Game", - "choose_group": "Choose Group", - "choose_ruleset": "Choose Ruleset", - "could_not_add_player": "Could not add player {playerName}", - "create_group": "Create Group", - "create_match": "Create match", - "create_new_group": "Create new group", - "create_new_match": "Create new match", - "data_successfully_deleted": "Data successfully deleted", - "data_successfully_exported": "Data successfully exported", - "data_successfully_imported": "Data successfully imported", - "days_ago": "{count} days ago", - "delete": "Delete", - "delete_all_data": "Delete all data?", - "error_creating_group": "Error while creating group, please try again", - "error_reading_file": "Error reading file", - "export_canceled": "Export canceled", - "export_data": "Export data", - "format_exception": "Format Exception (see console)", - "game": "Game", - "game_name": "Game Name", - "game_tracker": "Game Tracker", - "group": "Group", - "group_name": "Group name", - "groups": "Groups", - "home": "Home", - "import_canceled": "Import canceled", - "import_data": "Import data", - "info": "Info", - "invalid_schema": "Invalid Schema", - "least_points": "Least Points", - "match_in_progress": "Match in progress...", - "match_name": "Match name", - "matches": "Matches", - "menu": "Menu", - "most_points": "Most Points", - "no_data_available": "No data available", - "no_groups_created_yet": "No groups created yet", - "no_matches_created_yet": "No matches created yet", - "no_players_created_yet": "No players created yet", - "no_players_found_with_that_name": "No players found with that name", - "no_players_selected": "No players selected", - "no_recent_matches_available": "No recent matches available", - "no_second_match_available": "No second match available", - "no_statistics_available": "No statistics available", - "none": "None", - "none_group": "None", - "not_available": "Not available", - "player_name": "Player name", - "players": "Players", - "players_count": "{count} Players", - "quick_create": "Quick Create", - "recent_matches": "Recent Matches", - "ruleset": "Ruleset", - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "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:", - "selected_players": "Selected players: {count}", - "settings": "Settings", - "single_loser": "Single Loser", - "single_winner": "Single Winner", - "statistics": "Statistics", - "stats": "Stats", - "successfully_added_player": "Successfully added player {playerName}", - "there_is_no_group_matching_your_search": "There is no group matching your search", - "this_cannot_be_undone": "This can't be undone", - "today_at": "Today at {time}", - "undo": "Undo", - "unknown_exception": "Unknown Exception (see console)", - "winner": "Winner", - "winrate": "Winrate", - "wins": "Wins", - "yesterday_at": "Yesterday at {time}" + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@app_name": { + "description": "The name of the App" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails" + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today" + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday" + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "app_name": "Game Tracker", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "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:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 1743997..79ae804 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -116,6 +116,12 @@ abstract class AppLocalizations { /// **'Amount of Matches'** String get amount_of_matches; + /// The name of the App + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get app_name; + /// Cancel button text /// /// In en, this message translates to: @@ -143,8 +149,8 @@ abstract class AppLocalizations { /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); + /// **'Could not add player'** + String could_not_add_player(Object playerName); /// Button text to create a group /// @@ -248,12 +254,6 @@ abstract class AppLocalizations { /// **'Game Name'** String get game_name; - /// App Name - /// - /// In en, this message translates to: - /// **'Game Tracker'** - String get game_tracker; - /// Group label /// /// In en, this message translates to: @@ -545,8 +545,8 @@ abstract class AppLocalizations { /// Date format for today /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'Today at'** + String get today_at; /// Undo button text /// @@ -581,8 +581,8 @@ abstract class AppLocalizations { /// Date format for yesterday /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Yesterday at'** + String get yesterday_at; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 88374a1..17c459b 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -17,6 +17,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get amount_of_matches => 'Anzahl der Spiele'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Abbrechen'; @@ -30,7 +33,7 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String could_not_add_player(String playerName) { + String could_not_add_player(Object playerName) { return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @@ -88,9 +91,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get game_name => 'Spielvorlagenname'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Gruppe'; @@ -254,9 +254,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Dies kann nicht rückgängig gemacht werden'; @override - String today_at(String time) { - return 'Heute um $time'; - } + String get today_at => 'Heute um'; @override String get undo => 'Rückgängig'; @@ -274,7 +272,5 @@ class AppLocalizationsDe extends AppLocalizations { String get wins => 'Siege'; @override - String yesterday_at(String time) { - return 'Gestern um $time'; - } + String get yesterday_at => 'Gestern um'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 0cd8842..1bf24ca 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -17,6 +17,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get amount_of_matches => 'Amount of Matches'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Cancel'; @@ -30,8 +33,8 @@ class AppLocalizationsEn extends AppLocalizations { String get choose_ruleset => 'Choose Ruleset'; @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; + String could_not_add_player(Object playerName) { + return 'Could not add player'; } @override @@ -88,9 +91,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get game_name => 'Game Name'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Group'; @@ -253,9 +253,7 @@ class AppLocalizationsEn extends AppLocalizations { String get this_cannot_be_undone => 'This can\'t be undone'; @override - String today_at(String time) { - return 'Today at $time'; - } + String get today_at => 'Today at'; @override String get undo => 'Undo'; @@ -273,7 +271,5 @@ class AppLocalizationsEn extends AppLocalizations { String get wins => 'Wins'; @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } + String get yesterday_at => 'Yesterday at'; } diff --git a/lib/main.dart b/lib/main.dart index 8219fcb..656db90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, + onGenerateTitle: (context) => AppLocalizations.of(context).app_name, themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index bc349d3..55d81c3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -161,13 +161,9 @@ class _MatchTileState extends State { final loc = AppLocalizations.of(context); if (difference.inDays == 0) { - return AppLocalizations.of( - context, - ).today_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.today_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays == 1) { - return AppLocalizations.of( - context, - ).yesterday_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.yesterday_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays < 7) { return loc.days_ago(difference.inDays); } else { From 2a1573ee2da3a3e5f4f0103462bcbd83bde77233 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:15:01 +0100 Subject: [PATCH 18/19] Remove Whitespace --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 656db90..1dee10b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,13 +40,11 @@ class GameTracker extends StatelessWidget { primaryColor: CustomTheme.primaryColor, scaffoldBackgroundColor: CustomTheme.backgroundColor, appBarTheme: CustomTheme.appBarTheme, - colorScheme: ColorScheme.fromSeed( seedColor: CustomTheme.primaryColor, brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), ), - home: const CustomNavigationBar(), ); } From 88766652b964320cf89c9f8158e2b68425c204d5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:21:09 +0100 Subject: [PATCH 19/19] Small loc corrections --- lib/l10n/arb/app_de.arb | 6 +++--- lib/l10n/arb/app_en.arb | 12 +++--------- lib/l10n/generated/app_localizations.dart | 6 +++--- lib/l10n/generated/app_localizations_de.dart | 13 +++---------- lib/l10n/generated/app_localizations_en.dart | 11 ++--------- .../match_view/create_match/create_match_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 4 +--- 7 files changed, 16 insertions(+), 38 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4ed0997..4d86460 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "all_players": "Alle Spieler:innen:", + "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", @@ -44,7 +44,7 @@ "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "no_players_selected": "Keine Spieler:in ausgewählt", + "no_players_selected": "Keine Spieler:innen ausgewählt", "no_recent_matches_available": "Keine letzten Spiele verfügbar", "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:innen: {count}", + "selected_players": "Ausgewählte Spieler:innen", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index dd1e593..17c3b06 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -206,13 +206,7 @@ "description": "Label to select the winner" }, "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } + "description": "Shows the number of selected players" }, "@settings": { "description": "Settings label" @@ -265,7 +259,7 @@ "@yesterday_at": { "description": "Date format for yesterday" }, - "all_players": "All players:", + "all_players": "All players", "all_players_selected": "All players selected", "amount_of_matches": "Amount of Matches", "app_name": "Game Tracker", @@ -330,7 +324,7 @@ "search_for_groups": "Search for groups", "search_for_players": "Search for players", "select_winner": "Select Winner:", - "selected_players": "Selected players: {count}", + "selected_players": "Selected players", "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 79ae804..5080ff3 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -101,7 +101,7 @@ abstract class AppLocalizations { /// Label for all players list /// /// In en, this message translates to: - /// **'All players:'** + /// **'All players'** String get all_players; /// Message when all players are added to selection @@ -491,8 +491,8 @@ abstract class AppLocalizations { /// Shows the number of selected players /// /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); + /// **'Selected players'** + String get selected_players; /// Settings label /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 17c459b..c720941 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,7 +9,7 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get all_players => 'Alle Spieler:innen:'; + String get all_players => 'Alle Spieler:innen'; @override String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @@ -150,7 +150,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; + String get no_players_selected => 'Keine Spieler:innen ausgewählt'; @override String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @@ -216,14 +216,7 @@ class AppLocalizationsDe extends AppLocalizations { String get select_winner => 'Gewinner:in wählen:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:innen: $countString'; - } + String get selected_players => 'Ausgewählte Spieler:innen'; @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 1bf24ca..cd71035 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get all_players => 'All players:'; + String get all_players => 'All players'; @override String get all_players_selected => 'All players selected'; @@ -216,14 +216,7 @@ class AppLocalizationsEn extends AppLocalizations { String get select_winner => 'Select Winner:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Selected players: $countString'; - } + String get selected_players => 'Selected players'; @override String get settings => 'Settings'; 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 0cc25d0..dc6690b 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 @@ -155,7 +155,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = AppLocalizations.of(context).match_name; + hintText = loc.match_name; selectedRuleset = null; } }); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a582427..9280ae0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -119,9 +119,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of( - context, - ).selected_players(selectedPlayers.length), + loc.selected_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10),