Match braucht Game #174
@@ -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 ====================
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/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 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<GroupProfileView> createState() => _GroupProfileViewState();
|
||||
}
|
||||
|
||||
class _GroupProfileViewState extends State<GroupProfileView> {
|
||||
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<AppDatabase>(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<bool>(
|
||||
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: [
|
||||
const Center(
|
||||
child: ColoredIconContainer(
|
||||
icon: Icons.group,
|
||||
containerSize: 55,
|
||||
iconSize: 38,
|
||||
),
|
||||
),
|
||||
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.yMMMd(Localizations.localeOf(context).toString()).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,
|
||||
horizontalAlignment: CrossAxisAlignment.start,
|
||||
content: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
spacing: 12,
|
||||
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: () {
|
||||
// TODO: Uncomment when GroupDetailView is implemented
|
||||
/*
|
||||
await Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) {
|
||||
|
||||
return const GroupDetailView();
|
||||
},
|
||||
),
|
||||
);*/
|
||||
print('Edit Group pressed');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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),
|
||||
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<void> _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<Match> matches) {
|
||||
final bestPlayerCounts = <Player, int>{};
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -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<GroupsView> {
|
||||
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<GroupsView> {
|
||||
}
|
||||
|
||||
void loadGroups() {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
Future.wait([
|
||||
db.groupDao.getAllGroups(),
|
||||
Future.delayed(Constants.MINIMUM_SKELETON_DURATION),
|
||||
|
||||
@@ -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_container.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,
|
||||
|
||||
57
lib/presentation/widgets/colored_icon_container.dart
Normal file
57
lib/presentation/widgets/colored_icon_container.dart
Normal file
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,12 +21,22 @@ 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<GroupTile> createState() => _GroupTileState();
|
||||
}
|
||||
|
||||
class _GroupTileState extends State<GroupTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
margin: CustomTheme.standardMargin,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||
decoration: isHighlighted
|
||||
decoration: widget.isHighlighted
|
||||
? CustomTheme.highlightedBoxDecoration
|
||||
: CustomTheme.standardBoxDecoration,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
@@ -32,7 +48,7 @@ class GroupTile extends StatelessWidget {
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
group.name,
|
||||
widget.group.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -43,7 +59,7 @@ class GroupTile extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'${group.members.length}',
|
||||
'${widget.group.members.length}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 18,
|
||||
@@ -63,7 +79,7 @@ class GroupTile extends StatelessWidget {
|
||||
runSpacing: 8.0,
|
||||
children: <Widget>[
|
||||
for (var member in [
|
||||
...group.members,
|
||||
...widget.group.members,
|
||||
]..sort((a, b) => a.name.compareTo(b.name)))
|
||||
TextIconTile(text: member.name, iconEnabled: false),
|
||||
],
|
||||
@@ -71,6 +87,7 @@ class GroupTile extends StatelessWidget {
|
||||
const SizedBox(height: 2.5),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<InfoTile> createState() => _InfoTileState();
|
||||
}
|
||||
@@ -51,7 +55,7 @@ class _InfoTileState extends State<InfoTile> {
|
||||
decoration: CustomTheme.standardBoxDecoration,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
crossAxisAlignment: widget.horizontalAlignment,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
|
||||
@@ -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_container.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(
|
||||
|
||||
@@ -230,7 +230,7 @@ class _MatchTileState extends State<MatchTile> {
|
||||
} 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)}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_container.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)),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: game_tracker
|
||||
description: "Game Tracking App for Card Games"
|
||||
publish_to: 'none'
|
||||
version: 0.0.7+215
|
||||
version: 0.0.8+236
|
||||
|
sneeex marked this conversation as resolved
Outdated
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
|
||||
Reference in New Issue
Block a user
Versionsnummer erhöhen