MVP-Refactoring #139
@@ -1,2 +1,6 @@
|
||||
class Constants {
|
||||
Constants._(); // Private constructor to prevent instantiation
|
||||
|
||||
/// Minimum duration of all app skeletons
|
||||
Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
||||
static Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomTheme {
|
||||
CustomTheme._(); // Private constructor to prevent instantiation
|
||||
|
||||
// ==================== Colors ====================
|
||||
static Color primaryColor = const Color(0xFF7505E4);
|
||||
static Color secondaryColor = const Color(0xFFAFA2FF);
|
||||
static Color backgroundColor = const Color(0xFF0B0B0B);
|
||||
static Color boxColor = const Color(0xFF101010);
|
||||
static Color onBoxColor = const Color(0xFF181818);
|
||||
static Color boxBorder = const Color(0xFF272727);
|
||||
static const Color textColor = Colors.white;
|
||||
|
||||
// ==================== Border Radius ====================
|
||||
static const double standardBorderRadius = 12.0;
|
||||
static BorderRadius get standardBorderRadiusAll =>
|
||||
BorderRadius.circular(standardBorderRadius);
|
||||
|
||||
// ==================== Padding & Margins ====================
|
||||
static const EdgeInsets standardMargin = EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
);
|
||||
static const EdgeInsets tileMargin = EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 5,
|
||||
);
|
||||
|
||||
// ==================== Decorations ====================
|
||||
static BoxDecoration standardBoxDecoration = BoxDecoration(
|
||||
color: boxColor,
|
||||
border: Border.all(color: boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: standardBorderRadiusAll,
|
||||
);
|
||||
|
||||
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
||||
color: boxColor,
|
||||
border: Border.all(color: primaryColor),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: standardBorderRadiusAll,
|
||||
boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)],
|
||||
);
|
||||
|
||||
// ==================== App Bar Theme ====================
|
||||
static AppBarTheme appBarTheme = AppBarTheme(
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: textColor,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
titleTextStyle: const TextStyle(
|
||||
color: Colors.white,
|
||||
color: textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
iconTheme: const IconThemeData(color: textColor),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
|
||||
/// Button types used for styling the [CustomWidthButton]
|
||||
/// - [ButtonType.primary]: Primary button style.
|
||||
/// - [ButtonType.secondary]: Secondary button style.
|
||||
/// - [ButtonType.tertiary]: Tertiary button style.
|
||||
enum ButtonType { primary, secondary, tertiary }
|
||||
|
||||
/// Result types for import operations in the [SettingsView]
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"@@locale": "de",
|
||||
"all_players": "Alle Spieler:innen:",
|
||||
"all_players": "Alle Spieler:innen",
|
||||
"all_players_selected": "Alle Spieler:innen ausgewählt",
|
||||
"amount_of_matches": "Anzahl der Matches",
|
||||
"amount_of_matches": "Anzahl der Spiele",
|
||||
"cancel": "Abbrechen",
|
||||
"choose_game": "Spielvorlage wählen",
|
||||
"choose_group": "Gruppe wählen",
|
||||
"choose_ruleset": "Regelwerk wählen",
|
||||
"could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden",
|
||||
"create_group": "Gruppe erstellen",
|
||||
"create_match": "Match erstellen",
|
||||
"create_match": "Spiel erstellen",
|
||||
"create_new_group": "Neue Gruppe erstellen",
|
||||
"create_new_match": "Neues Match erstellen",
|
||||
"create_new_match": "Neues Spiel erstellen",
|
||||
"data_successfully_deleted": "Daten erfolgreich gelöscht",
|
||||
"data_successfully_exported": "Daten erfolgreich exportiert",
|
||||
"data_successfully_imported": "Daten erfolgreich importiert",
|
||||
@@ -34,28 +34,28 @@
|
||||
"info": "Info",
|
||||
"invalid_schema": "Ungültiges Schema",
|
||||
"least_points": "Niedrigste Punkte",
|
||||
"match_in_progress": "Match läuft...",
|
||||
"match_name": "Matchname",
|
||||
"matches": "Matches",
|
||||
"match_in_progress": "Spiel läuft...",
|
||||
"match_name": "Spieltitel",
|
||||
"matches": "Spiele",
|
||||
"menu": "Menü",
|
||||
"most_points": "Höchste Punkte",
|
||||
"no_data_available": "Keine Daten verfügbar",
|
||||
"no_groups_created_yet": "Noch keine Gruppen erstellt",
|
||||
"no_matches_created_yet": "Noch keine Matches erstellt",
|
||||
"no_matches_created_yet": "Noch keine Spiele erstellt",
|
||||
"no_players_created_yet": "Noch keine Spieler:in erstellt",
|
||||
"no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
|
||||
"no_players_selected": "Keine Spieler:in ausgewählt",
|
||||
"no_recent_matches_available": "Keine letzten Matches verfügbar",
|
||||
"no_second_match_available": "Kein zweites Match verfügbar",
|
||||
"no_players_selected": "Keine Spieler:innen ausgewählt",
|
||||
"no_recent_matches_available": "Keine letzten Spiele verfügbar",
|
||||
"no_second_match_available": "Kein zweites Spiel verfügbar",
|
||||
"no_statistics_available": "Keine Statistiken verfügbar",
|
||||
"none": "Kein",
|
||||
"none_group": "Keine",
|
||||
"not_available": "Nicht verfügbar",
|
||||
"player_name": "Spieler:innenname",
|
||||
"players": "Spieler:in",
|
||||
"players": "Spieler:innen",
|
||||
"players_count": "{count} Spieler",
|
||||
"quick_create": "Schnellzugriff",
|
||||
"recent_matches": "Letzte Matches",
|
||||
"recent_matches": "Letzte Spiele",
|
||||
"ruleset": "Regelwerk",
|
||||
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.",
|
||||
"ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",
|
||||
@@ -64,7 +64,7 @@
|
||||
"search_for_groups": "Nach Gruppen suchen",
|
||||
"search_for_players": "Nach Spieler:innen suchen",
|
||||
"select_winner": "Gewinner:in wählen:",
|
||||
"selected_players": "Ausgewählte Spieler:in: {count}",
|
||||
"selected_players": "Ausgewählte Spieler:innen",
|
||||
"settings": "Einstellungen",
|
||||
"single_loser": "Ein:e Verlierer:in",
|
||||
"single_winner": "Ein:e Gewinner:in",
|
||||
@@ -73,11 +73,11 @@
|
||||
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
||||
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
|
||||
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden",
|
||||
"today_at": "Heute um {time}",
|
||||
"today_at": "Heute um",
|
||||
"undo": "Rückgängig",
|
||||
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
||||
"winner": "Gewinner:in: {winnerName}",
|
||||
"winner": "Gewinner:in",
|
||||
"winrate": "Siegquote",
|
||||
"wins": "Siege",
|
||||
"yesterday_at": "Gestern um {time}"
|
||||
"yesterday_at": "Gestern um"
|
||||
}
|
||||
@@ -9,6 +9,9 @@
|
||||
"@amount_of_matches": {
|
||||
"description": "Label for amount of matches statistic"
|
||||
},
|
||||
"@app_name": {
|
||||
"description": "The name of the App"
|
||||
},
|
||||
"@cancel": {
|
||||
"description": "Cancel button text"
|
||||
},
|
||||
@@ -22,13 +25,7 @@
|
||||
"description": "Label for choosing a ruleset"
|
||||
},
|
||||
"@could_not_add_player": {
|
||||
"description": "Error message when adding a player fails",
|
||||
"placeholders": {
|
||||
"playerName": {
|
||||
"type": "String",
|
||||
"example": "John"
|
||||
}
|
||||
}
|
||||
"description": "Error message when adding a player fails"
|
||||
},
|
||||
"@create_group": {
|
||||
"description": "Button text to create a group"
|
||||
@@ -86,9 +83,6 @@
|
||||
"@game_name": {
|
||||
"description": "Placeholder for game name search"
|
||||
},
|
||||
"@game_tracker": {
|
||||
"description": "App Name"
|
||||
},
|
||||
"@group": {
|
||||
"description": "Group label"
|
||||
},
|
||||
@@ -212,13 +206,7 @@
|
||||
"description": "Label to select the winner"
|
||||
},
|
||||
"@selected_players": {
|
||||
"description": "Shows the number of selected players",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"format": "compact"
|
||||
}
|
||||
}
|
||||
"description": "Shows the number of selected players"
|
||||
},
|
||||
"@settings": {
|
||||
"description": "Settings label"
|
||||
@@ -251,13 +239,7 @@
|
||||
"description": "Warning message for irreversible actions"
|
||||
},
|
||||
"@today_at": {
|
||||
"description": "Date format for today",
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String",
|
||||
"example": "14:30"
|
||||
}
|
||||
}
|
||||
"description": "Date format for today"
|
||||
},
|
||||
"@undo": {
|
||||
"description": "Undo button text"
|
||||
@@ -275,22 +257,17 @@
|
||||
"description": "Label for wins statistic"
|
||||
},
|
||||
"@yesterday_at": {
|
||||
"description": "Date format for yesterday",
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String",
|
||||
"example": "14:30"
|
||||
}
|
||||
}
|
||||
"description": "Date format for yesterday"
|
||||
},
|
||||
"all_players": "All players:",
|
||||
"all_players": "All players",
|
||||
"all_players_selected": "All players selected",
|
||||
"amount_of_matches": "Amount of Matches",
|
||||
"app_name": "Game Tracker",
|
||||
"cancel": "Cancel",
|
||||
"choose_game": "Choose Game",
|
||||
"choose_group": "Choose Group",
|
||||
"choose_ruleset": "Choose Ruleset",
|
||||
"could_not_add_player": "Could not add player {playerName}",
|
||||
"could_not_add_player": "Could not add player",
|
||||
"create_group": "Create Group",
|
||||
"create_match": "Create match",
|
||||
"create_new_group": "Create new group",
|
||||
@@ -308,7 +285,6 @@
|
||||
"format_exception": "Format Exception (see console)",
|
||||
"game": "Game",
|
||||
"game_name": "Game Name",
|
||||
"game_tracker": "Game Tracker",
|
||||
"group": "Group",
|
||||
"group_name": "Group name",
|
||||
"groups": "Groups",
|
||||
@@ -348,7 +324,7 @@
|
||||
"search_for_groups": "Search for groups",
|
||||
"search_for_players": "Search for players",
|
||||
"select_winner": "Select Winner:",
|
||||
"selected_players": "Selected players: {count}",
|
||||
"selected_players": "Selected players",
|
||||
"settings": "Settings",
|
||||
"single_loser": "Single Loser",
|
||||
"single_winner": "Single Winner",
|
||||
@@ -357,11 +333,11 @@
|
||||
"successfully_added_player": "Successfully added player {playerName}",
|
||||
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
||||
"this_cannot_be_undone": "This can't be undone",
|
||||
"today_at": "Today at {time}",
|
||||
"today_at": "Today at",
|
||||
"undo": "Undo",
|
||||
"unknown_exception": "Unknown Exception (see console)",
|
||||
"winner": "Winner",
|
||||
"winrate": "Winrate",
|
||||
"wins": "Wins",
|
||||
"yesterday_at": "Yesterday at {time}"
|
||||
"yesterday_at": "Yesterday at"
|
||||
}
|
||||
@@ -101,7 +101,7 @@ abstract class AppLocalizations {
|
||||
/// Label for all players list
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All players:'**
|
||||
/// **'All players'**
|
||||
String get all_players;
|
||||
|
||||
/// Message when all players are added to selection
|
||||
@@ -116,6 +116,12 @@ abstract class AppLocalizations {
|
||||
/// **'Amount of Matches'**
|
||||
String get amount_of_matches;
|
||||
|
||||
/// The name of the App
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Game Tracker'**
|
||||
String get app_name;
|
||||
|
||||
/// Cancel button text
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -143,8 +149,8 @@ abstract class AppLocalizations {
|
||||
/// Error message when adding a player fails
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not add player {playerName}'**
|
||||
String could_not_add_player(String playerName);
|
||||
/// **'Could not add player'**
|
||||
String could_not_add_player(Object playerName);
|
||||
|
||||
/// Button text to create a group
|
||||
///
|
||||
@@ -248,12 +254,6 @@ abstract class AppLocalizations {
|
||||
/// **'Game Name'**
|
||||
String get game_name;
|
||||
|
||||
/// App Name
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Game Tracker'**
|
||||
String get game_tracker;
|
||||
|
||||
/// Group label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -491,8 +491,8 @@ abstract class AppLocalizations {
|
||||
/// Shows the number of selected players
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Selected players: {count}'**
|
||||
String selected_players(int count);
|
||||
/// **'Selected players'**
|
||||
String get selected_players;
|
||||
|
||||
/// Settings label
|
||||
///
|
||||
@@ -545,8 +545,8 @@ abstract class AppLocalizations {
|
||||
/// Date format for today
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Today at {time}'**
|
||||
String today_at(String time);
|
||||
/// **'Today at'**
|
||||
String get today_at;
|
||||
|
||||
/// Undo button text
|
||||
///
|
||||
@@ -564,7 +564,7 @@ abstract class AppLocalizations {
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Winner'**
|
||||
String winner(Object winnerName);
|
||||
String get winner;
|
||||
|
||||
/// Label for winrate statistic
|
||||
///
|
||||
@@ -581,8 +581,8 @@ abstract class AppLocalizations {
|
||||
/// Date format for yesterday
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yesterday at {time}'**
|
||||
String yesterday_at(String time);
|
||||
/// **'Yesterday at'**
|
||||
String get yesterday_at;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -9,13 +9,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get all_players => 'Alle Spieler:innen:';
|
||||
String get all_players => 'Alle Spieler:innen';
|
||||
|
||||
@override
|
||||
String get all_players_selected => 'Alle Spieler:innen ausgewählt';
|
||||
|
||||
@override
|
||||
String get amount_of_matches => 'Anzahl der Matches';
|
||||
String get amount_of_matches => 'Anzahl der Spiele';
|
||||
|
||||
@override
|
||||
String get app_name => 'Game Tracker';
|
||||
|
||||
@override
|
||||
String get cancel => 'Abbrechen';
|
||||
@@ -30,7 +33,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get choose_ruleset => 'Regelwerk wählen';
|
||||
|
||||
@override
|
||||
String could_not_add_player(String playerName) {
|
||||
String could_not_add_player(Object playerName) {
|
||||
return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
|
||||
}
|
||||
|
||||
@@ -38,13 +41,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get create_group => 'Gruppe erstellen';
|
||||
|
||||
@override
|
||||
String get create_match => 'Match erstellen';
|
||||
String get create_match => 'Spiel erstellen';
|
||||
|
||||
@override
|
||||
String get create_new_group => 'Neue Gruppe erstellen';
|
||||
|
||||
@override
|
||||
String get create_new_match => 'Neues Match erstellen';
|
||||
String get create_new_match => 'Neues Spiel erstellen';
|
||||
|
||||
@override
|
||||
String get data_successfully_deleted => 'Daten erfolgreich gelöscht';
|
||||
@@ -88,9 +91,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get game_name => 'Spielvorlagenname';
|
||||
|
||||
@override
|
||||
String get game_tracker => 'Game Tracker';
|
||||
|
||||
@override
|
||||
String get group => 'Gruppe';
|
||||
|
||||
@@ -119,13 +119,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get least_points => 'Niedrigste Punkte';
|
||||
|
||||
@override
|
||||
String get match_in_progress => 'Match läuft...';
|
||||
String get match_in_progress => 'Spiel läuft...';
|
||||
|
||||
@override
|
||||
String get match_name => 'Matchname';
|
||||
String get match_name => 'Spieltitel';
|
||||
|
||||
@override
|
||||
String get matches => 'Matches';
|
||||
String get matches => 'Spiele';
|
||||
|
||||
@override
|
||||
String get menu => 'Menü';
|
||||
@@ -140,7 +140,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get no_groups_created_yet => 'Noch keine Gruppen erstellt';
|
||||
|
||||
@override
|
||||
String get no_matches_created_yet => 'Noch keine Matches erstellt';
|
||||
String get no_matches_created_yet => 'Noch keine Spiele erstellt';
|
||||
|
||||
@override
|
||||
String get no_players_created_yet => 'Noch keine Spieler:in erstellt';
|
||||
@@ -150,13 +150,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Keine Spieler:in mit diesem Namen gefunden';
|
||||
|
||||
@override
|
||||
String get no_players_selected => 'Keine Spieler:in ausgewählt';
|
||||
String get no_players_selected => 'Keine Spieler:innen ausgewählt';
|
||||
|
||||
@override
|
||||
String get no_recent_matches_available => 'Keine letzten Matches verfügbar';
|
||||
String get no_recent_matches_available => 'Keine letzten Spiele verfügbar';
|
||||
|
||||
@override
|
||||
String get no_second_match_available => 'Kein zweites Match verfügbar';
|
||||
String get no_second_match_available => 'Kein zweites Spiel verfügbar';
|
||||
|
||||
@override
|
||||
String get no_statistics_available => 'Keine Statistiken verfügbar';
|
||||
@@ -174,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get player_name => 'Spieler:innenname';
|
||||
|
||||
@override
|
||||
String get players => 'Spieler:in';
|
||||
String get players => 'Spieler:innen';
|
||||
|
||||
@override
|
||||
String players_count(int count) {
|
||||
@@ -185,7 +185,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get quick_create => 'Schnellzugriff';
|
||||
|
||||
@override
|
||||
String get recent_matches => 'Letzte Matches';
|
||||
String get recent_matches => 'Letzte Spiele';
|
||||
|
||||
@override
|
||||
String get ruleset => 'Regelwerk';
|
||||
@@ -216,14 +216,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get select_winner => 'Gewinner:in wählen:';
|
||||
|
||||
@override
|
||||
String selected_players(int count) {
|
||||
final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact(
|
||||
locale: localeName,
|
||||
);
|
||||
final String countString = countNumberFormat.format(count);
|
||||
|
||||
return 'Ausgewählte Spieler:in: $countString';
|
||||
}
|
||||
String get selected_players => 'Ausgewählte Spieler:innen';
|
||||
|
||||
@override
|
||||
String get settings => 'Einstellungen';
|
||||
@@ -254,9 +247,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Dies kann nicht rückgängig gemacht werden';
|
||||
|
||||
@override
|
||||
String today_at(String time) {
|
||||
return 'Heute um $time';
|
||||
}
|
||||
String get today_at => 'Heute um';
|
||||
|
||||
@override
|
||||
String get undo => 'Rückgängig';
|
||||
@@ -265,9 +256,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)';
|
||||
|
||||
@override
|
||||
String winner(Object winnerName) {
|
||||
return 'Gewinner:in: $winnerName';
|
||||
}
|
||||
String get winner => 'Gewinner:in';
|
||||
|
||||
@override
|
||||
String get winrate => 'Siegquote';
|
||||
@@ -276,7 +265,5 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get wins => 'Siege';
|
||||
|
||||
@override
|
||||
String yesterday_at(String time) {
|
||||
return 'Gestern um $time';
|
||||
}
|
||||
String get yesterday_at => 'Gestern um';
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get all_players => 'All players:';
|
||||
String get all_players => 'All players';
|
||||
|
||||
@override
|
||||
String get all_players_selected => 'All players selected';
|
||||
@@ -17,6 +17,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get amount_of_matches => 'Amount of Matches';
|
||||
|
||||
@override
|
||||
String get app_name => 'Game Tracker';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@@ -30,8 +33,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get choose_ruleset => 'Choose Ruleset';
|
||||
|
||||
@override
|
||||
String could_not_add_player(String playerName) {
|
||||
return 'Could not add player $playerName';
|
||||
String could_not_add_player(Object playerName) {
|
||||
return 'Could not add player';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -88,9 +91,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get game_name => 'Game Name';
|
||||
|
||||
@override
|
||||
String get game_tracker => 'Game Tracker';
|
||||
|
||||
@override
|
||||
String get group => 'Group';
|
||||
|
||||
@@ -216,14 +216,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get select_winner => 'Select Winner:';
|
||||
|
||||
@override
|
||||
String selected_players(int count) {
|
||||
final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact(
|
||||
locale: localeName,
|
||||
);
|
||||
final String countString = countNumberFormat.format(count);
|
||||
|
||||
return 'Selected players: $countString';
|
||||
}
|
||||
String get selected_players => 'Selected players';
|
||||
|
||||
@override
|
||||
String get settings => 'Settings';
|
||||
@@ -253,9 +246,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get this_cannot_be_undone => 'This can\'t be undone';
|
||||
|
||||
@override
|
||||
String today_at(String time) {
|
||||
return 'Today at $time';
|
||||
}
|
||||
String get today_at => 'Today at';
|
||||
|
||||
@override
|
||||
String get undo => 'Undo';
|
||||
@@ -264,9 +255,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get unknown_exception => 'Unknown Exception (see console)';
|
||||
|
||||
@override
|
||||
String winner(Object winnerName) {
|
||||
return 'Winner';
|
||||
}
|
||||
String get winner => 'Winner';
|
||||
|
||||
@override
|
||||
String get winrate => 'Winrate';
|
||||
@@ -275,7 +264,5 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get wins => 'Wins';
|
||||
|
||||
@override
|
||||
String yesterday_at(String time) {
|
||||
return 'Yesterday at $time';
|
||||
}
|
||||
String get yesterday_at => 'Yesterday at';
|
||||
}
|
||||
|
||||
@@ -34,21 +34,17 @@ class GameTracker extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
debugShowCheckedModeBanner: false,
|
||||
onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker,
|
||||
darkTheme: ThemeData.dark(),
|
||||
|
||||
onGenerateTitle: (context) => AppLocalizations.of(context).app_name,
|
||||
themeMode: ThemeMode.dark, // forces dark mode
|
||||
theme: ThemeData(
|
||||
primaryColor: CustomTheme.primaryColor,
|
||||
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||
appBarTheme: CustomTheme.appBarTheme,
|
||||
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: CustomTheme.primaryColor,
|
||||
brightness: Brightness.dark,
|
||||
).copyWith(surface: CustomTheme.backgroundColor),
|
||||
),
|
||||
|
||||
home: const CustomNavigationBar(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ class CustomNavigationBar extends StatefulWidget {
|
||||
|
||||
class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
/// Currently selected tab index
|
||||
int currentIndex = 0;
|
||||
|
||||
/// Key count to force rebuild of tab views
|
||||
int tabKeyCount = 0;
|
||||
|
||||
@override
|
||||
@@ -119,12 +122,14 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles tab tap events. Updates the current [index] state.
|
||||
void onTabTapped(int index) {
|
||||
setState(() {
|
||||
currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the title of the current tab based on [currentIndex].
|
||||
String _currentTabTitle(context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (currentIndex) {
|
||||
|
||||
@@ -18,15 +18,17 @@ class CreateGroupView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
final _groupNameController = TextEditingController();
|
||||
late final AppDatabase db;
|
||||
|
sneeex marked this conversation as resolved
|
||||
|
||||
/// Controller for the group name input field
|
||||
final _groupNameController = TextEditingController();
|
||||
|
||||
/// List of currently selected players
|
||||
List<Player> selectedPlayers = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_groupNameController.addListener(() {
|
||||
setState(() {});
|
||||
@@ -44,27 +46,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Text(
|
||||
loc.create_new_group,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
appBar: AppBar(title: Text(loc.create_new_group)),
|
||||
|
sneeex marked this conversation as resolved
sneeex
commented
schatz irgendwas passt hier nicht: warum hast du alles weggelassen außer den title??? das ganze design ist anders jetzt und kacke auch schatz irgendwas passt hier nicht:

warum hast du alles weggelassen außer den title??? das ganze design ist anders jetzt und kacke auch
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
margin: CustomTheme.standardMargin,
|
||||
child: TextInputField(
|
||||
controller: _groupNameController,
|
||||
hintText: loc.group_name,
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -109,7 +100,6 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -21,7 +21,11 @@ class GroupsView extends StatefulWidget {
|
||||
|
||||
class _GroupsViewState extends State<GroupsView> {
|
||||
late final AppDatabase db;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
hier ebenfalls kein comment? hier ebenfalls kein comment?
|
||||
|
||||
/// Loaded groups from the database
|
||||
late List<Group> loadedGroups;
|
||||
|
||||
/// Loading state
|
||||
bool isLoading = true;
|
||||
|
||||
List<Group> groups = List.filled(
|
||||
@@ -101,7 +105,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
void loadGroups() {
|
||||
Future.wait([
|
||||
db.groupDao.getAllGroups(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
Future.delayed(Constants.minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
loadedGroups = results[0] as List<Group>;
|
||||
setState(() {
|
||||
|
||||
@@ -21,9 +21,17 @@ class HomeView extends StatefulWidget {
|
||||
|
||||
class _HomeViewState extends State<HomeView> {
|
||||
bool isLoading = true;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
hier ebenfalls kein comment? hier ebenfalls kein comment?
|
||||
|
||||
/// Amount of matches in the database
|
||||
int matchCount = 0;
|
||||
|
||||
/// Amount of groups in the database
|
||||
int groupCount = 0;
|
||||
|
||||
/// Loaded recent matches from the database
|
||||
List<Match> loadedRecentMatches = [];
|
||||
|
||||
/// Recent matches to display, initially filled with skeleton matches
|
||||
List<Match> recentMatches = List.filled(
|
||||
2,
|
||||
Match(
|
||||
@@ -42,32 +50,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
Future.wait([
|
||||
db.matchDao.getMatchCount(),
|
||||
db.groupDao.getGroupCount(),
|
||||
db.matchDao.getAllMatches(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
matchCount = results[0] as int;
|
||||
groupCount = results[1] as int;
|
||||
loadedRecentMatches = results[2] as List<Match>;
|
||||
recentMatches =
|
||||
(loadedRecentMatches
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||
.take(2)
|
||||
.toList();
|
||||
if (loadedRecentMatches.length < 2) {
|
||||
recentMatches.add(
|
||||
Match(name: 'Dummy Match', winner: null, group: null, players: null),
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
loadHomeViewData();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -230,6 +213,40 @@ class _HomeViewState extends State<HomeView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads the data for the HomeView from the database.
|
||||
/// This includes the match count, group count, and recent matches.
|
||||
void loadHomeViewData() {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
Future.wait([
|
||||
db.matchDao.getMatchCount(),
|
||||
db.groupDao.getGroupCount(),
|
||||
db.matchDao.getAllMatches(),
|
||||
Future.delayed(Constants.minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
matchCount = results[0] as int;
|
||||
groupCount = results[1] as int;
|
||||
loadedRecentMatches = results[2] as List<Match>;
|
||||
recentMatches =
|
||||
(loadedRecentMatches
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||
.take(2)
|
||||
.toList();
|
||||
if (loadedRecentMatches.length < 2) {
|
||||
recentMatches.add(
|
||||
Match(name: 'Dummy Match', winner: null, group: null, players: null),
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Generates a text representation of the players in the match.
|
||||
/// If the match has a group, it returns the group name and the number of additional players.
|
||||
/// If there is no group, it returns the count of players.
|
||||
String _getPlayerText(Match game, context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
if (game.group == null) {
|
||||
|
||||
@@ -20,10 +20,12 @@ class ChooseGameView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
late int selectedGameIndex;
|
||||
|
||||
/// Controller for the search bar
|
||||
final TextEditingController searchBarController = TextEditingController();
|
||||
|
||||
/// Currently selected game index
|
||||
late int selectedGameIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedGameIndex = widget.initialGameIndex;
|
||||
@@ -36,19 +38,13 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(selectedGameIndex);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
loc.choose_game,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(loc.choose_game),
|
||||
),
|
||||
body: PopScope(
|
||||
// This fixes that the Android Back Gesture didn't return the
|
||||
|
||||
@@ -38,8 +38,6 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
@@ -52,11 +50,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
loc.choose_group,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(loc.choose_group),
|
||||
),
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
|
||||
body: PopScope(
|
||||
// This fixes that the Android Back Gesture didn't return the
|
||||
@@ -136,8 +130,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Filters the groups based on the search query.
|
||||
/// TODO: Maybe implement also targetting player names?
|
||||
/// Filters the groups based on the search [query].
|
||||
|
sneeex marked this conversation as resolved
sneeex
commented
warum query in klammern? entweder ganz oder garnicht aber wat soll dat hier warum query in klammern? entweder ganz oder garnicht aber wat soll dat hier
flixcoo
commented
Weil das der Input Parameter der Funktion ist Weil das der Input Parameter der Funktion ist
|
||||
void filterGroups(String query) {
|
||||
setState(() {
|
||||
if (query.isEmpty) {
|
||||
|
||||
@@ -19,6 +19,7 @@ class ChooseRulesetView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||
/// Currently selected ruleset index
|
||||
late int selectedRulesetIndex;
|
||||
|
||||
@override
|
||||
@@ -36,8 +37,6 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||
child: Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
|
||||
@@ -48,11 +47,7 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
loc.choose_ruleset,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(loc.choose_ruleset),
|
||||
),
|
||||
body: PopScope(
|
||||
// This fixes that the Android Back Gesture didn't return the
|
||||
|
||||
@@ -26,14 +26,13 @@ class CreateMatchView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Reference to the app database
|
||||
late final AppDatabase db;
|
||||
|
||||
/// Controller for the match name input field
|
||||
final TextEditingController _matchNameController = TextEditingController();
|
||||
|
||||
/// Hint text for the match name input field
|
||||
String hintText = 'Match Name';
|
||||
String? hintText;
|
||||
|
||||
/// List of all groups from the database
|
||||
List<Group> groupsList = [];
|
||||
@@ -68,6 +67,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// The currently selected players
|
||||
List<Player>? selectedPlayers;
|
||||
|
||||
/// List of available rulesets with their localized string representations
|
||||
late final List<(Ruleset, String)> _rulesets;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -89,9 +91,18 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
});
|
||||
}
|
||||
|
||||
List<(Ruleset, String)> _getRulesets(BuildContext context) {
|
||||
@override
|
||||
void dispose() {
|
||||
_matchNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final loc = AppLocalizations.of(context);
|
||||
return [
|
||||
hintText ??= loc.match_name;
|
||||
_rulesets = [
|
||||
(Ruleset.singleWinner, loc.ruleset_single_winner),
|
||||
(Ruleset.singleLoser, loc.ruleset_single_loser),
|
||||
(Ruleset.mostPoints, loc.ruleset_most_points),
|
||||
@@ -110,24 +121,16 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Text(
|
||||
loc.create_new_match,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
appBar: AppBar(title: Text(loc.create_new_match)),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
margin: CustomTheme.tileMargin,
|
||||
child: TextInputField(
|
||||
controller: _matchNameController,
|
||||
hintText: hintText,
|
||||
hintText: hintText ?? '',
|
||||
),
|
||||
),
|
||||
ChooseTile(
|
||||
@@ -148,11 +151,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
if (selectedGameIndex != -1) {
|
||||
hintText = games[selectedGameIndex].$1;
|
||||
selectedRuleset = games[selectedGameIndex].$3;
|
||||
selectedRulesetIndex = _getRulesets(
|
||||
context,
|
||||
).indexWhere((r) => r.$1 == selectedRuleset);
|
||||
selectedRulesetIndex = _rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
} else {
|
||||
hintText = 'Match Name';
|
||||
hintText = loc.match_name;
|
||||
selectedRuleset = null;
|
||||
}
|
||||
});
|
||||
@@ -164,17 +167,16 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
? loc.none
|
||||
: translateRulesetToString(selectedRuleset!, context),
|
||||
onPressed: () async {
|
||||
final rulesets = _getRulesets(context);
|
||||
selectedRuleset = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChooseRulesetView(
|
||||
rulesets: rulesets,
|
||||
rulesets: _rulesets,
|
||||
initialRulesetIndex: selectedRulesetIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!mounted) return;
|
||||
selectedRulesetIndex = rulesets.indexWhere(
|
||||
selectedRulesetIndex = _rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
selectedGameIndex = -1;
|
||||
@@ -228,7 +230,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
? () async {
|
||||
Match match = Match(
|
||||
name: _matchNameController.text.isEmpty
|
||||
? hintText
|
||||
? (hintText ?? '')
|
||||
: _matchNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
group: selectedGroup,
|
||||
@@ -256,11 +258,14 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Determines whether the "Create Game" button should be enabled based on
|
||||
/// the current state of the input fields.
|
||||
/// Determines whether the "Create Match" button should be enabled.
|
||||
///
|
||||
/// Returns `true` if:
|
||||
/// - A ruleset is selected AND
|
||||
/// - Either a group is selected OR at least 2 players are selected
|
||||
bool _enableCreateGameButton() {
|
||||
return selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1) &&
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1)) &&
|
||||
selectedRuleset != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ class MatchResultView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MatchResultViewState extends State<MatchResultView> {
|
||||
late final List<Player> allPlayers;
|
||||
late final AppDatabase db;
|
||||
|
||||
/// List of all players who participated in the match
|
||||
late final List<Player> allPlayers;
|
||||
|
||||
/// Currently selected winner player
|
||||
Player? _selectedPlayer;
|
||||
|
||||
@override
|
||||
@@ -47,17 +51,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Text(
|
||||
widget.match.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(widget.match.name),
|
||||
),
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
@@ -132,6 +126,8 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles saving or removing the winner in the database
|
||||
/// based on the current selection.
|
||||
Future<void> _handleWinnerSaving() async {
|
||||
if (_selectedPlayer == null) {
|
||||
await db.matchDao.removeWinner(matchId: widget.match.id);
|
||||
@@ -144,6 +140,10 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
/// Retrieves all players associated with the given [match].
|
||||
/// This includes players directly assigned to the match
|
||||
/// as well as members of the group (if any).
|
||||
/// The returned list is sorted alphabetically by player name.
|
||||
List<Player> getAllPlayers(Match match) {
|
||||
List<Player> players = [];
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ class _MatchViewState extends State<MatchView> {
|
||||
late final AppDatabase db;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
kein comment? kein comment?
|
||||
bool isLoading = true;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
kein comment? kein comment?
|
||||
|
||||
/// Loaded matches from the database,
|
||||
/// initially filled with skeleton matches
|
||||
List<Match> matches = List.filled(
|
||||
4,
|
||||
Match(
|
||||
@@ -44,7 +46,6 @@ class _MatchViewState extends State<MatchView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
loadGames();
|
||||
}
|
||||
@@ -117,10 +118,11 @@ class _MatchViewState extends State<MatchView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads the games from the database and sorts them by creation date.
|
||||
void loadGames() {
|
||||
Future.wait([
|
||||
db.matchDao.getAllMatches(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
Future.delayed(Constants.minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
||||
@@ -25,28 +25,7 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
Future.wait([
|
||||
db.matchDao.getAllMatches(),
|
||||
db.playerDao.getAllPlayers(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) async {
|
||||
if (!mounted) return;
|
||||
final matches = results[0] as List<Match>;
|
||||
final players = results[1] as List<Player>;
|
||||
winCounts = _calculateWinsForAllPlayers(matches, players, context);
|
||||
matchCounts = _calculateMatchAmountsForAllPlayers(
|
||||
matches,
|
||||
players,
|
||||
context,
|
||||
);
|
||||
winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
});
|
||||
loadStatisticData();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -78,7 +57,7 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
width: constraints.maxWidth * 0.95,
|
||||
values: winCounts,
|
||||
itemCount: 3,
|
||||
barColor: Colors.blue,
|
||||
barColor: Colors.green,
|
||||
),
|
||||
SizedBox(height: constraints.maxHeight * 0.02),
|
||||
StatisticsTile(
|
||||
@@ -96,7 +75,7 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
width: constraints.maxWidth * 0.95,
|
||||
values: matchCounts,
|
||||
itemCount: 10,
|
||||
barColor: Colors.green,
|
||||
barColor: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -118,13 +97,43 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads matches and players from the database
|
||||
/// and calculates statistics for each player
|
||||
void loadStatisticData() {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
Future.wait([
|
||||
db.matchDao.getAllMatches(),
|
||||
db.playerDao.getAllPlayers(),
|
||||
Future.delayed(Constants.minimumSkeletonDuration),
|
||||
]).then((results) async {
|
||||
if (!mounted) return;
|
||||
final matches = results[0] as List<Match>;
|
||||
final players = results[1] as List<Player>;
|
||||
winCounts = _calculateWinsForAllPlayers(
|
||||
matches: matches,
|
||||
players: players,
|
||||
context: context,
|
||||
);
|
||||
matchCounts = _calculateMatchAmountsForAllPlayers(
|
||||
matches: matches,
|
||||
players: players,
|
||||
context: context,
|
||||
);
|
||||
winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Calculates the number of wins for each player
|
||||
/// and returns a sorted list of tuples (playerName, winCount)
|
||||
List<(String, int)> _calculateWinsForAllPlayers(
|
||||
List<Match> matches,
|
||||
List<Player> players,
|
||||
BuildContext context,
|
||||
) {
|
||||
List<(String, int)> _calculateWinsForAllPlayers({
|
||||
required List<Match> matches,
|
||||
required List<Player> players,
|
||||
required BuildContext context,
|
||||
}) {
|
||||
List<(String, int)> winCounts = [];
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
@@ -169,11 +178,11 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
|
||||
/// Calculates the number of matches played for each player
|
||||
/// and returns a sorted list of tuples (playerName, matchCount)
|
||||
List<(String, int)> _calculateMatchAmountsForAllPlayers(
|
||||
List<Match> matches,
|
||||
List<Player> players,
|
||||
BuildContext context,
|
||||
) {
|
||||
List<(String, int)> _calculateMatchAmountsForAllPlayers({
|
||||
required List<Match> matches,
|
||||
required List<Player> players,
|
||||
required BuildContext context,
|
||||
}) {
|
||||
List<(String, int)> matchCounts = [];
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
/// A widget that provides a skeleton loading effect to its child widget tree.
|
||||
/// - [child]: The widget tree to apply the skeleton effect to.
|
||||
/// - [enabled]: A boolean to enable or disable the skeleton effect.
|
||||
/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher.
|
||||
class AppSkeleton extends StatefulWidget {
|
||||
final Widget child;
|
||||
final bool enabled;
|
||||
final bool fixLayoutBuilder;
|
||||
|
||||
const AppSkeleton({
|
||||
super.key,
|
||||
required this.child,
|
||||
@@ -13,6 +13,15 @@ class AppSkeleton extends StatefulWidget {
|
||||
this.fixLayoutBuilder = false,
|
||||
});
|
||||
|
||||
/// The widget tree to apply the skeleton effect to.
|
||||
final Widget child;
|
||||
|
||||
/// A boolean to enable or disable the skeleton effect.
|
||||
final bool enabled;
|
||||
|
||||
/// A boolean to fix the layout builder for AnimatedSwitcher.
|
||||
final bool fixLayoutBuilder;
|
||||
|
||||
@override
|
||||
State<AppSkeleton> createState() => _AppSkeletonState();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
|
||||
/// A custom button widget that is designed to have a width relative to the screen size.
|
||||
/// It supports three types of buttons: primary, secondary, and text buttons.
|
||||
/// - [text]: The text to display on the button.
|
||||
/// - [buttonType]: The type of button to display. Defaults to [ButtonType.primary].
|
||||
/// - [sizeRelativeToWidth]: The size of the button relative to the width of the screen.
|
||||
/// - [onPressed]: The callback to be invoked when the button is pressed.
|
||||
class CustomWidthButton extends StatelessWidget {
|
||||
const CustomWidthButton({
|
||||
super.key,
|
||||
@@ -11,9 +17,16 @@ class CustomWidthButton extends StatelessWidget {
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
/// The text to display on the button.
|
||||
final String text;
|
||||
|
||||
/// The size of the button relative to the width of the screen.
|
||||
final double sizeRelativeToWidth;
|
||||
|
||||
/// The callback to be invoked when the button is pressed.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// The type of button to display. Depends on the enum [ButtonType].
|
||||
final ButtonType buttonType;
|
||||
|
||||
@override
|
||||
@@ -47,7 +60,7 @@ class CustomWidthButton extends StatelessWidget {
|
||||
60,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
@@ -78,7 +91,7 @@ class CustomWidthButton extends StatelessWidget {
|
||||
),
|
||||
side: BorderSide(color: borderSideColor, width: 2),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A button widget designed for quick creating matches in the [HomeView]
|
||||
/// - [text]: The text to display on the button.
|
||||
/// - [onPressed]: The callback to be invoked when the button is pressed.
|
||||
class QuickCreateButton extends StatefulWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
const QuickCreateButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
/// The text to display on the button.
|
||||
final String text;
|
||||
|
||||
/// The callback to be invoked when the button is pressed.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
State<QuickCreateButton> createState() => _QuickCreateButtonState();
|
||||
}
|
||||
@@ -22,7 +29,9 @@ class _QuickCreateButtonState extends State<QuickCreateButton> {
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(140, 45),
|
||||
backgroundColor: CustomTheme.primaryColor,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
widget.text,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A navigation bar item widget that represents a single tab in a navigation bar.
|
||||
/// - [index]: The index of the tab.
|
||||
/// - [isSelected]: A boolean indicating whether the tab is currently selected.
|
||||
/// - [icon]: The icon to display for the tab.
|
||||
/// - [label]: The label to display for the tab.
|
||||
/// - [onTabTapped]: The callback to be invoked when the tab is tapped.
|
||||
class NavbarItem extends StatefulWidget {
|
||||
final int index;
|
||||
final bool isSelected;
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final Function(int) onTabTapped;
|
||||
|
||||
const NavbarItem({
|
||||
super.key,
|
||||
required this.index,
|
||||
@@ -16,6 +16,21 @@ class NavbarItem extends StatefulWidget {
|
||||
required this.onTabTapped,
|
||||
});
|
||||
|
||||
/// The index of the tab.
|
||||
final int index;
|
||||
|
||||
/// A boolean indicating whether the tab is currently selected.
|
||||
final bool isSelected;
|
||||
|
||||
/// The icon to display for the tab.
|
||||
final IconData icon;
|
||||
|
||||
/// The label to display for the tab.
|
||||
final String label;
|
||||
|
||||
/// The callback to be invoked when the tab is tapped.
|
||||
final Function(int) onTabTapped;
|
||||
|
||||
@override
|
||||
State<NavbarItem> createState() => _NavbarItemState();
|
||||
}
|
||||
|
||||
@@ -11,31 +11,55 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// A widget that allows users to select players from a list,
|
||||
/// with search functionality and the ability to add new players.
|
||||
/// - [availablePlayers]: An optional list of players to choose from. If null, all
|
||||
/// players from the database are used.
|
||||
/// - [initialSelectedPlayers]: An optional list of players that should be pre-selected.
|
||||
/// - [onChanged]: A callback function that is invoked whenever the selection changes,
|
||||
/// providing the updated list of selected players.
|
||||
class PlayerSelection extends StatefulWidget {
|
||||
final Function(List<Player> value) onChanged;
|
||||
final List<Player>? availablePlayers;
|
||||
final List<Player>? initialSelectedPlayers;
|
||||
|
||||
const PlayerSelection({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.availablePlayers,
|
||||
this.initialSelectedPlayers,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
/// An optional list of players to choose from. If null, all players from the database are used.
|
||||
final List<Player>? availablePlayers;
|
||||
|
||||
/// An optional list of players that should be pre-selected.
|
||||
final List<Player>? initialSelectedPlayers;
|
||||
|
||||
/// A callback function that is invoked whenever the selection changes,
|
||||
final Function(List<Player> value) onChanged;
|
||||
|
||||
@override
|
||||
State<PlayerSelection> createState() => _PlayerSelectionState();
|
||||
}
|
||||
|
||||
class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
List<Player> selectedPlayers = [];
|
||||
List<Player> suggestedPlayers = [];
|
||||
List<Player> allPlayers = [];
|
||||
late final AppDatabase db;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
kein comment? kein comment?
|
||||
bool isLoading = true;
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
kein comment? kein comment?
|
||||
|
||||
/// Future that loads all players from the database.
|
||||
late Future<List<Player>> _allPlayersFuture;
|
||||
|
||||
/// The complete list of all available players.
|
||||
List<Player> allPlayers = [];
|
||||
|
||||
/// The list of players suggested based on the search input.
|
||||
List<Player> suggestedPlayers = [];
|
||||
|
||||
/// The list of currently selected players.
|
||||
List<Player> selectedPlayers = [];
|
||||
|
||||
/// Controller for the search bar input.
|
||||
late final TextEditingController _searchBarController =
|
||||
TextEditingController();
|
||||
late final AppDatabase db;
|
||||
late Future<List<Player>> _allPlayersFuture;
|
||||
|
||||
/// Skeleton data used while loading players.
|
||||
late final List<Player> skeletonData = List.filled(
|
||||
7,
|
||||
Player(name: 'Player 0'),
|
||||
@@ -49,47 +73,11 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
loadPlayerList();
|
||||
}
|
||||
|
||||
void loadPlayerList() {
|
||||
_allPlayersFuture = Future.wait([
|
||||
db.playerDao.getAllPlayers(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) => results[0] as List<Player>);
|
||||
if (mounted) {
|
||||
_allPlayersFuture.then((loadedPlayers) {
|
||||
setState(() {
|
||||
// If a list of available players is provided (even if empty), use that list.
|
||||
if (widget.availablePlayers != null) {
|
||||
widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...widget.availablePlayers!];
|
||||
suggestedPlayers = [...allPlayers];
|
||||
|
||||
if (widget.initialSelectedPlayers != null) {
|
||||
// Ensures that only players available for selection are pre-selected.
|
||||
selectedPlayers = widget.initialSelectedPlayers!
|
||||
.where(
|
||||
(p) => widget.availablePlayers!.any(
|
||||
(available) => available.id == p.id,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
} else {
|
||||
// Otherwise, use the loaded players from the database.
|
||||
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...loadedPlayers];
|
||||
suggestedPlayers = [...loadedPlayers];
|
||||
}
|
||||
isLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
margin: CustomTheme.standardMargin,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
decoration: CustomTheme.standardBoxDecoration,
|
||||
child: Column(
|
||||
@@ -131,9 +119,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).selected_players(selectedPlayers.length),
|
||||
loc.selected_players,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
@@ -227,51 +213,95 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads the list of players from the database or uses the provided available players.
|
||||
/// Sets the loading state and updates the player lists accordingly.
|
||||
void loadPlayerList() {
|
||||
_allPlayersFuture = Future.wait([
|
||||
db.playerDao.getAllPlayers(),
|
||||
Future.delayed(Constants.minimumSkeletonDuration),
|
||||
]).then((results) => results[0] as List<Player>);
|
||||
if (mounted) {
|
||||
_allPlayersFuture.then((loadedPlayers) {
|
||||
setState(() {
|
||||
// If a list of available players is provided (even if empty), use that list.
|
||||
if (widget.availablePlayers != null) {
|
||||
widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...widget.availablePlayers!];
|
||||
suggestedPlayers = [...allPlayers];
|
||||
|
||||
if (widget.initialSelectedPlayers != null) {
|
||||
// Ensures that only players available for selection are pre-selected.
|
||||
selectedPlayers = widget.initialSelectedPlayers!
|
||||
.where(
|
||||
(p) => widget.availablePlayers!.any(
|
||||
(available) => available.id == p.id,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
} else {
|
||||
// Otherwise, use the loaded players from the database.
|
||||
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...loadedPlayers];
|
||||
suggestedPlayers = [...loadedPlayers];
|
||||
}
|
||||
isLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new player to the database from the search bar input.
|
||||
/// Shows a snackbar indicating success or failure.
|
||||
/// [context] - BuildContext to show the snackbar.
|
||||
void addNewPlayerFromSearch({required BuildContext context}) async {
|
||||
Future<void> addNewPlayerFromSearch({required BuildContext context}) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
String playerName = _searchBarController.text.trim();
|
||||
Player createdPlayer = Player(name: playerName);
|
||||
bool success = await db.playerDao.addPlayer(player: createdPlayer);
|
||||
final playerName = _searchBarController.text.trim();
|
||||
|
||||
final createdPlayer = Player(name: playerName);
|
||||
final success = await db.playerDao.addPlayer(player: createdPlayer);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (success) {
|
||||
selectedPlayers.insert(0, createdPlayer);
|
||||
_handleSuccessfulPlayerCreation(createdPlayer);
|
||||
showSnackBarMessage(loc.successfully_added_player(playerName));
|
||||
} else {
|
||||
showSnackBarMessage(loc.could_not_add_player(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the state after successfully adding a new player.
|
||||
void _handleSuccessfulPlayerCreation(Player player) {
|
||||
selectedPlayers.insert(0, player);
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
allPlayers.add(createdPlayer);
|
||||
allPlayers.add(player);
|
||||
|
||||
setState(() {
|
||||
_searchBarController.clear();
|
||||
suggestedPlayers = allPlayers.where((player) {
|
||||
return !selectedPlayers.contains(player);
|
||||
}).toList();
|
||||
_updateSuggestedPlayers();
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: CustomTheme.boxColor,
|
||||
content: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).successfully_added_player(playerName),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: CustomTheme.boxColor,
|
||||
content: Center(
|
||||
child: Text(
|
||||
loc.could_not_add_player(playerName),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates the suggested players list based on current selection.
|
||||
void _updateSuggestedPlayers() {
|
||||
suggestedPlayers = allPlayers
|
||||
.where((player) => !selectedPlayers.contains(player))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Displays a snackbar message at the bottom of the screen.
|
||||
/// [message] - The message to display in the snackbar.
|
||||
void showSnackBarMessage(String message) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: CustomTheme.boxColor,
|
||||
content: Center(
|
||||
child: Text(message, style: const TextStyle(color: Colors.white)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Determines the appropriate info text to display when no players
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A custom search bar widget that encapsulates a [SearchBar] with additional customization options.
|
||||
/// - [controller]: The controller for the search bar's text input.
|
||||
/// - [hintText]: The hint text displayed in the search bar when it is empty.
|
||||
/// - [trailingButtonShown]: Whether to show the trailing button.
|
||||
/// - [trailingButtonicon]: The icon for the trailing button.
|
||||
/// - [trailingButtonEnabled]: Whether the trailing button is in enabled state.
|
||||
/// - [onTrailingButtonPressed]: The callback invoked when the trailing button is pressed.
|
||||
/// - [onChanged]: The callback invoked when the text in the search bar changes.
|
||||
/// - [constraints]: The constraints for the search bar.
|
||||
class CustomSearchBar extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final BoxConstraints? constraints;
|
||||
final bool trailingButtonShown;
|
||||
final bool trailingButtonEnabled;
|
||||
final VoidCallback? onTrailingButtonPressed;
|
||||
final IconData trailingButtonicon;
|
||||
|
||||
const CustomSearchBar({
|
||||
super.key,
|
||||
required this.controller,
|
||||
@@ -23,6 +23,30 @@ class CustomSearchBar extends StatelessWidget {
|
||||
this.constraints,
|
||||
});
|
||||
|
||||
/// The controller for the search bar's text input.
|
||||
final TextEditingController controller;
|
||||
|
||||
/// The hint text displayed in the search bar when it is empty.
|
||||
final String hintText;
|
||||
|
||||
/// Whether to show the trailing button.
|
||||
final bool trailingButtonShown;
|
||||
|
||||
/// The icon for the trailing button.
|
||||
final IconData trailingButtonicon;
|
||||
|
||||
/// Whether the trailing button is in enabled state.
|
||||
final bool trailingButtonEnabled;
|
||||
|
||||
/// The callback invoked when the trailing button is pressed.
|
||||
final VoidCallback? onTrailingButtonPressed;
|
||||
|
||||
/// The callback invoked when the text in the search bar changes.
|
||||
final ValueChanged<String>? onChanged;
|
||||
|
||||
/// The constraints for the search bar.
|
||||
final BoxConstraints? constraints;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SearchBar(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A custom text input field widget that encapsulates a [TextField] with specific styling.
|
||||
/// - [controller]: The controller for the text input field.
|
||||
/// - [onChanged]: The callback invoked when the text in the field changes.
|
||||
/// - [hintText]: The hint text displayed in the text input field when it is empty
|
||||
class TextInputField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final String hintText;
|
||||
|
||||
const TextInputField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
@@ -13,6 +13,15 @@ class TextInputField extends StatelessWidget {
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
/// The controller for the text input field.
|
||||
final TextEditingController controller;
|
||||
|
||||
/// The callback invoked when the text in the field changes.
|
||||
final ValueChanged<String>? onChanged;
|
||||
|
||||
/// The hint text displayed in the text input field when it is empty.
|
||||
final String hintText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A tile widget that allows users to choose an option by tapping on it.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [trailingText]: Optional trailing text displayed on the tile.
|
||||
/// - [onPressed]: The callback invoked when the tile is tapped.
|
||||
class ChooseTile extends StatefulWidget {
|
||||
final String title;
|
||||
final VoidCallback? onPressed;
|
||||
final String? trailingText;
|
||||
const ChooseTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
@@ -12,6 +13,15 @@ class ChooseTile extends StatefulWidget {
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
/// The title text displayed on the tile.
|
||||
final String title;
|
||||
|
||||
/// The callback invoked when the tile is tapped.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Optional trailing text displayed on the tile.
|
||||
final String? trailingText;
|
||||
|
||||
@override
|
||||
State<ChooseTile> createState() => _ChooseTileState();
|
||||
}
|
||||
@@ -22,8 +32,8 @@ class _ChooseTileState extends State<ChooseTile> {
|
||||
return GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
margin: CustomTheme.tileMargin,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: CustomTheme.standardBoxDecoration,
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality.
|
||||
/// - [text]: The text to display next to the radio button.
|
||||
/// - [value]: The value associated with the radio button.
|
||||
/// - [onContainerTap]: The callback invoked when the container is tapped.
|
||||
class CustomRadioListTile<T> extends StatelessWidget {
|
||||
final String text;
|
||||
final T value;
|
||||
final ValueChanged<T> onContainerTap;
|
||||
|
||||
const CustomRadioListTile({
|
||||
super.key,
|
||||
required this.text,
|
||||
@@ -13,6 +13,15 @@ class CustomRadioListTile<T> extends StatelessWidget {
|
||||
required this.onContainerTap,
|
||||
});
|
||||
|
||||
/// The text to display next to the radio button.
|
||||
final String text;
|
||||
|
||||
/// The value associated with the radio button.
|
||||
final T value;
|
||||
|
||||
/// The callback invoked when the container is tapped.
|
||||
final ValueChanged<T> onContainerTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@@ -23,7 +32,7 @@ class CustomRadioListTile<T> extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -3,16 +3,22 @@ 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';
|
||||
|
||||
/// 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.
|
||||
class GroupTile extends StatelessWidget {
|
||||
const GroupTile({super.key, required this.group, this.isHighlighted = false});
|
||||
|
||||
/// The group data to be displayed.
|
||||
final Group group;
|
||||
|
||||
/// Whether the tile should be highlighted.
|
||||
final bool isHighlighted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
margin: CustomTheme.standardMargin,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||
decoration: isHighlighted
|
||||
? CustomTheme.highlightedBoxDecoration
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A tile widget that displays a title with an icon and some content below it.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [icon]: The icon displayed next to the title.
|
||||
/// - [content]: The content widget displayed below the title.
|
||||
/// - [padding]: Optional padding for the tile content.
|
||||
/// - [height]: Optional height for the tile.
|
||||
/// - [width]: Optional width for the tile.
|
||||
class InfoTile extends StatefulWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final Widget content;
|
||||
final EdgeInsets? padding;
|
||||
final double? height;
|
||||
final double? width;
|
||||
const InfoTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
@@ -18,6 +19,24 @@ class InfoTile extends StatefulWidget {
|
||||
this.width,
|
||||
});
|
||||
|
||||
/// The title text displayed on the tile.
|
||||
final String title;
|
||||
|
||||
/// The icon displayed next to the title.
|
||||
final IconData icon;
|
||||
|
||||
/// The content widget displayed below the title.
|
||||
final Widget content;
|
||||
|
||||
/// Optional padding for the tile content.
|
||||
final EdgeInsets? padding;
|
||||
|
||||
/// Optional height for the tile.
|
||||
final double? height;
|
||||
|
||||
/// Optional width for the tile.
|
||||
final double? width;
|
||||
|
||||
@override
|
||||
State<InfoTile> createState() => _InfoTileState();
|
||||
}
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// A tile widget that displays information about a match, including its name,
|
||||
/// creation date, associated group, winner, and players.
|
||||
/// - [match]: The match data to be displayed.
|
||||
/// - [onTap]: The callback invoked when the tile is tapped.
|
||||
class MatchTile extends StatefulWidget {
|
||||
final Match match;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const MatchTile({super.key, required this.match, required this.onTap});
|
||||
|
||||
/// The match data to be displayed.
|
||||
final Match match;
|
||||
|
||||
/// The callback invoked when the tile is tapped.
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
State<MatchTile> createState() => _MatchTileState();
|
||||
}
|
||||
|
||||
class _MatchTileState extends State<MatchTile> {
|
||||
late final List<Player> _allPlayers;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_allPlayers = _getCombinedPlayers();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final group = widget.match.group;
|
||||
final winner = widget.match.winner;
|
||||
final allPlayers = _getAllPlayers();
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: CustomTheme.tileMargin,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
@@ -103,7 +118,7 @@ class _MatchTileState extends State<MatchTile> {
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -114,7 +129,7 @@ class _MatchTileState extends State<MatchTile> {
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
if (allPlayers.isNotEmpty) ...[
|
||||
if (_allPlayers.isNotEmpty) ...[
|
||||
Text(
|
||||
loc.players,
|
||||
style: const TextStyle(
|
||||
@@ -127,7 +142,7 @@ class _MatchTileState extends State<MatchTile> {
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: allPlayers.map((player) {
|
||||
children: _allPlayers.map((player) {
|
||||
return TextIconTile(text: player.name, iconEnabled: false);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -138,19 +153,17 @@ class _MatchTileState extends State<MatchTile> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats the given [dateTime] into a human-readable string based on its
|
||||
/// difference from the current date.
|
||||
String _formatDate(DateTime dateTime, BuildContext context) {
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(dateTime);
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
if (difference.inDays == 0) {
|
||||
return AppLocalizations.of(
|
||||
context,
|
||||
).today_at(DateFormat('HH:mm').format(dateTime));
|
||||
return "${loc.today_at} ${DateFormat('HH:mm').format(dateTime)}";
|
||||
} else if (difference.inDays == 1) {
|
||||
return AppLocalizations.of(
|
||||
context,
|
||||
).yesterday_at(DateFormat('HH:mm').format(dateTime));
|
||||
return "${loc.yesterday_at} ${DateFormat('HH:mm').format(dateTime)}";
|
||||
} else if (difference.inDays < 7) {
|
||||
return loc.days_ago(difference.inDays);
|
||||
} else {
|
||||
@@ -158,8 +171,10 @@ class _MatchTileState extends State<MatchTile> {
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> _getAllPlayers() {
|
||||
final allPlayers = <dynamic>[];
|
||||
/// Retrieves all unique players associated with the match,
|
||||
/// combining players from both the match and its group.
|
||||
List<Player> _getCombinedPlayers() {
|
||||
final allPlayers = <Player>[];
|
||||
final playerIds = <String>{};
|
||||
|
||||
// Add players from game.players
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A tile widget that displays a title with an icon and a numeric value below it.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [icon]: The icon displayed next to the title.
|
||||
/// - [value]: The numeric value displayed below the title.
|
||||
/// - [height]: Optional height for the tile.
|
||||
/// - [width]: Optional width for the tile.
|
||||
/// - [padding]: Optional padding for the tile content.
|
||||
class QuickInfoTile extends StatefulWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final int value;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final EdgeInsets? padding;
|
||||
const QuickInfoTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
@@ -18,6 +19,24 @@ class QuickInfoTile extends StatefulWidget {
|
||||
this.padding,
|
||||
});
|
||||
|
||||
/// The title text displayed on the tile.
|
||||
final String title;
|
||||
|
||||
/// The icon displayed next to the title.
|
||||
final IconData icon;
|
||||
|
||||
/// The numeric value displayed below the title.
|
||||
final int value;
|
||||
|
||||
/// Optional height for the tile.
|
||||
final double? height;
|
||||
|
||||
/// Optional width for the tile.
|
||||
final double? width;
|
||||
|
||||
/// Optional padding for the tile content.
|
||||
final EdgeInsets? padding;
|
||||
|
||||
@override
|
||||
State<QuickInfoTile> createState() => _QuickInfoTileState();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget.
|
||||
/// - [icon]: The icon displayed on the left side of the tile.
|
||||
/// - [title]: The title text displayed next to the icon.
|
||||
/// - [suffixWidget]: An optional widget displayed on the right side of the tile.
|
||||
/// - [onPressed]: The callback invoked when the tile is tapped.
|
||||
class SettingsListTile extends StatelessWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Widget? suffixWidget;
|
||||
const SettingsListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.suffixWidget,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
/// The icon displayed on the left side of the tile.
|
||||
final IconData icon;
|
||||
|
||||
/// The title text displayed next to the icon.
|
||||
final String title;
|
||||
|
||||
/// An optional widget displayed on the right side of the tile.
|
||||
final Widget? suffixWidget;
|
||||
|
||||
/// The callback invoked when the tile is tapped.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
|
||||
@@ -4,6 +4,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||
|
||||
/// A tile widget that displays statistical data using horizontal bars.
|
||||
/// - [icon]: The icon displayed next to the title.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [width]: The width of the tile.
|
||||
/// - [values]: A list of tuples containing labels and their corresponding numeric values.
|
||||
/// - [itemCount]: The maximum number of items to display.
|
||||
/// - [barColor]: The color of the bars representing the values.
|
||||
class StatisticsTile extends StatelessWidget {
|
||||
const StatisticsTile({
|
||||
super.key,
|
||||
@@ -15,16 +22,26 @@ class StatisticsTile extends StatelessWidget {
|
||||
required this.barColor,
|
||||
});
|
||||
|
||||
/// The icon displayed next to the title.
|
||||
final IconData icon;
|
||||
|
||||
/// The title text displayed on the tile.
|
||||
final String title;
|
||||
|
||||
/// The width of the tile.
|
||||
final double width;
|
||||
|
||||
/// A list of tuples containing labels and their corresponding numeric values.
|
||||
final List<(String, num)> values;
|
||||
|
||||
/// The maximum number of items to display.
|
||||
final int itemCount;
|
||||
|
||||
/// The color of the bars representing the values.
|
||||
final Color barColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maxBarWidth = MediaQuery.of(context).size.width * 0.65;
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return InfoTile(
|
||||
@@ -39,7 +56,10 @@ class StatisticsTile extends StatelessWidget {
|
||||
heightFactor: 4,
|
||||
child: Text(loc.no_data_available),
|
||||
),
|
||||
child: Column(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxBarWidth = constraints.maxWidth * 0.65;
|
||||
return Column(
|
||||
children: List.generate(min(values.length, itemCount), (index) {
|
||||
/// The maximum wins among all players
|
||||
final maxMatches = values.isNotEmpty ? values[0].$2 : 0;
|
||||
@@ -96,6 +116,8 @@ class StatisticsTile extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A list tile widget that displays text with an optional icon button.
|
||||
/// - [text]: The text to display in the tile.
|
||||
/// - [onPressed]: The callback to be invoked when the icon is pressed.
|
||||
/// - [iconEnabled]: A boolean to determine if the icon should be displayed.
|
||||
class TextIconListTile extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final bool iconEnabled;
|
||||
|
||||
const TextIconListTile({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.iconEnabled = true,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
/// The text to display in the tile.
|
||||
final String text;
|
||||
|
||||
/// A boolean to determine if the icon should be displayed.
|
||||
final bool iconEnabled;
|
||||
|
||||
/// The callback to be invoked when the icon is pressed.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A tile widget that displays text with an optional icon that can be tapped.
|
||||
/// - [text]: The text to display in the tile.
|
||||
/// - [iconEnabled]: A boolean to determine if the icon should be displayed.
|
||||
/// - [onIconTap]: The callback to be invoked when the icon is tapped.
|
||||
class TextIconTile extends StatelessWidget {
|
||||
final String text;
|
||||
final bool iconEnabled;
|
||||
final VoidCallback? onIconTap;
|
||||
|
||||
const TextIconTile({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onIconTap,
|
||||
this.iconEnabled = true,
|
||||
this.onIconTap,
|
||||
});
|
||||
|
||||
/// The text to display in the tile.
|
||||
final String text;
|
||||
|
||||
/// A boolean to determine if the icon should be displayed.
|
||||
final bool iconEnabled;
|
||||
|
||||
/// The callback to be invoked when the icon is tapped.
|
||||
final VoidCallback? onIconTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
/// A list tile widget that displays a title and description, with optional highlighting and badge.
|
||||
/// - [title]: The title text displayed on the tile.
|
||||
/// - [description]: The description text displayed below the title.
|
||||
/// - [onPressed]: The callback invoked when the tile is tapped.
|
||||
/// - [isHighlighted]: A boolean to determine if the tile should be highlighted.
|
||||
/// - [badgeText]: Optional text to display in a badge on the right side of the title.
|
||||
/// - [badgeColor]: Optional color for the badge background.
|
||||
class TitleDescriptionListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isHighlighted;
|
||||
final String? badgeText;
|
||||
final Color? badgeColor;
|
||||
|
||||
const TitleDescriptionListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
@@ -19,6 +19,24 @@ class TitleDescriptionListTile extends StatelessWidget {
|
||||
this.badgeColor,
|
||||
});
|
||||
|
||||
/// The title text displayed on the tile.
|
||||
final String title;
|
||||
|
||||
/// The description text displayed below the title.
|
||||
final String description;
|
||||
|
||||
/// The callback invoked when the tile is tapped.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// A boolean to determine if the tile should be highlighted.
|
||||
final bool isHighlighted;
|
||||
|
||||
/// Optional text to display in a badge on the right side of the title.
|
||||
final String? badgeText;
|
||||
|
||||
/// Optional color for the badge background.
|
||||
final Color? badgeColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget that displays a message centered at the top of the screen with an icon, title, and message.
|
||||
/// - [icon]: The icon to display above the title.
|
||||
/// - [title]: The title text to display.
|
||||
/// - [message]: The message text to display below the title.
|
||||
class TopCenteredMessage extends StatelessWidget {
|
||||
const TopCenteredMessage({
|
||||
super.key,
|
||||
@@ -8,10 +12,15 @@ class TopCenteredMessage extends StatelessWidget {
|
||||
required this.message,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String message;
|
||||
/// The icon to display above the title.
|
||||
final IconData icon;
|
||||
|
||||
/// The title text to display.
|
||||
final String title;
|
||||
|
||||
/// The message text to display below the title.
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
||||
Reference in New Issue
Block a user
warum für den rest comments aber nicht für db?
Habe Datenbank und
isLoadingnicht kommentiert, weil die so allgegenwärtig sind, dass die eigentlich keinen kommentar brauchen. Soll ich einen hinzufügen?