Lokalisierung implementieren #112

Merged
sneeex merged 41 commits from feature/100-lokalisierung-hinzufügen into development 2026-01-07 11:30:11 +00:00
17 changed files with 247 additions and 157 deletions
Showing only changes of commit 534d19efc3 - Show all commits

View File

@@ -1,3 +1,6 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
/// Button types used for styling the [CustomWidthButton] /// Button types used for styling the [CustomWidthButton]
enum ButtonType { primary, secondary, tertiary } enum ButtonType { primary, secondary, tertiary }
@@ -30,16 +33,16 @@ enum ExportResult { success, canceled, unknownException }
/// - [Ruleset.leastPoints]: The player with the fewest points wins. /// - [Ruleset.leastPoints]: The player with the fewest points wins.
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints }
/// Translates a [Ruleset] enum value to its corresponding string representation. /// Translates a [Ruleset] enum value to its corresponding localized string.
String translateRulesetToString(Ruleset ruleset) { String translateRulesetToString(Ruleset ruleset, BuildContext context) {
switch (ruleset) { switch (ruleset) {
case Ruleset.singleWinner: case Ruleset.singleWinner:
return 'Single Winner'; return AppLocalizations.of(context)!.single_winner;
case Ruleset.singleLoser: case Ruleset.singleLoser:
return 'Single Loser'; return AppLocalizations.of(context)!.single_loser;
case Ruleset.mostPoints: case Ruleset.mostPoints:
return 'Most Points'; return AppLocalizations.of(context)!.most_points;
case Ruleset.leastPoints: case Ruleset.leastPoints:
return 'Least Points'; return AppLocalizations.of(context)!.least_points;
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -20,6 +21,8 @@ class GameTracker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
sneeex marked this conversation as resolved Outdated

Debug-Print entfernen

Debug-Print entfernen
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Game Tracker', title: 'Game Tracker',
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart';
@@ -89,28 +90,28 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
index: 0, index: 0,
isSelected: currentIndex == 0, isSelected: currentIndex == 0,
icon: Icons.home_rounded, icon: Icons.home_rounded,
label: 'Home', label: AppLocalizations.of(context)!.home,
onTabTapped: onTabTapped, onTabTapped: onTabTapped,
), ),
NavbarItem( NavbarItem(
index: 1, index: 1,
isSelected: currentIndex == 1, isSelected: currentIndex == 1,
icon: Icons.gamepad_rounded, icon: Icons.gamepad_rounded,
label: 'Matches', label: AppLocalizations.of(context)!.matches,
onTabTapped: onTabTapped, onTabTapped: onTabTapped,
), ),
NavbarItem( NavbarItem(
index: 2, index: 2,
isSelected: currentIndex == 2, isSelected: currentIndex == 2,
icon: Icons.group_rounded, icon: Icons.group_rounded,
label: 'Groups', label: AppLocalizations.of(context)!.groups,
onTabTapped: onTabTapped, onTabTapped: onTabTapped,
), ),
NavbarItem( NavbarItem(
index: 3, index: 3,
isSelected: currentIndex == 3, isSelected: currentIndex == 3,
icon: Icons.bar_chart_rounded, icon: Icons.bar_chart_rounded,
label: 'Stats', label: AppLocalizations.of(context)!.statistics,
onTabTapped: onTabTapped, onTabTapped: onTabTapped,
), ),
], ],
@@ -131,13 +132,13 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
String _currentTabTitle() { String _currentTabTitle() {
switch (currentIndex) { switch (currentIndex) {
case 0: case 0:
return 'Home'; return AppLocalizations.of(context)!.home;
case 1: case 1:
return 'Matches'; return AppLocalizations.of(context)!.matches;
case 2: case 2:
return 'Groups'; return AppLocalizations.of(context)!.groups;
case 3: case 3:
return 'Statistics'; return AppLocalizations.of(context)!.statistics;
default: default:
return ''; return '';
} }

View File

@@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart';
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart';
@@ -43,8 +44,8 @@ class _CreateGroupViewState extends State<CreateGroupView> {
appBar: AppBar( appBar: AppBar(
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
title: const Text( title: Text(
'Create new group', AppLocalizations.of(context)!.create_new_group,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
centerTitle: true, centerTitle: true,
@@ -57,7 +58,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: TextInputField( child: TextInputField(
controller: _groupNameController, controller: _groupNameController,
hintText: 'Group name', hintText: AppLocalizations.of(context)!.group_name,
onChanged: (value) { onChanged: (value) {
setState(() {}); setState(() {});
}, },
@@ -73,7 +74,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
), ),
), ),
CustomWidthButton( CustomWidthButton(
text: 'Create group', text: AppLocalizations.of(context)!.create_group,
sizeRelativeToWidth: 0.95, sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary, buttonType: ButtonType.primary,
onPressed: onPressed:
@@ -94,10 +95,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
backgroundColor: CustomTheme.boxColor, backgroundColor: CustomTheme.boxColor,
content: const Center( content: Center(
child: Text( child: Text(
'Error while creating group, please try again', AppLocalizations.of(
style: TextStyle(color: Colors.white), context,
)!.error_while_creating_group_please_try_again,
sneeex marked this conversation as resolved Outdated

Abkürzen für etwas generelles, z.B. creating_group_error oder creating_group_error_message

Abkürzen für etwas generelles, z.B. `creating_group_error` oder `creating_group_error_message`
style: const TextStyle(color: Colors.white),
), ),
), ),
), ),

View File

@@ -4,6 +4,7 @@ import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
@@ -49,11 +50,11 @@ class _GroupsViewState extends State<GroupsView> {
enabled: isLoading, enabled: isLoading,
child: Visibility( child: Visibility(
visible: groups.isNotEmpty, visible: groups.isNotEmpty,
replacement: const Center( replacement: Center(
child: TopCenteredMessage( child: TopCenteredMessage(
icon: Icons.info, icon: Icons.info,
title: 'Info', title: AppLocalizations.of(context)!.info,
message: 'No groups created yet', message: AppLocalizations.of(context)!.no_groups_created_yet,
), ),
), ),
child: ListView.builder( child: ListView.builder(
@@ -73,7 +74,7 @@ class _GroupsViewState extends State<GroupsView> {
Positioned( Positioned(
bottom: MediaQuery.paddingOf(context).bottom, bottom: MediaQuery.paddingOf(context).bottom,
child: CustomWidthButton( child: CustomWidthButton(
text: 'Create Group', text: AppLocalizations.of(context)!.create_group,
sizeRelativeToWidth: 0.90, sizeRelativeToWidth: 0.90,
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(

View File

@@ -4,6 +4,7 @@ import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart';
@@ -86,7 +87,7 @@ class _HomeViewState extends State<HomeView> {
QuickInfoTile( QuickInfoTile(
width: constraints.maxWidth * 0.45, width: constraints.maxWidth * 0.45,
height: constraints.maxHeight * 0.15, height: constraints.maxHeight * 0.15,
title: 'Matches', title: AppLocalizations.of(context)!.matches,
icon: Icons.groups_rounded, icon: Icons.groups_rounded,
value: matchCount, value: matchCount,
), ),
@@ -94,7 +95,7 @@ class _HomeViewState extends State<HomeView> {
QuickInfoTile( QuickInfoTile(
width: constraints.maxWidth * 0.45, width: constraints.maxWidth * 0.45,
height: constraints.maxHeight * 0.15, height: constraints.maxHeight * 0.15,
title: 'Groups', title: AppLocalizations.of(context)!.groups,
icon: Icons.groups_rounded, icon: Icons.groups_rounded,
value: groupCount, value: groupCount,
), ),
@@ -104,15 +105,19 @@ class _HomeViewState extends State<HomeView> {
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: InfoTile( child: InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: 'Recent Matches', title: AppLocalizations.of(context)!.recent_matches,
icon: Icons.timer, icon: Icons.timer,
content: Padding( content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0), padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Visibility( child: Visibility(
visible: !isLoading && loadedRecentMatches.isNotEmpty, visible: !isLoading && loadedRecentMatches.isNotEmpty,
replacement: const Center( replacement: Center(
heightFactor: 12, heightFactor: 12,
child: Text('No recent games available'), child: Text(
AppLocalizations.of(
context,
)!.no_recent_matches_available,
flixcoo marked this conversation as resolved Outdated

Idee: Generell alle Info-Messages auch als solche bennenen z.B. info_no_recent_matches

Idee: Generell alle Info-Messages auch als solche bennenen z.B. `info_no_recent_matches`

finde ich eigentlich unnötig

finde ich eigentlich unnötig

weil eigentlich kann man die messages ja auch anders benutzen und es ist ja eigentlich egal obs ne info ist

weil eigentlich kann man die messages ja auch anders benutzen und es ist ja eigentlich egal obs ne info ist
),
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@@ -120,11 +125,15 @@ class _HomeViewState extends State<HomeView> {
children: [ children: [
MatchTile( MatchTile(
matchTitle: recentMatches[0].name, matchTitle: recentMatches[0].name,
game: 'Winner', game: AppLocalizations.of(context)!.winner_label,
ruleset: 'Ruleset', ruleset: AppLocalizations.of(
context,
)!.ruleset_label,
sneeex marked this conversation as resolved Outdated

Wenn winner_labelund ruleset_label nirgends anders verwendet werden, einfach die normalen Strings belassen, weil diese ja in Zukunft durch das Ruleset und das Game ersetzt werden

Wenn `winner_label`und `ruleset_label` nirgends anders verwendet werden, einfach die normalen Strings belassen, weil diese ja in Zukunft durch das Ruleset und das Game ersetzt werden
players: _getPlayerText(recentMatches[0]), players: _getPlayerText(recentMatches[0]),
winner: recentMatches[0].winner == null winner: recentMatches[0].winner == null
? 'Match in progress...' ? AppLocalizations.of(
context,
)!.match_in_progress
: recentMatches[0].winner!.name, : recentMatches[0].winner!.name,
), ),
const Padding( const Padding(
@@ -134,18 +143,28 @@ class _HomeViewState extends State<HomeView> {
if (loadedRecentMatches.length > 1) ...[ if (loadedRecentMatches.length > 1) ...[
MatchTile( MatchTile(
matchTitle: recentMatches[1].name, matchTitle: recentMatches[1].name,
game: 'Winner', game: AppLocalizations.of(
ruleset: 'Ruleset', context,
)!.winner_label,
ruleset: AppLocalizations.of(
context,
)!.ruleset_label,
players: _getPlayerText(recentMatches[1]), players: _getPlayerText(recentMatches[1]),
winner: recentMatches[1].winner == null winner: recentMatches[1].winner == null
? 'Game in progress...' ? AppLocalizations.of(
context,
)!.match_in_progress
: recentMatches[1].winner!.name, : recentMatches[1].winner!.name,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
] else ...[ ] else ...[
const Center( Center(
heightFactor: 5.35, heightFactor: 5.35,
child: Text('No second game available'), child: Text(
AppLocalizations.of(
context,
)!.no_second_match_available,
),
), ),
], ],
], ],
@@ -156,7 +175,7 @@ class _HomeViewState extends State<HomeView> {
), ),
InfoTile( InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: 'Quick Create', title: AppLocalizations.of(context)!.quick_create,
icon: Icons.add_box_rounded, icon: Icons.add_box_rounded,
content: Column( content: Column(
children: [ children: [
@@ -213,7 +232,7 @@ class _HomeViewState extends State<HomeView> {
String _getPlayerText(Match game) { String _getPlayerText(Match game) {
if (game.group == null) { if (game.group == null) {
final playerCount = game.players?.length ?? 0; final playerCount = game.players?.length ?? 0;
return '$playerCount Players'; return AppLocalizations.of(context)!.players_count(playerCount);
} }
if (game.players == null || game.players!.isEmpty) { if (game.players == null || game.players!.isEmpty) {
return game.group!.name; return game.group!.name;

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
@@ -41,8 +42,8 @@ class _ChooseGameViewState extends State<ChooseGameView> {
Navigator.of(context).pop(selectedGameIndex); Navigator.of(context).pop(selectedGameIndex);
}, },
), ),
title: const Text( title: Text(
'Choose Game', AppLocalizations.of(context)!.choose_game,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
centerTitle: true, centerTitle: true,
@@ -53,7 +54,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: CustomSearchBar( child: CustomSearchBar(
controller: searchBarController, controller: searchBarController,
hintText: 'Game Name', hintText: AppLocalizations.of(context)!.game_name,
), ),
), ),
const SizedBox(height: 5), const SizedBox(height: 5),
@@ -64,8 +65,10 @@ class _ChooseGameViewState extends State<ChooseGameView> {
return TitleDescriptionListTile( return TitleDescriptionListTile(
title: widget.games[index].$1, title: widget.games[index].$1,
description: widget.games[index].$2, description: widget.games[index].$2,
badgeText: translateRulesetToString(widget.games[index].$3), badgeText: translateRulesetToString(
isHighlighted: selectedGameIndex == index, widget.games[index].$3,
context,
),
onPressed: () async { onPressed: () async {
setState(() { setState(() {
if (selectedGameIndex == index) { if (selectedGameIndex == index) {

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
@@ -22,7 +23,6 @@ class ChooseGroupView extends StatefulWidget {
class _ChooseGroupViewState extends State<ChooseGroupView> { class _ChooseGroupViewState extends State<ChooseGroupView> {
late String selectedGroupId; late String selectedGroupId;
final TextEditingController controller = TextEditingController(); final TextEditingController controller = TextEditingController();
final String hintText = 'Group Name';
late final List<Group> filteredGroups; late final List<Group> filteredGroups;
@override @override
@@ -51,8 +51,8 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
); );
}, },
), ),
title: const Text( title: Text(
'Choose Group', AppLocalizations.of(context)!.choose_group,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
centerTitle: true, centerTitle: true,
@@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: CustomSearchBar( child: CustomSearchBar(
controller: controller, controller: controller,
hintText: hintText, hintText: AppLocalizations.of(context)!.group_name,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
filterGroups(value); filterGroups(value);
@@ -76,15 +76,17 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
visible: filteredGroups.isNotEmpty, visible: filteredGroups.isNotEmpty,
replacement: Visibility( replacement: Visibility(
visible: widget.groups.isNotEmpty, visible: widget.groups.isNotEmpty,
replacement: const TopCenteredMessage( replacement: TopCenteredMessage(
icon: Icons.info, icon: Icons.info,
title: 'Info', title: AppLocalizations.of(context)!.info,
message: 'You have no groups created yet', message: AppLocalizations.of(context)!.no_groups_created_yet,
), ),
child: const TopCenteredMessage( child: TopCenteredMessage(
icon: Icons.info, icon: Icons.info,
title: 'Info', title: AppLocalizations.of(context)!.info,
message: 'There is no group matching your search', message: AppLocalizations.of(
context,
)!.there_is_no_group_matching_your_search,
), ),
), ),
child: ListView.builder( child: ListView.builder(

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
class ChooseRulesetView extends StatefulWidget { class ChooseRulesetView extends StatefulWidget {
@@ -46,8 +47,8 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
); );
}, },
), ),
title: const Text( title: Text(
'Choose Ruleset', AppLocalizations.of(context)!.choose_ruleset,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
centerTitle: true, centerTitle: true,
@@ -66,7 +67,10 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
} }
}); });
}, },
title: translateRulesetToString(widget.rulesets[index].$1), title: translateRulesetToString(
widget.rulesets[index].$1,
context,
),
description: widget.rulesets[index].$2, description: widget.rulesets[index].$2,
isHighlighted: selectedRulesetIndex == index, isHighlighted: selectedRulesetIndex == index,
); );

View File

@@ -6,6 +6,7 @@ import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart';
@@ -64,34 +65,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// The currently selected players /// The currently selected players
List<Player>? selectedPlayers; List<Player>? selectedPlayers;
/// List of available rulesets with their descriptions
/// as tuples of (Ruleset, String)
/// TODO: Replace when rulesets are implemented
List<(Ruleset, String)> rulesets = [
(
Ruleset.singleWinner,
'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.',
),
(
Ruleset.singleLoser,
'Exactly one loser is determined; last place receives the penalty or consequence.',
),
(
Ruleset.mostPoints,
'Traditional ruleset: the player with the most points wins.',
),
(
Ruleset.leastPoints,
'Inverse scoring: the player with the fewest points wins.',
),
];
// TODO: Replace when games are implemented
List<(String, String, Ruleset)> games = [
('Example Game 1', 'This is a discription', Ruleset.leastPoints),
('Example Game 2', '', Ruleset.singleWinner),
];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -111,6 +84,33 @@ class _CreateMatchViewState extends State<CreateMatchView> {
filteredPlayerList = List.from(playerList); filteredPlayerList = List.from(playerList);
} }
List<(Ruleset, String)> _getRulesets(BuildContext context) {
return [
(
Ruleset.singleWinner,
AppLocalizations.of(context)!.ruleset_single_winner_desc,
),
(
Ruleset.singleLoser,
AppLocalizations.of(context)!.ruleset_single_loser_desc,
),
sneeex marked this conversation as resolved Outdated

Warum _desc?

Warum `_desc`?
(
Ruleset.mostPoints,
AppLocalizations.of(context)!.ruleset_most_points_desc,
),
(
Ruleset.leastPoints,
AppLocalizations.of(context)!.ruleset_least_points_desc,
),
];
}
// TODO: Replace when games are implemented
List<(String, String, Ruleset)> games = [
('Example Game 1', 'This is a description', Ruleset.leastPoints),
('Example Game 2', '', Ruleset.singleWinner),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -118,8 +118,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
appBar: AppBar( appBar: AppBar(
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
title: const Text( title: Text(
'Create new match', AppLocalizations.of(context)!.create_new_match,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
centerTitle: true, centerTitle: true,
@@ -132,13 +132,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
child: TextInputField( child: TextInputField(
controller: _matchNameController, controller: _matchNameController,
hintText: 'Match name', hintText: AppLocalizations.of(context)!.match_name,
), ),
), ),
ChooseTile( ChooseTile(
title: 'Game', title: AppLocalizations.of(context)!.game,
trailingText: selectedGameIndex == -1 trailingText: selectedGameIndex == -1
? 'None' ? AppLocalizations.of(context)!.none
: games[selectedGameIndex].$1, : games[selectedGameIndex].$1,
onPressed: () async { onPressed: () async {
selectedGameIndex = await Navigator.of(context).push( selectedGameIndex = await Navigator.of(context).push(
@@ -152,9 +152,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
setState(() { setState(() {
if (selectedGameIndex != -1) { if (selectedGameIndex != -1) {
selectedRuleset = games[selectedGameIndex].$3; selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = rulesets.indexWhere( selectedRulesetIndex = _getRulesets(
(r) => r.$1 == selectedRuleset, context,
); ).indexWhere((r) => r.$1 == selectedRuleset);
} else { } else {
selectedRuleset = null; selectedRuleset = null;
} }
@@ -162,30 +162,30 @@ class _CreateMatchViewState extends State<CreateMatchView> {
}, },
), ),
ChooseTile( ChooseTile(
title: 'Ruleset', title: AppLocalizations.of(context)!.ruleset,
trailingText: selectedRuleset == null trailingText: selectedRuleset == null
? 'None' ? AppLocalizations.of(context)!.none
: translateRulesetToString(selectedRuleset!), : translateRulesetToString(selectedRuleset!, context),
onPressed: () async { onPressed: () async {
selectedRuleset = await Navigator.of(context).push( selectedRuleset = await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChooseRulesetView( builder: (context) => ChooseRulesetView(
rulesets: rulesets, rulesets: _getRulesets(context),
initialRulesetIndex: selectedRulesetIndex, initialRulesetIndex: selectedRulesetIndex,
), ),
), ),
); );
selectedRulesetIndex = rulesets.indexWhere( selectedRulesetIndex = _getRulesets(
(r) => r.$1 == selectedRuleset, context,
); ).indexWhere((r) => r.$1 == selectedRuleset);
selectedGameIndex = -1; selectedGameIndex = -1;
setState(() {}); setState(() {});
}, },
), ),
ChooseTile( ChooseTile(
title: 'Group', title: AppLocalizations.of(context)!.group,
trailingText: selectedGroup == null trailingText: selectedGroup == null
? 'None' ? AppLocalizations.of(context)!.none
: selectedGroup!.name, : selectedGroup!.name,
onPressed: () async { onPressed: () async {
selectedGroup = await Navigator.of(context).push( selectedGroup = await Navigator.of(context).push(
@@ -222,7 +222,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
), ),
), ),
CustomWidthButton( CustomWidthButton(
text: 'Create match', text: AppLocalizations.of(context)!.create_match,
sizeRelativeToWidth: 0.95, sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary, buttonType: ButtonType.primary,
onPressed: _enableCreateGameButton() onPressed: _enableCreateGameButton()

View File

@@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.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/custom_radio_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -79,8 +80,8 @@ class _MatchResultViewState extends State<MatchResultView> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Select Winner:', AppLocalizations.of(context)!.select_winner,
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@@ -8,6 +8,7 @@ import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
@@ -58,11 +59,11 @@ class _MatchViewState extends State<MatchView> {
enabled: isLoading, enabled: isLoading,
child: Visibility( child: Visibility(
visible: matches.isNotEmpty, visible: matches.isNotEmpty,
replacement: const Center( replacement: Center(
child: TopCenteredMessage( child: TopCenteredMessage(
icon: Icons.report, icon: Icons.report,
title: 'Info', title: AppLocalizations.of(context)!.info,
message: 'No games created yet', message: AppLocalizations.of(context)!.no_matches_created_yet,
), ),
), ),
child: ListView.builder( child: ListView.builder(
@@ -96,7 +97,7 @@ class _MatchViewState extends State<MatchView> {
Positioned( Positioned(
bottom: MediaQuery.paddingOf(context).bottom, bottom: MediaQuery.paddingOf(context).bottom,
child: CustomWidthButton( child: CustomWidthButton(
text: 'Create Game', text: AppLocalizations.of(context)!.create_match,
sizeRelativeToWidth: 0.90, sizeRelativeToWidth: 0.90,
onPressed: () async { onPressed: () async {
Navigator.push( Navigator.push(

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:game_tracker/services/data_transfer_service.dart';
@@ -24,30 +25,33 @@ class _SettingsViewState extends State<SettingsView> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.fromLTRB(24, 0, 24, 10), padding: const EdgeInsets.fromLTRB(24, 0, 24, 10),
child: Text( child: Text(
textAlign: TextAlign.start, textAlign: TextAlign.start,
'Menu', AppLocalizations.of(context)!.menu,
style: TextStyle( style: const TextStyle(
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
const Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 10,
),
child: Text( child: Text(
textAlign: TextAlign.start, textAlign: TextAlign.start,
'Settings', AppLocalizations.of(context)!.settings,
style: TextStyle( style: const TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
SettingsListTile( SettingsListTile(
title: 'Export data', title: AppLocalizations.of(context)!.export_data,
icon: Icons.upload_outlined, icon: Icons.upload_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async { onPressed: () async {
@@ -62,7 +66,7 @@ class _SettingsViewState extends State<SettingsView> {
}, },
), ),
SettingsListTile( SettingsListTile(
title: 'Import data', title: AppLocalizations.of(context)!.import_data,
icon: Icons.download_outlined, icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async { onPressed: () async {
@@ -74,23 +78,27 @@ class _SettingsViewState extends State<SettingsView> {
}, },
), ),
SettingsListTile( SettingsListTile(
title: 'Delete all data', title: AppLocalizations.of(context)!.delete_all_data,
icon: Icons.download_outlined, icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () { onPressed: () {
showDialog<bool>( showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Delete all data?'), title: Text(
content: const Text('This can\'t be undone'), AppLocalizations.of(context)!.delete_all_data,
),
content: Text(
AppLocalizations.of(context)!.this_cannot_be_undone,
flixcoo marked this conversation as resolved Outdated

Hier auch sowas wie popup oder so mit einbauen, dass klar wird, wozu dieser text ist

Hier auch sowas wie `popup` oder so mit einbauen, dass klar wird, wozu dieser text ist

finde nicht, das kann man für verschiedene sachen nutzen und reicht so m. M. n.

finde nicht, das kann man für verschiedene sachen nutzen und reicht so m. M. n.
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
child: const Text('Abbrechen'), child: Text(AppLocalizations.of(context)!.cancel),
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: const Text('Löschen'), child: Text(AppLocalizations.of(context)!.delete),
), ),
], ],
), ),
@@ -99,7 +107,9 @@ class _SettingsViewState extends State<SettingsView> {
DataTransferService.deleteAllData(context); DataTransferService.deleteAllData(context);
showSnackbar( showSnackbar(
context: context, context: context,
message: 'Daten erfolgreich gelöscht', message: AppLocalizations.of(
context,
)!.data_successfully_deleted,
); );
} }
}); });
@@ -122,22 +132,34 @@ class _SettingsViewState extends State<SettingsView> {
}) { }) {
switch (result) { switch (result) {
case ImportResult.success: case ImportResult.success:
showSnackbar(context: context, message: 'Data successfully imported'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.data_successfully_imported,
flixcoo marked this conversation as resolved Outdated

hier sowas wie snackbar?

hier sowas wie `snackbar`?

auch nicht relevant m. M. n.

auch nicht relevant m. M. n.
);
case ImportResult.invalidSchema: case ImportResult.invalidSchema:
showSnackbar(context: context, message: 'Invalid Schema'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.invalid_schema,
);
case ImportResult.fileReadError: case ImportResult.fileReadError:
showSnackbar(context: context, message: 'Error reading file'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.error_reading_file,
);
case ImportResult.canceled: case ImportResult.canceled:
showSnackbar(context: context, message: 'Import canceled'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.import_canceled,
);
case ImportResult.formatException: case ImportResult.formatException:
showSnackbar( showSnackbar(
context: context, context: context,
message: 'Format Exception (see console)', message: AppLocalizations.of(context)!.format_exception,
); );
case ImportResult.unknownException: case ImportResult.unknownException:
showSnackbar( showSnackbar(
context: context, context: context,
message: 'Unknown Exception (see console)', message: AppLocalizations.of(context)!.unknown_exception,
); );
} }
} }
@@ -152,13 +174,19 @@ class _SettingsViewState extends State<SettingsView> {
}) { }) {
switch (result) { switch (result) {
case ExportResult.success: case ExportResult.success:
showSnackbar(context: context, message: 'Data successfully exported'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.data_successfully_exported,
);
case ExportResult.canceled: case ExportResult.canceled:
showSnackbar(context: context, message: 'Export canceled'); showSnackbar(
context: context,
message: AppLocalizations.of(context)!.export_canceled,
);
case ExportResult.unknownException: case ExportResult.unknownException:
showSnackbar( showSnackbar(
context: context, context: context,
message: 'Unknown Exception (see console)', message: AppLocalizations.of(context)!.unknown_exception,
); );
} }
} }
@@ -183,7 +211,10 @@ class _SettingsViewState extends State<SettingsView> {
backgroundColor: CustomTheme.onBoxColor, backgroundColor: CustomTheme.onBoxColor,
duration: duration, duration: duration,
action: action != null action: action != null
? SnackBarAction(label: 'Rückgängig', onPressed: action) ? SnackBarAction(
label: AppLocalizations.of(context)!.undo,
onPressed: action,
)
: null, : null,
), ),
); );

View File

@@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -60,7 +61,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.01), SizedBox(height: constraints.maxHeight * 0.01),
StatisticsTile( StatisticsTile(
icon: Icons.sports_score, icon: Icons.sports_score,
title: 'Wins', title: AppLocalizations.of(context)!.wins,
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
values: winCounts, values: winCounts,
itemCount: 3, itemCount: 3,
@@ -69,7 +70,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.02), SizedBox(height: constraints.maxHeight * 0.02),
StatisticsTile( StatisticsTile(
icon: Icons.percent, icon: Icons.percent,
title: 'Winrate', title: AppLocalizations.of(context)!.winrate,
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
values: winRates, values: winRates,
itemCount: 5, itemCount: 5,
@@ -78,7 +79,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.02), SizedBox(height: constraints.maxHeight * 0.02),
StatisticsTile( StatisticsTile(
icon: Icons.casino, icon: Icons.casino,
title: 'Amount of Matches', title: AppLocalizations.of(context)!.amount_of_matches,
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
values: matchCounts, values: matchCounts,
itemCount: 10, itemCount: 10,

View File

@@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart';
@@ -129,14 +130,20 @@ class _PlayerSelectionState extends State<PlayerSelection> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
'Selected players: (${selectedPlayers.length})', AppLocalizations.of(
context,
)!.selected_players(selectedPlayers.length),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
height: 50, height: 50,
child: selectedPlayers.isEmpty child: selectedPlayers.isEmpty
? const Center(child: Text('No players selected')) ? Center(
child: Text(
AppLocalizations.of(context)!.no_players_selected,
),
)
: SingleChildScrollView( : SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
@@ -171,8 +178,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text( Text(
'All players:', AppLocalizations.of(context)!.all_players,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -186,12 +193,14 @@ class _PlayerSelectionState extends State<PlayerSelection> {
visible: suggestedPlayers.isNotEmpty, visible: suggestedPlayers.isNotEmpty,
replacement: TopCenteredMessage( replacement: TopCenteredMessage(
icon: Icons.info, icon: Icons.info,
title: 'Info', title: AppLocalizations.of(context)!.info,
message: allPlayers.isEmpty message: allPlayers.isEmpty
? 'No players created yet' ? AppLocalizations.of(context)!.no_players_created_yet
: (selectedPlayers.length == allPlayers.length) : (selectedPlayers.length == allPlayers.length)
? 'No more players to add' ? AppLocalizations.of(context)!.all_players_selected
: 'No players found with that name', : AppLocalizations.of(
context,
)!.no_players_found_with_that_name,
), ),
child: ListView.builder( child: ListView.builder(
itemCount: suggestedPlayers.length, itemCount: suggestedPlayers.length,
@@ -243,7 +252,9 @@ class _PlayerSelectionState extends State<PlayerSelection> {
backgroundColor: CustomTheme.boxColor, backgroundColor: CustomTheme.boxColor,
content: Center( content: Center(
child: Text( child: Text(
'Successfully added player $playerName.', AppLocalizations.of(
context,
)!.successfully_added_player(playerName),
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
), ),
@@ -255,7 +266,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
backgroundColor: CustomTheme.boxColor, backgroundColor: CustomTheme.boxColor,
content: Center( content: Center(
child: Text( child: Text(
'Could not add player $playerName.', AppLocalizations.of(context)!.could_not_add_player(playerName),
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
), ),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -97,7 +98,7 @@ class _GameHistoryTileState extends State<GameHistoryTile> {
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'Winner: ${winner.name}', AppLocalizations.of(context)!.winner(winner.name),
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -113,8 +114,8 @@ class _GameHistoryTileState extends State<GameHistoryTile> {
], ],
if (allPlayers.isNotEmpty) ...[ if (allPlayers.isNotEmpty) ...[
const Text( Text(
'Players', AppLocalizations.of(context)!.players,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Colors.grey, color: Colors.grey,
@@ -141,11 +142,15 @@ class _GameHistoryTileState extends State<GameHistoryTile> {
final difference = now.difference(dateTime); final difference = now.difference(dateTime);
if (difference.inDays == 0) { if (difference.inDays == 0) {
return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; return AppLocalizations.of(
context,
)!.today_at(DateFormat('HH:mm').format(dateTime));
} else if (difference.inDays == 1) { } else if (difference.inDays == 1) {
return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; return AppLocalizations.of(
context,
)!.yesterday_at(DateFormat('HH:mm').format(dateTime));
} else if (difference.inDays < 7) { } else if (difference.inDays < 7) {
return '${difference.inDays} days ago'; return AppLocalizations.of(context)!.days_ago(difference.inDays);
} else { } else {
return DateFormat('MMM d, yyyy').format(dateTime); return DateFormat('MMM d, yyyy').format(dateTime);
} }

View File

@@ -1,6 +1,7 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
class StatisticsTile extends StatelessWidget { class StatisticsTile extends StatelessWidget {
@@ -33,9 +34,9 @@ class StatisticsTile extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Visibility( child: Visibility(
visible: values.isNotEmpty, visible: values.isNotEmpty,
replacement: const Center( replacement: Center(
heightFactor: 4, heightFactor: 4,
child: Text('No data available.'), child: Text(AppLocalizations.of(context)!.no_data_available),
), ),
child: Column( child: Column(
children: List.generate(min(values.length, itemCount), (index) { children: List.generate(min(values.length, itemCount), (index) {