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]
enum ButtonType { primary, secondary, tertiary }
@@ -30,16 +33,16 @@ enum ExportResult { success, canceled, unknownException }
/// - [Ruleset.leastPoints]: The player with the fewest points wins.
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints }
/// Translates a [Ruleset] enum value to its corresponding string representation.
String translateRulesetToString(Ruleset ruleset) {
/// Translates a [Ruleset] enum value to its corresponding localized string.
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
switch (ruleset) {
case Ruleset.singleWinner:
return 'Single Winner';
return AppLocalizations.of(context)!.single_winner;
case Ruleset.singleLoser:
return 'Single Loser';
return AppLocalizations.of(context)!.single_loser;
case Ruleset.mostPoints:
return 'Most Points';
return AppLocalizations.of(context)!.most_points;
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:game_tracker/core/custom_theme.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:provider/provider.dart';
@@ -20,6 +21,8 @@ class GameTracker extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
sneeex marked this conversation as resolved Outdated

Debug-Print entfernen

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

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.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/home_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,
isSelected: currentIndex == 0,
icon: Icons.home_rounded,
label: 'Home',
label: AppLocalizations.of(context)!.home,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 1,
isSelected: currentIndex == 1,
icon: Icons.gamepad_rounded,
label: 'Matches',
label: AppLocalizations.of(context)!.matches,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 2,
isSelected: currentIndex == 2,
icon: Icons.group_rounded,
label: 'Groups',
label: AppLocalizations.of(context)!.groups,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 3,
isSelected: currentIndex == 3,
icon: Icons.bar_chart_rounded,
label: 'Stats',
label: AppLocalizations.of(context)!.statistics,
onTabTapped: onTabTapped,
),
],
@@ -131,13 +132,13 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
String _currentTabTitle() {
switch (currentIndex) {
case 0:
return 'Home';
return AppLocalizations.of(context)!.home;
case 1:
return 'Matches';
return AppLocalizations.of(context)!.matches;
case 2:
return 'Groups';
return AppLocalizations.of(context)!.groups;
case 3:
return 'Statistics';
return AppLocalizations.of(context)!.statistics;
default:
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/dto/group.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/player_selection.dart';
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart';
@@ -43,8 +44,8 @@ class _CreateGroupViewState extends State<CreateGroupView> {
appBar: AppBar(
backgroundColor: CustomTheme.backgroundColor,
scrolledUnderElevation: 0,
title: const Text(
'Create new group',
title: Text(
AppLocalizations.of(context)!.create_new_group,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
@@ -57,7 +58,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: TextInputField(
controller: _groupNameController,
hintText: 'Group name',
hintText: AppLocalizations.of(context)!.group_name,
onChanged: (value) {
setState(() {});
},
@@ -73,7 +74,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
),
),
CustomWidthButton(
text: 'Create group',
text: AppLocalizations.of(context)!.create_group,
sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary,
onPressed:
@@ -94,10 +95,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: const Center(
content: Center(
child: Text(
'Error while creating group, please try again',
style: TextStyle(color: Colors.white),
AppLocalizations.of(
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/dto/group.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/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
@@ -49,11 +50,11 @@ class _GroupsViewState extends State<GroupsView> {
enabled: isLoading,
child: Visibility(
visible: groups.isNotEmpty,
replacement: const Center(
replacement: Center(
child: TopCenteredMessage(
icon: Icons.info,
title: 'Info',
message: 'No groups created yet',
title: AppLocalizations.of(context)!.info,
message: AppLocalizations.of(context)!.no_groups_created_yet,
),
),
child: ListView.builder(
@@ -73,7 +74,7 @@ class _GroupsViewState extends State<GroupsView> {
Positioned(
bottom: MediaQuery.paddingOf(context).bottom,
child: CustomWidthButton(
text: 'Create Group',
text: AppLocalizations.of(context)!.create_group,
sizeRelativeToWidth: 0.90,
onPressed: () async {
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/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/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart';
@@ -86,7 +87,7 @@ class _HomeViewState extends State<HomeView> {
QuickInfoTile(
width: constraints.maxWidth * 0.45,
height: constraints.maxHeight * 0.15,
title: 'Matches',
title: AppLocalizations.of(context)!.matches,
icon: Icons.groups_rounded,
value: matchCount,
),
@@ -94,7 +95,7 @@ class _HomeViewState extends State<HomeView> {
QuickInfoTile(
width: constraints.maxWidth * 0.45,
height: constraints.maxHeight * 0.15,
title: 'Groups',
title: AppLocalizations.of(context)!.groups,
icon: Icons.groups_rounded,
value: groupCount,
),
@@ -104,15 +105,19 @@ class _HomeViewState extends State<HomeView> {
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: InfoTile(
width: constraints.maxWidth * 0.95,
title: 'Recent Matches',
title: AppLocalizations.of(context)!.recent_matches,
icon: Icons.timer,
content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Visibility(
visible: !isLoading && loadedRecentMatches.isNotEmpty,
replacement: const Center(
replacement: Center(
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(
mainAxisAlignment: MainAxisAlignment.start,
@@ -120,11 +125,15 @@ class _HomeViewState extends State<HomeView> {
children: [
MatchTile(
matchTitle: recentMatches[0].name,
game: 'Winner',
ruleset: 'Ruleset',
game: AppLocalizations.of(context)!.winner_label,
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]),
winner: recentMatches[0].winner == null
? 'Match in progress...'
? AppLocalizations.of(
context,
)!.match_in_progress
: recentMatches[0].winner!.name,
),
const Padding(
@@ -134,18 +143,28 @@ class _HomeViewState extends State<HomeView> {
if (loadedRecentMatches.length > 1) ...[
MatchTile(
matchTitle: recentMatches[1].name,
game: 'Winner',
ruleset: 'Ruleset',
game: AppLocalizations.of(
context,
)!.winner_label,
ruleset: AppLocalizations.of(
context,
)!.ruleset_label,
players: _getPlayerText(recentMatches[1]),
winner: recentMatches[1].winner == null
? 'Game in progress...'
? AppLocalizations.of(
context,
)!.match_in_progress
: recentMatches[1].winner!.name,
),
const SizedBox(height: 8),
] else ...[
const Center(
Center(
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(
width: constraints.maxWidth * 0.95,
title: 'Quick Create',
title: AppLocalizations.of(context)!.quick_create,
icon: Icons.add_box_rounded,
content: Column(
children: [
@@ -213,7 +232,7 @@ class _HomeViewState extends State<HomeView> {
String _getPlayerText(Match game) {
if (game.group == null) {
final playerCount = game.players?.length ?? 0;
return '$playerCount Players';
return AppLocalizations.of(context)!.players_count(playerCount);
}
if (game.players == null || game.players!.isEmpty) {
return game.group!.name;

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.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';
class ChooseRulesetView extends StatefulWidget {
@@ -46,8 +47,8 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
);
},
),
title: const Text(
'Choose Ruleset',
title: Text(
AppLocalizations.of(context)!.choose_ruleset,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
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,
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/match.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_group_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
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
void initState() {
super.initState();
@@ -111,6 +84,33 @@ class _CreateMatchViewState extends State<CreateMatchView> {
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
Widget build(BuildContext context) {
return Scaffold(
@@ -118,8 +118,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
appBar: AppBar(
backgroundColor: CustomTheme.backgroundColor,
scrolledUnderElevation: 0,
title: const Text(
'Create new match',
title: Text(
AppLocalizations.of(context)!.create_new_match,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
@@ -132,13 +132,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
child: TextInputField(
controller: _matchNameController,
hintText: 'Match name',
hintText: AppLocalizations.of(context)!.match_name,
),
),
ChooseTile(
title: 'Game',
title: AppLocalizations.of(context)!.game,
trailingText: selectedGameIndex == -1
? 'None'
? AppLocalizations.of(context)!.none
: games[selectedGameIndex].$1,
onPressed: () async {
selectedGameIndex = await Navigator.of(context).push(
@@ -152,9 +152,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
setState(() {
if (selectedGameIndex != -1) {
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
selectedRulesetIndex = _getRulesets(
context,
).indexWhere((r) => r.$1 == selectedRuleset);
} else {
selectedRuleset = null;
}
@@ -162,30 +162,30 @@ class _CreateMatchViewState extends State<CreateMatchView> {
},
),
ChooseTile(
title: 'Ruleset',
title: AppLocalizations.of(context)!.ruleset,
trailingText: selectedRuleset == null
? 'None'
: translateRulesetToString(selectedRuleset!),
? AppLocalizations.of(context)!.none
: translateRulesetToString(selectedRuleset!, context),
onPressed: () async {
selectedRuleset = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChooseRulesetView(
rulesets: rulesets,
rulesets: _getRulesets(context),
initialRulesetIndex: selectedRulesetIndex,
),
),
);
selectedRulesetIndex = rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
selectedRulesetIndex = _getRulesets(
context,
).indexWhere((r) => r.$1 == selectedRuleset);
selectedGameIndex = -1;
setState(() {});
},
),
ChooseTile(
title: 'Group',
title: AppLocalizations.of(context)!.group,
trailingText: selectedGroup == null
? 'None'
? AppLocalizations.of(context)!.none
: selectedGroup!.name,
onPressed: () async {
selectedGroup = await Navigator.of(context).push(
@@ -222,7 +222,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
),
),
CustomWidthButton(
text: 'Create match',
text: AppLocalizations.of(context)!.create_match,
sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary,
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/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/custom_radio_list_tile.dart';
import 'package:provider/provider.dart';
@@ -79,8 +80,8 @@ class _MatchResultViewState extends State<MatchResultView> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Select Winner:',
Text(
AppLocalizations.of(context)!.select_winner,
style: TextStyle(
fontSize: 18,
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/match.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/match_result_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
@@ -58,11 +59,11 @@ class _MatchViewState extends State<MatchView> {
enabled: isLoading,
child: Visibility(
visible: matches.isNotEmpty,
replacement: const Center(
replacement: Center(
child: TopCenteredMessage(
icon: Icons.report,
title: 'Info',
message: 'No games created yet',
title: AppLocalizations.of(context)!.info,
message: AppLocalizations.of(context)!.no_matches_created_yet,
),
),
child: ListView.builder(
@@ -96,7 +97,7 @@ class _MatchViewState extends State<MatchView> {
Positioned(
bottom: MediaQuery.paddingOf(context).bottom,
child: CustomWidthButton(
text: 'Create Game',
text: AppLocalizations.of(context)!.create_match,
sizeRelativeToWidth: 0.90,
onPressed: () async {
Navigator.push(

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.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/services/data_transfer_service.dart';
@@ -24,30 +25,33 @@ class _SettingsViewState extends State<SettingsView> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.fromLTRB(24, 0, 24, 10),
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 10),
child: Text(
textAlign: TextAlign.start,
'Menu',
style: TextStyle(
AppLocalizations.of(context)!.menu,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 10,
),
child: Text(
textAlign: TextAlign.start,
'Settings',
style: TextStyle(
AppLocalizations.of(context)!.settings,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: 'Export data',
title: AppLocalizations.of(context)!.export_data,
icon: Icons.upload_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
@@ -62,7 +66,7 @@ class _SettingsViewState extends State<SettingsView> {
},
),
SettingsListTile(
title: 'Import data',
title: AppLocalizations.of(context)!.import_data,
icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
@@ -74,23 +78,27 @@ class _SettingsViewState extends State<SettingsView> {
},
),
SettingsListTile(
title: 'Delete all data',
title: AppLocalizations.of(context)!.delete_all_data,
icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete all data?'),
content: const Text('This can\'t be undone'),
title: Text(
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: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Abbrechen'),
child: Text(AppLocalizations.of(context)!.cancel),
),
TextButton(
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);
showSnackbar(
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) {
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:
showSnackbar(context: context, message: 'Invalid Schema');
showSnackbar(
context: context,
message: AppLocalizations.of(context)!.invalid_schema,
);
case ImportResult.fileReadError:
showSnackbar(context: context, message: 'Error reading file');
showSnackbar(
context: context,
message: AppLocalizations.of(context)!.error_reading_file,
);
case ImportResult.canceled:
showSnackbar(context: context, message: 'Import canceled');
showSnackbar(
context: context,
message: AppLocalizations.of(context)!.import_canceled,
);
case ImportResult.formatException:
showSnackbar(
context: context,
message: 'Format Exception (see console)',
message: AppLocalizations.of(context)!.format_exception,
);
case ImportResult.unknownException:
showSnackbar(
context: context,
message: 'Unknown Exception (see console)',
message: AppLocalizations.of(context)!.unknown_exception,
);
}
}
@@ -152,13 +174,19 @@ class _SettingsViewState extends State<SettingsView> {
}) {
switch (result) {
case ExportResult.success:
showSnackbar(context: context, message: 'Data successfully exported');
showSnackbar(
context: context,
message: AppLocalizations.of(context)!.data_successfully_exported,
);
case ExportResult.canceled:
showSnackbar(context: context, message: 'Export canceled');
showSnackbar(
context: context,
message: AppLocalizations.of(context)!.export_canceled,
);
case ExportResult.unknownException:
showSnackbar(
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,
duration: duration,
action: action != null
? SnackBarAction(label: 'Rückgängig', onPressed: action)
? SnackBarAction(
label: AppLocalizations.of(context)!.undo,
onPressed: action,
)
: 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/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/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart';
import 'package:provider/provider.dart';
@@ -60,7 +61,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.01),
StatisticsTile(
icon: Icons.sports_score,
title: 'Wins',
title: AppLocalizations.of(context)!.wins,
width: constraints.maxWidth * 0.95,
values: winCounts,
itemCount: 3,
@@ -69,7 +70,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.02),
StatisticsTile(
icon: Icons.percent,
title: 'Winrate',
title: AppLocalizations.of(context)!.winrate,
width: constraints.maxWidth * 0.95,
values: winRates,
itemCount: 5,
@@ -78,7 +79,7 @@ class _StatisticsViewState extends State<StatisticsView> {
SizedBox(height: constraints.maxHeight * 0.02),
StatisticsTile(
icon: Icons.casino,
title: 'Amount of Matches',
title: AppLocalizations.of(context)!.amount_of_matches,
width: constraints.maxWidth * 0.95,
values: matchCounts,
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/data/db/database.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/text_input/custom_search_bar.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),
Text(
'Selected players: (${selectedPlayers.length})',
AppLocalizations.of(
context,
)!.selected_players(selectedPlayers.length),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
SizedBox(
height: 50,
child: selectedPlayers.isEmpty
? const Center(child: Text('No players selected'))
? Center(
child: Text(
AppLocalizations.of(context)!.no_players_selected,
),
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
@@ -171,8 +178,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
),
),
const SizedBox(height: 10),
const Text(
'All players:',
Text(
AppLocalizations.of(context)!.all_players,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
@@ -186,12 +193,14 @@ class _PlayerSelectionState extends State<PlayerSelection> {
visible: suggestedPlayers.isNotEmpty,
replacement: TopCenteredMessage(
icon: Icons.info,
title: 'Info',
title: AppLocalizations.of(context)!.info,
message: allPlayers.isEmpty
? 'No players created yet'
? AppLocalizations.of(context)!.no_players_created_yet
: (selectedPlayers.length == allPlayers.length)
? 'No more players to add'
: 'No players found with that name',
? AppLocalizations.of(context)!.all_players_selected
: AppLocalizations.of(
context,
)!.no_players_found_with_that_name,
),
child: ListView.builder(
itemCount: suggestedPlayers.length,
@@ -243,7 +252,9 @@ class _PlayerSelectionState extends State<PlayerSelection> {
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
'Successfully added player $playerName.',
AppLocalizations.of(
context,
)!.successfully_added_player(playerName),
style: const TextStyle(color: Colors.white),
),
),
@@ -255,7 +266,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
'Could not add player $playerName.',
AppLocalizations.of(context)!.could_not_add_player(playerName),
style: const TextStyle(color: Colors.white),
),
),

View File

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

View File

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