diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 2ef9ee9..65aa2b3 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -23,7 +23,9 @@ "delete": "Löschen", "delete_all_data": "Alle Daten löschen", "delete_group": "Gruppe löschen", + "delete_match": "Spiel löschen", "edit_group": "Gruppe bearbeiten", + "enter_results": "Ergebnisse eintragen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_reading_file": "Fehler beim Lesen der Datei", "export_canceled": "Export abgebrochen", @@ -46,6 +48,7 @@ "licenses": "Lizenzen", "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", + "match_profile": "Spielprofil", "matches": "Spiele", "members": "Mitglieder", "most_points": "Höchste Punkte", @@ -70,6 +73,8 @@ "privacy_policy": "Datenschutzerklärung", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", + "result": "Ergebnis", + "results": "Ergebnisse", "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.", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index fa4adc8..cc07a45 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -74,9 +74,15 @@ "@delete_group": { "description": "Button text to delete a group" }, + "@delete_match": { + "description": "Button text to delete a match" + }, "@edit_group": { "description": "Button text to edit a group" }, + "@enter_results": { + "description": "Button text to enter match results" + }, "@error_creating_group": { "description": "Error message when group creation fails" }, @@ -143,6 +149,9 @@ "@match_name": { "description": "Placeholder for match name input" }, + "@match_profile": { + "description": "Title for match profile view" + }, "@matches": { "description": "Label for matches" }, @@ -220,6 +229,9 @@ "@recent_matches": { "description": "Title for recent matches section" }, + "@results": { + "description": "Label for match results" + }, "@ruleset": { "description": "Ruleset label" }, @@ -321,7 +333,9 @@ "delete": "Delete", "delete_all_data": "Delete all data", "delete_group": "Delete Group", + "delete_match": "Delete Match", "edit_group": "Edit Group", + "enter_results": "Enter Results", "error_creating_group": "Error while creating group, please try again", "error_reading_file": "Error reading file", "export_canceled": "Export canceled", @@ -344,6 +358,7 @@ "licenses": "Licenses", "match_in_progress": "Match in progress...", "match_name": "Match name", + "match_profile": "Match Profile", "matches": "Matches", "members": "Members", "most_points": "Most Points", @@ -368,6 +383,7 @@ "privacy_policy": "Privacy Policy", "quick_create": "Quick Create", "recent_matches": "Recent Matches", + "results": "Results", "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.", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 57dbdd8..627d4b1 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -236,12 +236,24 @@ abstract class AppLocalizations { /// **'Delete Group'** String get delete_group; + /// Button text to delete a match + /// + /// In en, this message translates to: + /// **'Delete Match'** + String get delete_match; + /// Button text to edit a group /// /// In en, this message translates to: /// **'Edit Group'** String get edit_group; + /// Button text to enter match results + /// + /// In en, this message translates to: + /// **'Enter Results'** + String get enter_results; + /// Error message when group creation fails /// /// In en, this message translates to: @@ -374,6 +386,12 @@ abstract class AppLocalizations { /// **'Match name'** String get match_name; + /// Title for match profile view + /// + /// In en, this message translates to: + /// **'Match Profile'** + String get match_profile; + /// Label for matches /// /// In en, this message translates to: @@ -518,6 +536,12 @@ abstract class AppLocalizations { /// **'Recent Matches'** String get recent_matches; + /// Label for match results + /// + /// In en, this message translates to: + /// **'Results'** + String get results; + /// Ruleset label /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index f78f9f4..3078855 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -81,9 +81,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get delete_group => 'Gruppe löschen'; + @override + String get delete_match => 'Spiel löschen'; + @override String get edit_group => 'Gruppe bearbeiten'; + @override + String get enter_results => 'Ergebnisse eintragen'; + @override String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @@ -151,6 +157,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get match_name => 'Spieltitel'; + @override + String get match_profile => 'Spielprofil'; + @override String get matches => 'Spiele'; @@ -226,6 +235,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get recent_matches => 'Letzte Spiele'; + @override + String get results => 'Ergebnisse'; + @override String get ruleset => 'Regelwerk'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 32512c7..12d8a36 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -81,9 +81,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get delete_group => 'Delete Group'; + @override + String get delete_match => 'Delete Match'; + @override String get edit_group => 'Edit Group'; + @override + String get enter_results => 'Enter Results'; + @override String get error_creating_group => 'Error while creating group, please try again'; @@ -151,6 +157,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get match_name => 'Match name'; + @override + String get match_profile => 'Match Profile'; + @override String get matches => 'Matches'; @@ -226,6 +235,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get recent_matches => 'Recent Matches'; + @override + String get results => 'Results'; + @override String get ruleset => 'Ruleset'; diff --git a/lib/presentation/views/main_menu/match_view/match_profile_view.dart b/lib/presentation/views/main_menu/match_view/match_profile_view.dart new file mode 100644 index 0000000..7c18f11 --- /dev/null +++ b/lib/presentation/views/main_menu/match_view/match_profile_view.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; +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/views/main_menu/match_view/match_result_view.dart'; +import 'package:game_tracker/presentation/widgets/buttons/animated_dialog_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; +import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart'; +import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class MatchProfileView extends StatefulWidget { + /// A view that displays the profile of a match + /// - [match]: The match to display + /// - [callback]: Callback to refresh the match list + const MatchProfileView({ + super.key, + required this.match, + required this.callback, + }); + + /// The match to display + final Match match; + + /// Callback to refresh the match list + final VoidCallback callback; + + @override + State createState() => _MatchProfileViewState(); +} + +class _MatchProfileViewState extends State { + late final AppDatabase db; + + /// All players who participated in the match + late final List allPlayers; + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + allPlayers = _getAllPlayers(); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final extraPlayersCount = + (widget.match.players?.length ?? 0) + + (widget.match.group?.members.length ?? 0) - + allPlayers.length; + + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + title: Text(loc.match_profile), + actions: [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + showDialog( + context: context, + builder: (context) => CustomAlertDialog( + title: '${loc.delete_match}?', + content: loc.this_cannot_be_undone, + actions: [ + AnimatedDialogButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text( + loc.cancel, + style: const TextStyle(color: CustomTheme.textColor), + ), + ), + AnimatedDialogButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text( + loc.delete, + style: TextStyle(color: CustomTheme.secondaryColor), + ), + ), + ], + ), + ).then((confirmed) async { + if (confirmed! && context.mounted) { + await db.matchDao.deleteMatch(matchId: widget.match.id); + if (!context.mounted) return; + Navigator.pop(context); + widget.callback.call(); + } + }); + }, + ), + ], + ), + body: SafeArea( + child: Stack( + alignment: Alignment.center, + children: [ + ListView( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 20, + bottom: 100, + ), + children: [ + const Center( + child: ColoredIconContainer( + icon: Icons.sports_esports, + containerSize: 55, + iconSize: 38, + ), + ), + const SizedBox(height: 10), + Text( + widget.match.name, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Text( + '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(widget.match.createdAt)}', + style: const TextStyle( + fontSize: 12, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + if (widget.match.group != null) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.group), + const SizedBox(width: 8), + Text( + '${widget.match.group!.name} ${extraPlayersCount > 0 ? '+ $extraPlayersCount' : ''}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 20), + ], + InfoTile( + title: loc.players, + icon: Icons.people, + horizontalAlignment: CrossAxisAlignment.start, + content: Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 12, + runSpacing: 8, + children: allPlayers.map((player) { + return TextIconTile( + text: player.name, + iconEnabled: false, + ); + }).toList(), + ), + ), + const SizedBox(height: 15), + InfoTile( + title: loc.results, + icon: Icons.emoji_events, + content: Padding( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + loc.winner, + style: const TextStyle( + fontSize: 16, + color: CustomTheme.textColor, + ), + ), + Text( + widget.match.winner?.name ?? '-', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: widget.match.winner != null + ? CustomTheme.primaryColor + : CustomTheme.textColor, + ), + ), + ], + ), + ), + ), + ], + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: Row( + children: [ + MainMenuButton(icon: Icons.edit, onPressed: () {}), + const SizedBox(width: 15), + MainMenuButton( + text: loc.enter_results, + icon: Icons.note_add, + onPressed: () async { + await Navigator.push( + context, + adaptivePageRoute( + fullscreenDialog: true, + builder: (context) => MatchResultView( + match: widget.match, + onWinnerChanged: () { + widget.callback.call(); + setState(() {}); + }, + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + + /// Gets all players who participated in the match (from group and individual players) + List _getAllPlayers() { + final List players = []; + + // Add group members if group exists + if (widget.match.group != null) { + players.addAll(widget.match.group!.members); + } + + // Add individual players + if (widget.match.players != null) { + for (var player in widget.match.players!) { + // Avoid duplicates + if (!players.any((p) => p.id == player.id)) { + players.add(player); + } + } + } + + return 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 e85bf77..65ff29c 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -11,7 +11,7 @@ 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/views/main_menu/match_view/match_profile_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; @@ -89,10 +89,9 @@ class _MatchViewState extends State { Navigator.push( context, adaptivePageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView( + builder: (context) => MatchProfileView( match: matches[index], - onWinnerChanged: loadGames, + callback: loadGames, ), ), ); @@ -128,6 +127,7 @@ class _MatchViewState extends State { /// Loads the games from the database and sorts them by creation date. void loadGames() { + isLoading = true; Future.wait([ db.matchDao.getAllMatches(), Future.delayed(Constants.MINIMUM_SKELETON_DURATION), diff --git a/lib/presentation/widgets/buttons/main_menu_button.dart b/lib/presentation/widgets/buttons/main_menu_button.dart index 747c31e..417a296 100644 --- a/lib/presentation/widgets/buttons/main_menu_button.dart +++ b/lib/presentation/widgets/buttons/main_menu_button.dart @@ -7,16 +7,16 @@ class MainMenuButton extends StatefulWidget { /// - [onPressed]: The callback to be invoked when the button is pressed. const MainMenuButton({ super.key, - required this.text, - this.icon, + required this.icon, required this.onPressed, + this.text, }); /// The text of the button. - final String text; + final String? text; /// The icon of the button. - final IconData? icon; + final IconData icon; /// The callback to be invoked when the button is pressed. final void Function() onPressed; @@ -71,18 +71,18 @@ class _MainMenuButtonState extends State mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - if (widget.icon != null) ...[ - Icon(widget.icon, size: 26, color: Colors.black), + Icon(widget.icon, size: 26, color: Colors.black), + if (widget.text != null) ...[ const SizedBox(width: 7), - ], - Text( - widget.text, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black, + Text( + widget.text!, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black, + ), ), - ), + ], ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 33091e5..54ff72b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.9+236 +version: 0.0.9+242 environment: sdk: ^3.8.1