Implemented MatchProfileView

This commit is contained in:
2026-01-18 00:08:39 +01:00
parent 9ce2ca0ceb
commit 374c9295ef
9 changed files with 349 additions and 19 deletions

View File

@@ -23,7 +23,9 @@
"delete": "Löschen", "delete": "Löschen",
"delete_all_data": "Alle Daten löschen", "delete_all_data": "Alle Daten löschen",
"delete_group": "Gruppe löschen", "delete_group": "Gruppe löschen",
"delete_match": "Spiel löschen",
"edit_group": "Gruppe bearbeiten", "edit_group": "Gruppe bearbeiten",
"enter_results": "Ergebnisse eintragen",
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
"error_reading_file": "Fehler beim Lesen der Datei", "error_reading_file": "Fehler beim Lesen der Datei",
"export_canceled": "Export abgebrochen", "export_canceled": "Export abgebrochen",
@@ -46,6 +48,7 @@
"licenses": "Lizenzen", "licenses": "Lizenzen",
"match_in_progress": "Spiel läuft...", "match_in_progress": "Spiel läuft...",
"match_name": "Spieltitel", "match_name": "Spieltitel",
"match_profile": "Spielprofil",
"matches": "Spiele", "matches": "Spiele",
"members": "Mitglieder", "members": "Mitglieder",
"most_points": "Höchste Punkte", "most_points": "Höchste Punkte",
@@ -70,6 +73,8 @@
"privacy_policy": "Datenschutzerklärung", "privacy_policy": "Datenschutzerklärung",
"quick_create": "Schnellzugriff", "quick_create": "Schnellzugriff",
"recent_matches": "Letzte Spiele", "recent_matches": "Letzte Spiele",
"result": "Ergebnis",
"results": "Ergebnisse",
"ruleset": "Regelwerk", "ruleset": "Regelwerk",
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", "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.", "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",

View File

@@ -74,9 +74,15 @@
"@delete_group": { "@delete_group": {
"description": "Button text to delete a group" "description": "Button text to delete a group"
}, },
"@delete_match": {
"description": "Button text to delete a match"
},
"@edit_group": { "@edit_group": {
"description": "Button text to edit a group" "description": "Button text to edit a group"
}, },
"@enter_results": {
"description": "Button text to enter match results"
},
"@error_creating_group": { "@error_creating_group": {
"description": "Error message when group creation fails" "description": "Error message when group creation fails"
}, },
@@ -143,6 +149,9 @@
"@match_name": { "@match_name": {
"description": "Placeholder for match name input" "description": "Placeholder for match name input"
}, },
"@match_profile": {
"description": "Title for match profile view"
},
"@matches": { "@matches": {
"description": "Label for matches" "description": "Label for matches"
}, },
@@ -220,6 +229,9 @@
"@recent_matches": { "@recent_matches": {
"description": "Title for recent matches section" "description": "Title for recent matches section"
}, },
"@results": {
"description": "Label for match results"
},
"@ruleset": { "@ruleset": {
"description": "Ruleset label" "description": "Ruleset label"
}, },
@@ -321,7 +333,9 @@
"delete": "Delete", "delete": "Delete",
"delete_all_data": "Delete all data", "delete_all_data": "Delete all data",
"delete_group": "Delete Group", "delete_group": "Delete Group",
"delete_match": "Delete Match",
"edit_group": "Edit Group", "edit_group": "Edit Group",
"enter_results": "Enter Results",
"error_creating_group": "Error while creating group, please try again", "error_creating_group": "Error while creating group, please try again",
"error_reading_file": "Error reading file", "error_reading_file": "Error reading file",
"export_canceled": "Export canceled", "export_canceled": "Export canceled",
@@ -344,6 +358,7 @@
"licenses": "Licenses", "licenses": "Licenses",
"match_in_progress": "Match in progress...", "match_in_progress": "Match in progress...",
"match_name": "Match name", "match_name": "Match name",
"match_profile": "Match Profile",
"matches": "Matches", "matches": "Matches",
"members": "Members", "members": "Members",
"most_points": "Most Points", "most_points": "Most Points",
@@ -368,6 +383,7 @@
"privacy_policy": "Privacy Policy", "privacy_policy": "Privacy Policy",
"quick_create": "Quick Create", "quick_create": "Quick Create",
"recent_matches": "Recent Matches", "recent_matches": "Recent Matches",
"results": "Results",
"ruleset": "Ruleset", "ruleset": "Ruleset",
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.", "ruleset_most_points": "Traditional ruleset: the player with the most points wins.",

View File

@@ -236,12 +236,24 @@ abstract class AppLocalizations {
/// **'Delete Group'** /// **'Delete Group'**
String get 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 /// Button text to edit a group
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Edit Group'** /// **'Edit Group'**
String get 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 /// Error message when group creation fails
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -374,6 +386,12 @@ abstract class AppLocalizations {
/// **'Match name'** /// **'Match name'**
String get 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 /// Label for matches
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -518,6 +536,12 @@ abstract class AppLocalizations {
/// **'Recent Matches'** /// **'Recent Matches'**
String get recent_matches; String get recent_matches;
/// Label for match results
///
/// In en, this message translates to:
/// **'Results'**
String get results;
/// Ruleset label /// Ruleset label
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -81,9 +81,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get delete_group => 'Gruppe löschen'; String get delete_group => 'Gruppe löschen';
@override
String get delete_match => 'Spiel löschen';
@override @override
String get edit_group => 'Gruppe bearbeiten'; String get edit_group => 'Gruppe bearbeiten';
@override
String get enter_results => 'Ergebnisse eintragen';
@override @override
String get error_creating_group => String get error_creating_group =>
'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen';
@@ -151,6 +157,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get match_name => 'Spieltitel'; String get match_name => 'Spieltitel';
@override
String get match_profile => 'Spielprofil';
@override @override
String get matches => 'Spiele'; String get matches => 'Spiele';
@@ -226,6 +235,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get recent_matches => 'Letzte Spiele'; String get recent_matches => 'Letzte Spiele';
@override
String get results => 'Ergebnisse';
@override @override
String get ruleset => 'Regelwerk'; String get ruleset => 'Regelwerk';

View File

@@ -81,9 +81,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get delete_group => 'Delete Group'; String get delete_group => 'Delete Group';
@override
String get delete_match => 'Delete Match';
@override @override
String get edit_group => 'Edit Group'; String get edit_group => 'Edit Group';
@override
String get enter_results => 'Enter Results';
@override @override
String get error_creating_group => String get error_creating_group =>
'Error while creating group, please try again'; 'Error while creating group, please try again';
@@ -151,6 +157,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get match_name => 'Match name'; String get match_name => 'Match name';
@override
String get match_profile => 'Match Profile';
@override @override
String get matches => 'Matches'; String get matches => 'Matches';
@@ -226,6 +235,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get recent_matches => 'Recent Matches'; String get recent_matches => 'Recent Matches';
@override
String get results => 'Results';
@override @override
String get ruleset => 'Ruleset'; String get ruleset => 'Ruleset';

View File

@@ -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<MatchProfileView> createState() => _MatchProfileViewState();
}
class _MatchProfileViewState extends State<MatchProfileView> {
late final AppDatabase db;
/// All players who participated in the match
late final List<Player> allPlayers;
@override
void initState() {
super.initState();
db = Provider.of<AppDatabase>(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<bool>(
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<Player> _getAllPlayers() {
final List<Player> 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;
}
}

View File

@@ -11,7 +11,7 @@ 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/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_profile_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/main_menu_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart';
@@ -89,10 +89,9 @@ class _MatchViewState extends State<MatchView> {
Navigator.push( Navigator.push(
context, context,
adaptivePageRoute( adaptivePageRoute(
fullscreenDialog: true, builder: (context) => MatchProfileView(
builder: (context) => MatchResultView(
match: matches[index], match: matches[index],
onWinnerChanged: loadGames, callback: loadGames,
), ),
), ),
); );
@@ -128,6 +127,7 @@ class _MatchViewState extends State<MatchView> {
/// Loads the games from the database and sorts them by creation date. /// Loads the games from the database and sorts them by creation date.
void loadGames() { void loadGames() {
isLoading = true;
Future.wait([ Future.wait([
db.matchDao.getAllMatches(), db.matchDao.getAllMatches(),
Future.delayed(Constants.MINIMUM_SKELETON_DURATION), Future.delayed(Constants.MINIMUM_SKELETON_DURATION),

View File

@@ -7,16 +7,16 @@ class MainMenuButton extends StatefulWidget {
/// - [onPressed]: The callback to be invoked when the button is pressed. /// - [onPressed]: The callback to be invoked when the button is pressed.
const MainMenuButton({ const MainMenuButton({
super.key, super.key,
required this.text, required this.icon,
this.icon,
required this.onPressed, required this.onPressed,
this.text,
}); });
/// The text of the button. /// The text of the button.
final String text; final String? text;
/// The icon of the button. /// The icon of the button.
final IconData? icon; final IconData icon;
/// The callback to be invoked when the button is pressed. /// The callback to be invoked when the button is pressed.
final void Function() onPressed; final void Function() onPressed;
@@ -71,18 +71,18 @@ class _MainMenuButtonState extends State<MainMenuButton>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ 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), const SizedBox(width: 7),
], Text(
Text( widget.text!,
widget.text, style: const TextStyle(
style: const TextStyle( fontSize: 18,
fontSize: 18, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, color: Colors.black,
color: Colors.black, ),
), ),
), ],
], ],
), ),
), ),

View File

@@ -1,7 +1,7 @@
name: game_tracker name: game_tracker
description: "Game Tracking App for Card Games" description: "Game Tracking App for Card Games"
publish_to: 'none' publish_to: 'none'
version: 0.0.9+236 version: 0.0.9+242
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1