From a8129eb13427ebce0ab48c4be59c892203274cbb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 16 Jan 2026 23:34:47 +0100 Subject: [PATCH 01/15] Implemented first version of group profile --- lib/l10n/arb/app_de.arb | 7 + lib/l10n/arb/app_en.arb | 28 ++ lib/l10n/generated/app_localizations.dart | 42 +++ lib/l10n/generated/app_localizations_de.dart | 21 ++ lib/l10n/generated/app_localizations_en.dart | 21 ++ .../group_view/group_profile_view.dart | 271 ++++++++++++++++++ .../main_menu/group_view/groups_view.dart | 21 +- .../widgets/tiles/group_tile.dart | 119 ++++---- pubspec.yaml | 2 +- 9 files changed, 479 insertions(+), 53 deletions(-) create mode 100644 lib/presentation/views/main_menu/group_view/group_profile_view.dart diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 7091586..2ef9ee9 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -4,6 +4,7 @@ "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", "app_name": "Game Tracker", + "best_player": "Beste:r Spieler:in", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", @@ -13,6 +14,7 @@ "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", "create_new_match": "Neues Spiel erstellen", + "created_on": "Erstellt am", "data": "Daten", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", @@ -20,6 +22,8 @@ "days_ago": "vor {count} Tagen", "delete": "Löschen", "delete_all_data": "Alle Daten löschen", + "delete_group": "Gruppe löschen", + "edit_group": "Gruppe bearbeiten", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_reading_file": "Fehler beim Lesen der Datei", "export_canceled": "Export abgebrochen", @@ -29,6 +33,7 @@ "game_name": "Spielvorlagenname", "group": "Gruppe", "group_name": "Gruppenname", + "group_profile": "Gruppenprofil", "groups": "Gruppen", "home": "Startseite", "import_canceled": "Import abgebrochen", @@ -42,6 +47,7 @@ "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", "matches": "Spiele", + "members": "Mitglieder", "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", @@ -57,6 +63,7 @@ "none": "Kein", "none_group": "Keine", "not_available": "Nicht verfügbar", + "played_matches": "Gespielte Spiele", "player_name": "Spieler:innenname", "players": "Spieler:innen", "players_count": "{count} Spieler", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 6eb7613..fa4adc8 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -12,6 +12,9 @@ "@app_name": { "description": "The name of the App" }, + "@best_player": { + "description": "Label for best player statistic" + }, "@cancel": { "description": "Cancel button text" }, @@ -39,6 +42,9 @@ "@create_new_match": { "description": "Button text to create a new match" }, + "@created_on": { + "description": "Label for creation date" + }, "@data": { "description": "Data label" }, @@ -65,6 +71,12 @@ "@delete_all_data": { "description": "Confirmation dialog for deleting all data" }, + "@delete_group": { + "description": "Button text to delete a group" + }, + "@edit_group": { + "description": "Button text to edit a group" + }, "@error_creating_group": { "description": "Error message when group creation fails" }, @@ -92,6 +104,9 @@ "@group_name": { "description": "Placeholder for group name input" }, + "@group_profile": { + "description": "Title for group profile view" + }, "@groups": { "description": "Label for groups" }, @@ -131,6 +146,9 @@ "@matches": { "description": "Label for matches" }, + "@members": { + "description": "Label for group members" + }, "@most_points": { "description": "Title for most points ruleset" }, @@ -176,6 +194,9 @@ "@not_available": { "description": "Abbreviation for not available" }, + "@played_matches": { + "description": "Label for played matches statistic" + }, "@player_name": { "description": "Placeholder for player name input" }, @@ -281,6 +302,7 @@ "all_players_selected": "All players selected", "amount_of_matches": "Amount of Matches", "app_name": "Game Tracker", + "best_player": "Best Player", "cancel": "Cancel", "choose_game": "Choose Game", "choose_group": "Choose Group", @@ -289,6 +311,7 @@ "create_group": "Create Group", "create_match": "Create match", "create_new_group": "Create new group", + "created_on": "Created on", "create_new_match": "Create new match", "data": "Data", "data_successfully_deleted": "Data successfully deleted", @@ -297,6 +320,8 @@ "days_ago": "{count} days ago", "delete": "Delete", "delete_all_data": "Delete all data", + "delete_group": "Delete Group", + "edit_group": "Edit Group", "error_creating_group": "Error while creating group, please try again", "error_reading_file": "Error reading file", "export_canceled": "Export canceled", @@ -306,6 +331,7 @@ "game_name": "Game Name", "group": "Group", "group_name": "Group name", + "group_profile": "Group Profile", "groups": "Groups", "home": "Home", "import_canceled": "Import canceled", @@ -319,6 +345,7 @@ "match_in_progress": "Match in progress...", "match_name": "Match name", "matches": "Matches", + "members": "Members", "most_points": "Most Points", "no_data_available": "No data available", "no_groups_created_yet": "No groups created yet", @@ -334,6 +361,7 @@ "none": "None", "none_group": "None", "not_available": "Not available", + "played_matches": "Played Matches", "player_name": "Player name", "players": "Players", "players_count": "{count} Players", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index e78d69b..57dbdd8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -122,6 +122,12 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get app_name; + /// Label for best player statistic + /// + /// In en, this message translates to: + /// **'Best Player'** + String get best_player; + /// Cancel button text /// /// In en, this message translates to: @@ -170,6 +176,12 @@ abstract class AppLocalizations { /// **'Create new group'** String get create_new_group; + /// Label for creation date + /// + /// In en, this message translates to: + /// **'Created on'** + String get created_on; + /// Button text to create a new match /// /// In en, this message translates to: @@ -218,6 +230,18 @@ abstract class AppLocalizations { /// **'Delete all data'** String get delete_all_data; + /// Button text to delete a group + /// + /// In en, this message translates to: + /// **'Delete Group'** + String get delete_group; + + /// Button text to edit a group + /// + /// In en, this message translates to: + /// **'Edit Group'** + String get edit_group; + /// Error message when group creation fails /// /// In en, this message translates to: @@ -272,6 +296,12 @@ abstract class AppLocalizations { /// **'Group name'** String get group_name; + /// Title for group profile view + /// + /// In en, this message translates to: + /// **'Group Profile'** + String get group_profile; + /// Label for groups /// /// In en, this message translates to: @@ -350,6 +380,12 @@ abstract class AppLocalizations { /// **'Matches'** String get matches; + /// Label for group members + /// + /// In en, this message translates to: + /// **'Members'** + String get members; + /// Title for most points ruleset /// /// In en, this message translates to: @@ -440,6 +476,12 @@ abstract class AppLocalizations { /// **'Not available'** String get not_available; + /// Label for played matches statistic + /// + /// In en, this message translates to: + /// **'Played Matches'** + String get played_matches; + /// Placeholder for player name input /// /// 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 8ecc0a7..f78f9f4 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -20,6 +20,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get app_name => 'Game Tracker'; + @override + String get best_player => 'Beste:r Spieler:in'; + @override String get cancel => 'Abbrechen'; @@ -46,6 +49,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_new_group => 'Neue Gruppe erstellen'; + @override + String get created_on => 'Erstellt am'; + @override String get create_new_match => 'Neues Spiel erstellen'; @@ -72,6 +78,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get delete_all_data => 'Alle Daten löschen'; + @override + String get delete_group => 'Gruppe löschen'; + + @override + String get edit_group => 'Gruppe bearbeiten'; + @override String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @@ -100,6 +112,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get group_name => 'Gruppenname'; + @override + String get group_profile => 'Gruppenprofil'; + @override String get groups => 'Gruppen'; @@ -139,6 +154,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get matches => 'Spiele'; + @override + String get members => 'Mitglieder'; + @override String get most_points => 'Höchste Punkte'; @@ -185,6 +203,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get not_available => 'Nicht verfügbar'; + @override + String get played_matches => 'Gespielte Spiele'; + @override String get player_name => 'Spieler:innenname'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index b911676..32512c7 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -20,6 +20,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_name => 'Game Tracker'; + @override + String get best_player => 'Best Player'; + @override String get cancel => 'Cancel'; @@ -46,6 +49,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_new_group => 'Create new group'; + @override + String get created_on => 'Created on'; + @override String get create_new_match => 'Create new match'; @@ -72,6 +78,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get delete_all_data => 'Delete all data'; + @override + String get delete_group => 'Delete Group'; + + @override + String get edit_group => 'Edit Group'; + @override String get error_creating_group => 'Error while creating group, please try again'; @@ -100,6 +112,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get group_name => 'Group name'; + @override + String get group_profile => 'Group Profile'; + @override String get groups => 'Groups'; @@ -139,6 +154,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get matches => 'Matches'; + @override + String get members => 'Members'; + @override String get most_points => 'Most Points'; @@ -185,6 +203,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get not_available => 'Not available'; + @override + String get played_matches => 'Played Matches'; + @override String get player_name => 'Player name'; diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart new file mode 100644 index 0000000..90151c1 --- /dev/null +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -0,0 +1,271 @@ +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/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/group_view/create_group_view.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.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/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 GroupProfileView extends StatefulWidget { + /// A view that displays the profile of a group + /// - [group]: The group to display + const GroupProfileView({ + super.key, + required this.group, + required this.callback, + }); + + /// The group to display + final Group group; + + final VoidCallback callback; + + @override + State createState() => _GroupProfileViewState(); +} + +class _GroupProfileViewState extends State { + late final AppDatabase db; + bool isLoading = true; + + /// Total matches played in this group + int totalMatches = 0; + + String bestPlayer = ''; + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + _loadStatistics(); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + title: Text(loc.group_profile), + actions: [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + showDialog( + context: context, + builder: (context) => CustomAlertDialog( + title: '${loc.delete_group}?', + 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.groupDao.deleteGroup(groupId: widget.group.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: [ + Center( + child: Container( + width: 55, + height: 55, + decoration: BoxDecoration( + color: CustomTheme.primaryColor.withGreen(40), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.group, + size: 38, + color: CustomTheme.secondaryColor, + ), + ), + ), + const SizedBox(height: 10), + Text( + widget.group.name, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 5), + Text( + "${loc.created_on} ${DateFormat('dd.MM.yyyy').format(widget.group.createdAt)}", + style: const TextStyle( + fontSize: 12, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + InfoTile( + title: loc.members, + icon: Icons.people, + content: Wrap( + spacing: 8, + runSpacing: 8, + children: widget.group.members.map((member) { + return TextIconTile( + text: member.name, + iconEnabled: false, + ); + }).toList(), + ), + ), + const SizedBox(height: 15), + InfoTile( + title: loc.statistics, + icon: Icons.bar_chart, + content: AppSkeleton( + enabled: isLoading, + child: Column( + children: [ + _buildStatRow( + loc.members, + widget.group.members.length.toString(), + ), + _buildStatRow( + loc.played_matches, + totalMatches.toString(), + ), + _buildStatRow(loc.best_player, bestPlayer), + ], + ), + ), + ), + ], + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: MainMenuButton( + text: loc.edit_group, + icon: Icons.edit, + onPressed: () async { + await Navigator.push( + context, + adaptivePageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ); + } + + /// Builds a single statistic row with a label and value + Widget _buildStatRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + label, + style: const TextStyle( + fontSize: 16, + color: CustomTheme.textColor, + ), + ), + ], + ), + Text( + value, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } + + /// Loads statistics for this group + Future _loadStatistics() async { + final matches = await db.matchDao.getAllMatches(); + final groupMatches = matches + .where((match) => match.group?.id == widget.group.id) + .toList(); + + setState(() { + totalMatches = groupMatches.length; + bestPlayer = _getBestPlayer(groupMatches); + isLoading = false; + }); + } + + /// Determines the best player in the group based on match wins + String _getBestPlayer(List matches) { + final bestPlayerCounts = {}; + + // Count wins for each player + for (var match in matches) { + if (match.winner != null) { + bestPlayerCounts.update( + match.winner!, + (value) => value + 1, + ifAbsent: () => 1, + ); + } + } + + // Sort players by win count + final sortedPlayers = bestPlayerCounts.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + + // Get the best player + bestPlayer = sortedPlayers.isNotEmpty ? sortedPlayers.first.key.name : '-'; + + return bestPlayer; + } +} diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 10ae148..0083f26 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -7,6 +7,7 @@ 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/views/main_menu/group_view/group_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/group_tile.dart'; @@ -74,7 +75,22 @@ class _GroupsViewState extends State { height: MediaQuery.paddingOf(context).bottom - 20, ); } - return GroupTile(group: groups[index]); + return GroupTile( + group: groups[index], + onTap: () async { + await Navigator.push( + context, + adaptivePageRoute( + builder: (context) { + return GroupProfileView( + group: groups[index], + callback: loadGroups, + ); + }, + ), + ); + }, + ); }, ), ), @@ -105,6 +121,9 @@ class _GroupsViewState extends State { } void loadGroups() { + setState(() { + isLoading = true; + }); Future.wait([ db.groupDao.getAllGroups(), Future.delayed(Constants.minimumSkeletonDuration), diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index a06c6b5..c035a04 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -3,11 +3,17 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; -class GroupTile extends StatelessWidget { +class GroupTile extends StatefulWidget { /// A tile widget that displays information about a group, including its name and members. /// - [group]: The group data to be displayed. /// - [isHighlighted]: Whether the tile should be highlighted. - const GroupTile({super.key, required this.group, this.isHighlighted = false}); + /// - [onTap]: Callback function to be executed when the tile is tapped. + const GroupTile({ + super.key, + required this.group, + this.isHighlighted = false, + this.onTap, + }); /// The group data to be displayed. final Group group; @@ -15,61 +21,72 @@ class GroupTile extends StatelessWidget { /// Whether the tile should be highlighted. final bool isHighlighted; + /// Callback function to be executed when the tile is tapped. + final VoidCallback? onTap; + + @override + State createState() => _GroupTileState(); +} + +class _GroupTileState extends State { @override Widget build(BuildContext context) { - return AnimatedContainer( - margin: CustomTheme.standardMargin, - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: isHighlighted - ? CustomTheme.highlightedBoxDecoration - : CustomTheme.standardBoxDecoration, - duration: const Duration(milliseconds: 150), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - group.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - ), - Row( - children: [ - Text( - '${group.members.length}', + return GestureDetector( + onTap: widget.onTap, + child: AnimatedContainer( + margin: CustomTheme.standardMargin, + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: widget.isHighlighted + ? CustomTheme.highlightedBoxDecoration + : CustomTheme.standardBoxDecoration, + duration: const Duration(milliseconds: 150), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + widget.group.name, + overflow: TextOverflow.ellipsis, style: const TextStyle( - fontWeight: FontWeight.w900, + fontWeight: FontWeight.bold, fontSize: 18, ), ), - const SizedBox(width: 3), - const Icon(Icons.group, size: 22), - ], - ), - ], - ), - const SizedBox(height: 5), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 12.0, - runSpacing: 8.0, - children: [ - for (var member in [ - ...group.members, - ]..sort((a, b) => a.name.compareTo(b.name))) - TextIconTile(text: member.name, iconEnabled: false), - ], - ), - const SizedBox(height: 2.5), - ], + ), + Row( + children: [ + Text( + '${widget.group.members.length}', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + const SizedBox(width: 3), + const Icon(Icons.group, size: 22), + ], + ), + ], + ), + const SizedBox(height: 5), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 12.0, + runSpacing: 8.0, + children: [ + for (var member in [ + ...widget.group.members, + ]..sort((a, b) => a.name.compareTo(b.name))) + TextIconTile(text: member.name, iconEnabled: false), + ], + ), + const SizedBox(height: 2.5), + ], + ), ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index e9fd894..3f5a5f8 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.7+212 +version: 0.0.7+221 environment: sdk: ^3.8.1 From 6a0896d818f2ed5caf9883c793787349e81f3bb6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 00:52:09 +0100 Subject: [PATCH 02/15] Implemented ColoredIconContainer --- .../group_view/group_profile_view.dart | 33 +++++------ .../licenses/license_detail_view.dart | 19 ++----- lib/presentation/widgets/colored_icon.dart | 57 +++++++++++++++++++ .../widgets/tiles/license_tile.dart | 17 ++---- .../widgets/tiles/settings_list_tile.dart | 17 ++---- pubspec.yaml | 2 +- 6 files changed, 89 insertions(+), 56 deletions(-) create mode 100644 lib/presentation/widgets/colored_icon.dart diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index 90151c1..2555020 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -1,15 +1,14 @@ 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/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/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.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.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'; @@ -108,19 +107,11 @@ class _GroupProfileViewState extends State { bottom: 100, ), children: [ - Center( - child: Container( - width: 55, - height: 55, - decoration: BoxDecoration( - color: CustomTheme.primaryColor.withGreen(40), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.group, - size: 38, - color: CustomTheme.secondaryColor, - ), + const Center( + child: ColoredIconContainer( + icon: Icons.group, + containerSize: 55, + iconSize: 38, ), ), const SizedBox(height: 10), @@ -185,15 +176,19 @@ class _GroupProfileViewState extends State { child: MainMenuButton( text: loc.edit_group, icon: Icons.edit, - onPressed: () async { + onPressed: () { + // TODO: Uncomment when GroupDetailView is implemented + /* await Navigator.push( context, adaptivePageRoute( builder: (context) { - return const CreateGroupView(); + + return const GroupDetailView(); }, ), - ); + );*/ + print('Edit Group pressed'); }, ), ), @@ -204,6 +199,8 @@ class _GroupProfileViewState extends State { } /// Builds a single statistic row with a label and value + /// - [label]: The label of the statistic + /// - [value]: The value of the statistic Widget _buildStatRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index e46fc31..5c5e709 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -2,6 +2,7 @@ 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/settings_view/licenses/oss_licenses.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon.dart'; import 'package:url_launcher/url_launcher.dart'; class LicenseDetailView extends StatelessWidget { @@ -29,19 +30,11 @@ class LicenseDetailView extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - margin: const EdgeInsetsGeometry.only(right: 15), - width: 60, - height: 60, - decoration: BoxDecoration( - color: CustomTheme.primaryColor.withAlpha(40), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.description, - color: CustomTheme.primaryColor, - size: 30, - ), + const ColoredIconContainer( + icon: Icons.description, + margin: EdgeInsetsGeometry.only(right: 15), + containerSize: 60, + iconSize: 30, ), Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/widgets/colored_icon.dart b/lib/presentation/widgets/colored_icon.dart new file mode 100644 index 0000000..be51cd2 --- /dev/null +++ b/lib/presentation/widgets/colored_icon.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class ColoredIconContainer extends StatelessWidget { + /// A customizable container widget that displays an icon with a colored background. + /// - [icon]: The icon to be displayed inside the container. + /// - [containerSize]: The size of the container (width and height). + /// - [iconSize]: The size of the icon inside the container. + /// - [margin]: Optional margin around the container. + /// - [padding]: Optional padding inside the container. + const ColoredIconContainer({ + super.key, + required this.icon, + this.containerSize = 44, + this.iconSize = 28, + this.margin, + this.padding, + }); + + /// The icon to be displayed inside the container. + final IconData icon; + + /// The size of the container (width and height). + final double containerSize; + + /// The size of the icon inside the container. + final double iconSize; + + /// Optional margin around the container. + final EdgeInsetsGeometry? margin; + + /// Optional padding inside the container. + final EdgeInsetsGeometry? padding; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Container( + width: containerSize, + height: containerSize, + margin: margin, + padding: padding, + decoration: BoxDecoration( + color: CustomTheme.primaryColor.withAlpha(40), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + icon, + size: iconSize, + color: CustomTheme.primaryColor.withGreen(40), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index 14ee2bf..3523adb 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon.dart'; class LicenseTile extends StatelessWidget { /// A tile widget that displays information about a software package license. @@ -29,18 +30,10 @@ class LicenseTile extends StatelessWidget { ), child: Row( children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: CustomTheme.primaryColor.withAlpha(40), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.description, - color: CustomTheme.primaryColor, - size: 32, - ), + const ColoredIconContainer( + icon: Icons.description, + containerSize: 50, + iconSize: 32, ), const SizedBox(width: 16), Expanded( diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 53fb041..50f27d2 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon.dart'; class SettingsListTile extends StatelessWidget { /// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. @@ -46,18 +47,10 @@ class SettingsListTile extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: CustomTheme.primaryColor.withAlpha(40), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - icon, - size: 28, - color: CustomTheme.primaryColor.withGreen(40), - ), + ColoredIconContainer( + icon: icon, + containerSize: 44, + iconSize: 28, ), const SizedBox(width: 16), Text(title, style: const TextStyle(fontSize: 18)), diff --git a/pubspec.yaml b/pubspec.yaml index 3f5a5f8..e8de5ed 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.7+221 +version: 0.0.7+227 environment: sdk: ^3.8.1 From fc6eb2b9cf3ea3490bbac698b5d4e4b7840d7034 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 00:52:13 +0100 Subject: [PATCH 03/15] Added comments --- lib/core/custom_theme.dart | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 12ac4a8..2c18073 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -5,14 +5,32 @@ class CustomTheme { CustomTheme._(); // Private constructor to prevent instantiation // ==================== Colors ==================== + + /// Primary color of the app theme static Color primaryColor = const Color(0xFF7505E4); + + /// Secondary color of the app theme static Color secondaryColor = const Color(0xFFAFA2FF); + + /// Background color of the app theme static Color backgroundColor = const Color(0xFF0B0B0B); + + /// Default color for boxes and containers static Color boxColor = const Color(0xFF101010); - static Color onBoxColor = const Color(0xFF181818); + + /// Default border color for boxes and containers static Color boxBorder = const Color(0xFF272727); + + /// Color for boxes and containers displayed on boxes + static Color onBoxColor = const Color(0xFF181818); + + /// Text color used throughout the app static const Color textColor = Colors.white; + + /// Selected color for the [NavbarItem] static Color navBarItemSelectedColor = primaryColor.withGreen(100); + + /// Unselected color for the [NavbarItem] static Color navBarItemUnselectedColor = Colors.grey.shade400; // ==================== Border Radius ==================== From abb0fcbbd62b220665b6321b6ca43b2ed5b06cca Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 00:57:28 +0100 Subject: [PATCH 04/15] Updated file name --- .../views/main_menu/group_view/group_profile_view.dart | 2 +- .../main_menu/settings_view/licenses/license_detail_view.dart | 2 +- .../widgets/{colored_icon.dart => colored_icon_container.dart} | 0 lib/presentation/widgets/tiles/license_tile.dart | 2 +- lib/presentation/widgets/tiles/settings_list_tile.dart | 2 +- pubspec.yaml | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename lib/presentation/widgets/{colored_icon.dart => colored_icon_container.dart} (100%) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index 2555020..6a92c22 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -8,7 +8,7 @@ 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/animated_dialog_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon.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'; diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 5c5e709..54ff34e 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -2,7 +2,7 @@ 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/settings_view/licenses/oss_licenses.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; import 'package:url_launcher/url_launcher.dart'; class LicenseDetailView extends StatelessWidget { diff --git a/lib/presentation/widgets/colored_icon.dart b/lib/presentation/widgets/colored_icon_container.dart similarity index 100% rename from lib/presentation/widgets/colored_icon.dart rename to lib/presentation/widgets/colored_icon_container.dart diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index 3523adb..33e5a45 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; class LicenseTile extends StatelessWidget { /// A tile widget that displays information about a software package license. diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 50f27d2..d4bc6dc 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon.dart'; +import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; class SettingsListTile extends StatelessWidget { /// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. diff --git a/pubspec.yaml b/pubspec.yaml index e8de5ed..f64f521 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.7+227 +version: 0.0.8+227 environment: sdk: ^3.8.1 From 3addaa0f9d5bf227a1391135462d0332ac46391e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 14:22:45 +0100 Subject: [PATCH 05/15] Disable resizing when using keyboard --- .../views/main_menu/group_view/create_group_view.dart | 1 + .../main_menu/match_view/create_match/choose_game_view.dart | 1 + .../main_menu/match_view/create_match/choose_group_view.dart | 1 + .../main_menu/match_view/create_match/create_match_view.dart | 1 + pubspec.yaml | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 719b47d..b342448 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -47,6 +47,7 @@ class _CreateGroupViewState extends State { final loc = AppLocalizations.of(context); return ScaffoldMessenger( child: Scaffold( + resizeToAvoidBottomInset: false, backgroundColor: CustomTheme.backgroundColor, appBar: AppBar(title: Text(loc.create_new_group)), body: SafeArea( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 32868e4..3ff6e79 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -43,6 +43,7 @@ class _ChooseGameViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, + resizeToAvoidBottomInset: false, appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_ios), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 00a0276..592d765 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -43,6 +43,7 @@ class _ChooseGroupViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, + resizeToAvoidBottomInset: false, appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_ios), diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 694a82d..5509911 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -125,6 +125,7 @@ class _CreateMatchViewState extends State { final loc = AppLocalizations.of(context); return ScaffoldMessenger( child: Scaffold( + resizeToAvoidBottomInset: false, backgroundColor: CustomTheme.backgroundColor, appBar: AppBar(title: Text(loc.create_new_match)), body: SafeArea( diff --git a/pubspec.yaml b/pubspec.yaml index e9fd894..4d5b4bc 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.7+212 +version: 0.0.8+213 environment: sdk: ^3.8.1 From ff47ef38c1d32f9f322426b8c77c854f6b50f7ea Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 14:51:12 +0100 Subject: [PATCH 06/15] Changed alignment --- .../views/main_menu/group_view/group_profile_view.dart | 5 ++++- lib/presentation/widgets/tiles/info_tile.dart | 6 +++++- pubspec.yaml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index 6a92c22..1b19b6a 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -137,8 +137,11 @@ class _GroupProfileViewState extends State { InfoTile( title: loc.members, icon: Icons.people, + horizontalAlignment: CrossAxisAlignment.start, content: Wrap( - spacing: 8, + runAlignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 16, runSpacing: 8, children: widget.group.members.map((member) { return TextIconTile( diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index 280c7d7..78d7f28 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -17,6 +17,7 @@ class InfoTile extends StatefulWidget { this.padding, this.height, this.width, + this.horizontalAlignment = CrossAxisAlignment.center, }); /// The title text displayed on the tile. @@ -37,6 +38,9 @@ class InfoTile extends StatefulWidget { /// Optional width for the tile. final double? width; + /// The main axis alignment for the content. + final CrossAxisAlignment horizontalAlignment; + @override State createState() => _InfoTileState(); } @@ -51,7 +55,7 @@ class _InfoTileState extends State { decoration: CustomTheme.standardBoxDecoration, child: Column( mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: widget.horizontalAlignment, children: [ Row( children: [ diff --git a/pubspec.yaml b/pubspec.yaml index f64f521..1ea28cc 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.8+227 +version: 0.0.8+230 environment: sdk: ^3.8.1 From 514e0f8064b06852e0feb3fc4c7a4fb4bbc0fe68 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 14:54:06 +0100 Subject: [PATCH 07/15] Changed date format --- .../views/main_menu/group_view/group_profile_view.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index 1b19b6a..c739973 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -126,7 +126,7 @@ class _GroupProfileViewState extends State { ), const SizedBox(height: 5), Text( - "${loc.created_on} ${DateFormat('dd.MM.yyyy').format(widget.group.createdAt)}", + "${loc.created_on} ${DateFormat('MMM d, yyyy').format(widget.group.createdAt)}", style: const TextStyle( fontSize: 12, color: CustomTheme.textColor, diff --git a/pubspec.yaml b/pubspec.yaml index 1ea28cc..8b52ca1 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.8+230 +version: 0.0.8+231 environment: sdk: ^3.8.1 From ddd0a5d8bdb1755ceb84292b7f3093b4193e3865 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 15:17:33 +0100 Subject: [PATCH 08/15] Updated dateformat to localize --- .../views/main_menu/group_view/group_profile_view.dart | 2 +- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index c739973..072bbd6 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -126,7 +126,7 @@ class _GroupProfileViewState extends State { ), const SizedBox(height: 5), Text( - "${loc.created_on} ${DateFormat('MMM d, yyyy').format(widget.group.createdAt)}", + '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(widget.group.createdAt)}', style: const TextStyle( fontSize: 12, color: CustomTheme.textColor, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 11cdea0..e1365c1 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -230,7 +230,7 @@ class _MatchTileState extends State { } else if (difference.inDays < 7) { return loc.days_ago(difference.inDays); } else { - return DateFormat('MMM d, yyyy').format(dateTime); + return '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(dateTime)}'; } } From 8a38b9c3ea861933cc34e3e7d07776028d58cf72 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 17 Jan 2026 15:17:33 +0100 Subject: [PATCH 09/15] Updated dateformat to localize --- .../views/main_menu/group_view/group_profile_view.dart | 2 +- lib/presentation/widgets/tiles/match_tile.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index c739973..072bbd6 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -126,7 +126,7 @@ class _GroupProfileViewState extends State { ), const SizedBox(height: 5), Text( - "${loc.created_on} ${DateFormat('MMM d, yyyy').format(widget.group.createdAt)}", + '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(widget.group.createdAt)}', style: const TextStyle( fontSize: 12, color: CustomTheme.textColor, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 11cdea0..e1365c1 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -230,7 +230,7 @@ class _MatchTileState extends State { } else if (difference.inDays < 7) { return loc.days_ago(difference.inDays); } else { - return DateFormat('MMM d, yyyy').format(dateTime); + return '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(dateTime)}'; } } diff --git a/pubspec.yaml b/pubspec.yaml index 6f6fd9b..a8159f8 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.8+232 +version: 0.0.8+233 environment: sdk: ^3.8.1 From 92c62000af89d92f87d328c6d250115e480b1165 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 21:12:17 +0100 Subject: [PATCH 10/15] remove margin from title_description_list_tile.dart --- lib/presentation/widgets/tiles/title_description_list_tile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 3141cbe..a963d16 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -73,7 +73,6 @@ class TitleDescriptionListTile extends StatelessWidget { const Spacer(), Container( constraints: const BoxConstraints(maxWidth: 115), - margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, horizontal: 6, From 5f987806d67f4b0ee4c268f2b36529942371fc01 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 21:12:43 +0100 Subject: [PATCH 11/15] remove ruleset --- .../create_match/choose_ruleset_view.dart | 99 ------------------- .../create_match/create_match_view.dart | 49 +-------- 2 files changed, 2 insertions(+), 146 deletions(-) delete mode 100644 lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart deleted file mode 100644 index 3b1f37b..0000000 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ /dev/null @@ -1,99 +0,0 @@ -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 { - /// A view that allows the user to choose a ruleset from a list of available rulesets - /// - [rulesets]: A list of tuples containing the ruleset and its description - /// - [initialRulesetIndex]: The index of the initially selected ruleset - const ChooseRulesetView({ - super.key, - required this.rulesets, - required this.initialRulesetIndex, - }); - - /// A list of tuples containing the ruleset and its description - final List<(Ruleset, String)> rulesets; - - /// The index of the initially selected ruleset - final int initialRulesetIndex; - - @override - State createState() => _ChooseRulesetViewState(); -} - -class _ChooseRulesetViewState extends State { - /// Currently selected ruleset index - late int selectedRulesetIndex; - - @override - void initState() { - selectedRulesetIndex = widget.initialRulesetIndex; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final loc = AppLocalizations.of(context); - return DefaultTabController( - length: 2, - initialIndex: 0, - child: Scaffold( - backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: () { - Navigator.of(context).pop( - selectedRulesetIndex == -1 - ? null - : widget.rulesets[selectedRulesetIndex].$1, - ); - }, - ), - title: Text(loc.choose_ruleset), - ), - body: PopScope( - // This fixes that the Android Back Gesture didn't return the - // selectedRulesetIndex and therefore the selected Ruleset wasn't saved - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (didPop) { - return; - } - Navigator.of(context).pop( - selectedRulesetIndex == -1 - ? null - : widget.rulesets[selectedRulesetIndex].$1, - ); - }, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - if (selectedRulesetIndex == index) { - selectedRulesetIndex = -1; - } else { - selectedRulesetIndex = index; - } - }); - }, - title: translateRulesetToString( - widget.rulesets[index].$1, - context, - ), - description: widget.rulesets[index].$2, - isHighlighted: selectedRulesetIndex == index, - ); - }, - ), - ), - ), - ); - } -} diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index ea9cfd5..db46159 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -10,7 +10,6 @@ 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'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; @@ -58,13 +57,6 @@ class _CreateMatchViewState extends State { /// the [ChooseGroupView] String selectedGroupId = ''; - /// The currently selected ruleset - Ruleset? selectedRuleset; - - /// The index of the currently selected ruleset in [rulesets] to mark it in - /// the [ChooseRulesetView] - int selectedRulesetIndex = -1; - /// The index of the currently selected game in [games] to mark it in /// the [ChooseGameView] int selectedGameIndex = -1; @@ -72,9 +64,6 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their localized string representations - late final List<(Ruleset, String)> _rulesets; - @override void initState() { super.initState(); @@ -107,20 +96,14 @@ class _CreateMatchViewState extends State { super.didChangeDependencies(); final loc = AppLocalizations.of(context); hintText ??= loc.match_name; - _rulesets = [ - (Ruleset.singleWinner, loc.ruleset_single_winner), - (Ruleset.singleLoser, loc.ruleset_single_loser), - (Ruleset.mostPoints, loc.ruleset_most_points), - (Ruleset.leastPoints, loc.ruleset_least_points), - ]; } - // 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) { final loc = AppLocalizations.of(context); @@ -157,39 +140,12 @@ class _CreateMatchViewState extends State { setState(() { if (selectedGameIndex != -1) { hintText = games[selectedGameIndex].$1; - selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = _rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); } else { hintText = loc.match_name; - selectedRuleset = null; } }); }, ), - ChooseTile( - title: loc.ruleset, - trailingText: selectedRuleset == null - ? loc.none - : translateRulesetToString(selectedRuleset!, context), - onPressed: () async { - selectedRuleset = await Navigator.of(context).push( - adaptivePageRoute( - builder: (context) => ChooseRulesetView( - rulesets: _rulesets, - initialRulesetIndex: selectedRulesetIndex, - ), - ), - ); - if (!mounted) return; - selectedRulesetIndex = _rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - selectedGameIndex = -1; - setState(() {}); - }, - ), ChooseTile( title: loc.group, trailingText: selectedGroup == null @@ -274,7 +230,6 @@ class _CreateMatchViewState extends State { /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1)) && - selectedRuleset != null; + (selectedPlayers != null && selectedPlayers!.length > 1)); } } From 5c9f44e947f0aed187bf7d78ea00b7b70236f777 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 21:12:53 +0100 Subject: [PATCH 12/15] bump build nr --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9f957c2..c217de3 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.7+213 +version: 0.0.7+215 environment: sdk: ^3.8.1 From f5924c475834bd138a109f7e2bb5691d8d3c2cdb Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 21:27:22 +0100 Subject: [PATCH 13/15] adjusted spacing & changed runAlignment to alignment --- .../views/main_menu/group_view/group_profile_view.dart | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/group_profile_view.dart b/lib/presentation/views/main_menu/group_view/group_profile_view.dart index 072bbd6..e366834 100644 --- a/lib/presentation/views/main_menu/group_view/group_profile_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_profile_view.dart @@ -139,9 +139,9 @@ class _GroupProfileViewState extends State { icon: Icons.people, horizontalAlignment: CrossAxisAlignment.start, content: Wrap( - runAlignment: WrapAlignment.start, + alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, - spacing: 16, + spacing: 12, runSpacing: 8, children: widget.group.members.map((member) { return TextIconTile( diff --git a/pubspec.yaml b/pubspec.yaml index a8159f8..432675a 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.8+233 +version: 0.0.8+234 environment: sdk: ^3.8.1 From e9633a898c1712e58b990c87bd56bf2093716388 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 22:54:18 +0100 Subject: [PATCH 14/15] bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 19d2b43..33091e5 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.8+236 +version: 0.0.9+236 environment: sdk: ^3.8.1 From 39e6e485ac66091c00b40bdf051dc1abeef095c1 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 17 Jan 2026 22:54:22 +0100 Subject: [PATCH 15/15] remove newline --- .../main_menu/match_view/create_match/create_match_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index db46159..e106de7 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -103,7 +103,6 @@ class _CreateMatchViewState extends State { ('Example Game 2', '', Ruleset.singleWinner), ]; - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context);