1 Commits

Author SHA1 Message Date
799b7d8403 Implemented new nav bar with selected animation
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m3s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s
2026-01-09 21:12:09 +01:00
30 changed files with 716 additions and 8511 deletions

27
.gitignore vendored
View File

@@ -150,9 +150,25 @@ app.*.symbols
.gclient_previous_custom_vars .gclient_previous_custom_vars
.gclient_previous_sync_commits .gclient_previous_sync_commits
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/ .swiftpm/
migrate_working_dir/ migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
@@ -160,8 +176,17 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/ /build/
# Symbolication related
app.*.symbols
# Obfuscation related # Obfuscation related
app.*.map.json app.*.map.json
@@ -171,5 +196,3 @@ app.*.map.json
/android/app/profile /android/app/profile
/android/app/release /android/app/release
/devtools_options.yaml /devtools_options.yaml
untranslated_messages.json

View File

@@ -18,7 +18,7 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false
} }

View File

@@ -24,11 +24,6 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

View File

@@ -3,4 +3,3 @@ template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
output-dir: lib/l10n/generated output-dir: lib/l10n/generated
nullable-getter: false nullable-getter: false
untranslated-messages-file: lib/l10n/untranslated_messages.json

View File

@@ -1,19 +0,0 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Route<T> adaptivePageRoute<T>({
required Widget Function(BuildContext) builder,
bool fullscreenDialog = false,
}) {
if (Platform.isIOS) {
return CupertinoPageRoute<T>(
builder: builder,
fullscreenDialog: fullscreenDialog,
);
}
return MaterialPageRoute<T>(
builder: builder,
fullscreenDialog: fullscreenDialog,
);
}

View File

@@ -11,6 +11,8 @@ class CustomTheme {
static Color onBoxColor = const Color(0xFF181818); static Color onBoxColor = const Color(0xFF181818);
static Color boxBorder = const Color(0xFF272727); static Color boxBorder = const Color(0xFF272727);
static const Color textColor = Colors.white; static const Color textColor = Colors.white;
static Color navBarItemSelectedColor = primaryColor.withValues(green: 0.4);
static Color navBarItemUnselectedColor = Colors.white.withValues(alpha: 0.6);
// ==================== Border Radius ==================== // ==================== Border Radius ====================
static const double standardBorderRadius = 12.0; static const double standardBorderRadius = 12.0;

View File

@@ -9,7 +9,7 @@ class Match {
final String name; final String name;
final List<Player>? players; final List<Player>? players;
final Group? group; final Group? group;
Player? winner; final Player? winner;
Match({ Match({
String? id, String? id,

View File

@@ -3,7 +3,6 @@
"all_players": "Alle Spieler:innen", "all_players": "Alle Spieler:innen",
"all_players_selected": "Alle Spieler:innen ausgewählt", "all_players_selected": "Alle Spieler:innen ausgewählt",
"amount_of_matches": "Anzahl der Spiele", "amount_of_matches": "Anzahl der Spiele",
"app_name": "Game Tracker",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"choose_game": "Spielvorlage wählen", "choose_game": "Spielvorlage wählen",
"choose_group": "Gruppe wählen", "choose_group": "Gruppe wählen",
@@ -13,13 +12,12 @@
"create_match": "Spiel erstellen", "create_match": "Spiel erstellen",
"create_new_group": "Neue Gruppe erstellen", "create_new_group": "Neue Gruppe erstellen",
"create_new_match": "Neues Spiel erstellen", "create_new_match": "Neues Spiel erstellen",
"data": "Daten",
"data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_deleted": "Daten erfolgreich gelöscht",
"data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_exported": "Daten erfolgreich exportiert",
"data_successfully_imported": "Daten erfolgreich importiert", "data_successfully_imported": "Daten erfolgreich importiert",
"days_ago": "vor {count} Tagen", "days_ago": "vor {count} Tagen",
"delete": "Löschen", "delete": "Löschen",
"delete_all_data": "Alle Daten löschen", "delete_all_data": "Alle Daten löschen?",
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
"error_reading_file": "Fehler beim Lesen der Datei", "error_reading_file": "Fehler beim Lesen der Datei",
"export_canceled": "Export abgebrochen", "export_canceled": "Export abgebrochen",
@@ -36,17 +34,13 @@
"info": "Info", "info": "Info",
"invalid_schema": "Ungültiges Schema", "invalid_schema": "Ungültiges Schema",
"least_points": "Niedrigste Punkte", "least_points": "Niedrigste Punkte",
"legal": "Rechtliches",
"legal_notice": "Impressum",
"licenses": "Lizenzen",
"match_in_progress": "Spiel läuft...", "match_in_progress": "Spiel läuft...",
"match_name": "Spieltitel", "match_name": "Spieltitel",
"matches": "Spiele", "matches": "Spiele",
"menu": "Menü",
"most_points": "Höchste Punkte", "most_points": "Höchste Punkte",
"no_data_available": "Keine Daten verfügbar", "no_data_available": "Keine Daten verfügbar",
"no_groups_created_yet": "Noch keine Gruppen erstellt", "no_groups_created_yet": "Noch keine Gruppen erstellt",
"no_licenses_found": "Keine Lizenzen gefunden",
"no_license_text_available": "Kein Lizenztext verfügbar",
"no_matches_created_yet": "Noch keine Spiele erstellt", "no_matches_created_yet": "Noch keine Spiele erstellt",
"no_players_created_yet": "Noch keine Spieler:in 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_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
@@ -60,7 +54,6 @@
"player_name": "Spieler:innenname", "player_name": "Spieler:innenname",
"players": "Spieler:innen", "players": "Spieler:innen",
"players_count": "{count} Spieler", "players_count": "{count} Spieler",
"privacy_policy": "Datenschutzerklärung",
"quick_create": "Schnellzugriff", "quick_create": "Schnellzugriff",
"recent_matches": "Letzte Spiele", "recent_matches": "Letzte Spiele",
"ruleset": "Regelwerk", "ruleset": "Regelwerk",

View File

@@ -39,9 +39,6 @@
"@create_new_match": { "@create_new_match": {
"description": "Button text to create a new match" "description": "Button text to create a new match"
}, },
"@data": {
"description": "Data label"
},
"@data_successfully_deleted": { "@data_successfully_deleted": {
"description": "Success message after deleting data" "description": "Success message after deleting data"
}, },
@@ -113,15 +110,6 @@
"@least_points": { "@least_points": {
"description": "Title for least points ruleset" "description": "Title for least points ruleset"
}, },
"@legal": {
"description": "Legal section header"
},
"@legal_notice": {
"description": "Legal notice menu item"
},
"@licenses": {
"description": "Licenses menu item"
},
"@match_in_progress": { "@match_in_progress": {
"description": "Message when match is in progress" "description": "Message when match is in progress"
}, },
@@ -131,6 +119,9 @@
"@matches": { "@matches": {
"description": "Label for matches" "description": "Label for matches"
}, },
"@menu": {
"description": "Menu label"
},
"@most_points": { "@most_points": {
"description": "Title for most points ruleset" "description": "Title for most points ruleset"
}, },
@@ -140,12 +131,6 @@
"@no_groups_created_yet": { "@no_groups_created_yet": {
"description": "Message when no groups exist" "description": "Message when no groups exist"
}, },
"@no_licenses_found": {
"description": "Message when no licenses are found"
},
"@no_license_text_available": {
"description": "Message when no license text is available"
},
"@no_matches_created_yet": { "@no_matches_created_yet": {
"description": "Message when no matches exist" "description": "Message when no matches exist"
}, },
@@ -190,9 +175,6 @@
} }
} }
}, },
"@privacy_policy": {
"description": "Privacy policy menu item"
},
"@quick_create": { "@quick_create": {
"description": "Title for quick create section" "description": "Title for quick create section"
}, },
@@ -227,7 +209,7 @@
"description": "Shows the number of selected players" "description": "Shows the number of selected players"
}, },
"@settings": { "@settings": {
"description": "Label for the App Settings" "description": "Settings label"
}, },
"@single_loser": { "@single_loser": {
"description": "Title for single loser ruleset" "description": "Title for single loser ruleset"
@@ -290,13 +272,12 @@
"create_match": "Create match", "create_match": "Create match",
"create_new_group": "Create new group", "create_new_group": "Create new group",
"create_new_match": "Create new match", "create_new_match": "Create new match",
"data": "Data",
"data_successfully_deleted": "Data successfully deleted", "data_successfully_deleted": "Data successfully deleted",
"data_successfully_exported": "Data successfully exported", "data_successfully_exported": "Data successfully exported",
"data_successfully_imported": "Data successfully imported", "data_successfully_imported": "Data successfully imported",
"days_ago": "{count} days ago", "days_ago": "{count} days ago",
"delete": "Delete", "delete": "Delete",
"delete_all_data": "Delete all data", "delete_all_data": "Delete all data?",
"error_creating_group": "Error while creating group, please try again", "error_creating_group": "Error while creating group, please try again",
"error_reading_file": "Error reading file", "error_reading_file": "Error reading file",
"export_canceled": "Export canceled", "export_canceled": "Export canceled",
@@ -313,17 +294,13 @@
"info": "Info", "info": "Info",
"invalid_schema": "Invalid Schema", "invalid_schema": "Invalid Schema",
"least_points": "Least Points", "least_points": "Least Points",
"legal": "Legal",
"legal_notice": "Legal Notice",
"licenses": "Licenses",
"match_in_progress": "Match in progress...", "match_in_progress": "Match in progress...",
"match_name": "Match name", "match_name": "Match name",
"matches": "Matches", "matches": "Matches",
"menu": "Menu",
"most_points": "Most Points", "most_points": "Most Points",
"no_data_available": "No data available", "no_data_available": "No data available",
"no_groups_created_yet": "No groups created yet", "no_groups_created_yet": "No groups created yet",
"no_licenses_found": "No licenses found",
"no_license_text_available": "No license text available",
"no_matches_created_yet": "No matches created yet", "no_matches_created_yet": "No matches created yet",
"no_players_created_yet": "No players created yet", "no_players_created_yet": "No players created yet",
"no_players_found_with_that_name": "No players found with that name", "no_players_found_with_that_name": "No players found with that name",
@@ -337,7 +314,6 @@
"player_name": "Player name", "player_name": "Player name",
"players": "Players", "players": "Players",
"players_count": "{count} Players", "players_count": "{count} Players",
"privacy_policy": "Privacy Policy",
"quick_create": "Quick Create", "quick_create": "Quick Create",
"recent_matches": "Recent Matches", "recent_matches": "Recent Matches",
"ruleset": "Ruleset", "ruleset": "Ruleset",

View File

@@ -176,12 +176,6 @@ abstract class AppLocalizations {
/// **'Create new match'** /// **'Create new match'**
String get create_new_match; String get create_new_match;
/// Data label
///
/// In en, this message translates to:
/// **'Data'**
String get data;
/// Success message after deleting data /// Success message after deleting data
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -215,7 +209,7 @@ abstract class AppLocalizations {
/// Confirmation dialog for deleting all data /// Confirmation dialog for deleting all data
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Delete all data'** /// **'Delete all data?'**
String get delete_all_data; String get delete_all_data;
/// Error message when group creation fails /// Error message when group creation fails
@@ -314,24 +308,6 @@ abstract class AppLocalizations {
/// **'Least Points'** /// **'Least Points'**
String get least_points; String get least_points;
/// Legal section header
///
/// In en, this message translates to:
/// **'Legal'**
String get legal;
/// Legal notice menu item
///
/// In en, this message translates to:
/// **'Legal Notice'**
String get legal_notice;
/// Licenses menu item
///
/// In en, this message translates to:
/// **'Licenses'**
String get licenses;
/// Message when match is in progress /// Message when match is in progress
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -350,6 +326,12 @@ abstract class AppLocalizations {
/// **'Matches'** /// **'Matches'**
String get matches; String get matches;
/// Menu label
///
/// In en, this message translates to:
/// **'Menu'**
String get menu;
/// Title for most points ruleset /// Title for most points ruleset
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -368,18 +350,6 @@ abstract class AppLocalizations {
/// **'No groups created yet'** /// **'No groups created yet'**
String get no_groups_created_yet; String get no_groups_created_yet;
/// Message when no licenses are found
///
/// In en, this message translates to:
/// **'No licenses found'**
String get no_licenses_found;
/// Message when no license text is available
///
/// In en, this message translates to:
/// **'No license text available'**
String get no_license_text_available;
/// Message when no matches exist /// Message when no matches exist
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -458,12 +428,6 @@ abstract class AppLocalizations {
/// **'{count} Players'** /// **'{count} Players'**
String players_count(int count); String players_count(int count);
/// Privacy policy menu item
///
/// In en, this message translates to:
/// **'Privacy Policy'**
String get privacy_policy;
/// Title for quick create section /// Title for quick create section
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -530,7 +494,7 @@ abstract class AppLocalizations {
/// **'Selected players'** /// **'Selected players'**
String get selected_players; String get selected_players;
/// Label for the App Settings /// Settings label
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Settings'** /// **'Settings'**

View File

@@ -49,9 +49,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get create_new_match => 'Neues Spiel erstellen'; String get create_new_match => 'Neues Spiel erstellen';
@override
String get data => 'Daten';
@override @override
String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; String get data_successfully_deleted => 'Daten erfolgreich gelöscht';
@@ -70,7 +67,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get delete => 'Löschen'; String get delete => 'Löschen';
@override @override
String get delete_all_data => 'Alle Daten löschen'; String get delete_all_data => 'Alle Daten löschen?';
@override @override
String get error_creating_group => String get error_creating_group =>
@@ -121,15 +118,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get least_points => 'Niedrigste Punkte'; String get least_points => 'Niedrigste Punkte';
@override
String get legal => 'Rechtliches';
@override
String get legal_notice => 'Impressum';
@override
String get licenses => 'Lizenzen';
@override @override
String get match_in_progress => 'Spiel läuft...'; String get match_in_progress => 'Spiel läuft...';
@@ -139,6 +127,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get matches => 'Spiele'; String get matches => 'Spiele';
@override
String get menu => 'Menü';
@override @override
String get most_points => 'Höchste Punkte'; String get most_points => 'Höchste Punkte';
@@ -148,12 +139,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; String get no_groups_created_yet => 'Noch keine Gruppen erstellt';
@override
String get no_licenses_found => 'Keine Lizenzen gefunden';
@override
String get no_license_text_available => 'Kein Lizenztext verfügbar';
@override @override
String get no_matches_created_yet => 'Noch keine Spiele erstellt'; String get no_matches_created_yet => 'Noch keine Spiele erstellt';
@@ -196,9 +181,6 @@ class AppLocalizationsDe extends AppLocalizations {
return '$count Spieler'; return '$count Spieler';
} }
@override
String get privacy_policy => 'Datenschutzerklärung';
@override @override
String get quick_create => 'Schnellzugriff'; String get quick_create => 'Schnellzugriff';

View File

@@ -49,9 +49,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get create_new_match => 'Create new match'; String get create_new_match => 'Create new match';
@override
String get data => 'Data';
@override @override
String get data_successfully_deleted => 'Data successfully deleted'; String get data_successfully_deleted => 'Data successfully deleted';
@@ -70,7 +67,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get delete => 'Delete'; String get delete => 'Delete';
@override @override
String get delete_all_data => 'Delete all data'; String get delete_all_data => 'Delete all data?';
@override @override
String get error_creating_group => String get error_creating_group =>
@@ -121,15 +118,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get least_points => 'Least Points'; String get least_points => 'Least Points';
@override
String get legal => 'Legal';
@override
String get legal_notice => 'Legal Notice';
@override
String get licenses => 'Licenses';
@override @override
String get match_in_progress => 'Match in progress...'; String get match_in_progress => 'Match in progress...';
@@ -139,6 +127,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get matches => 'Matches'; String get matches => 'Matches';
@override
String get menu => 'Menu';
@override @override
String get most_points => 'Most Points'; String get most_points => 'Most Points';
@@ -148,12 +139,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get no_groups_created_yet => 'No groups created yet'; String get no_groups_created_yet => 'No groups created yet';
@override
String get no_licenses_found => 'No licenses found';
@override
String get no_license_text_available => 'No license text available';
@override @override
String get no_matches_created_yet => 'No matches created yet'; String get no_matches_created_yet => 'No matches created yet';
@@ -196,9 +181,6 @@ class AppLocalizationsEn extends AppLocalizations {
return '$count Players'; return '$count Players';
} }
@override
String get privacy_policy => 'Privacy Policy';
@override @override
String get quick_create => 'Quick Create'; String get quick_create => 'Quick Create';

View File

@@ -44,12 +44,6 @@ class GameTracker extends StatelessWidget {
seedColor: CustomTheme.primaryColor, seedColor: CustomTheme.primaryColor,
brightness: Brightness.dark, brightness: Brightness.dark,
).copyWith(surface: CustomTheme.backgroundColor), ).copyWith(surface: CustomTheme.backgroundColor),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
},
),
), ),
home: const CustomNavigationBar(), home: const CustomNavigationBar(),
); );

View File

@@ -1,11 +1,12 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart';
import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart';
import 'package:game_tracker/presentation/widgets/navbar_item.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart';
@@ -57,7 +58,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
adaptivePageRoute(builder: (_) => const SettingsView()), MaterialPageRoute(builder: (_) => const SettingsView()),
); );
setState(() { setState(() {
tabKeyCount++; tabKeyCount++;
@@ -71,50 +72,49 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
body: tabs[currentIndex], body: tabs[currentIndex],
extendBody: true, extendBody: true,
bottomNavigationBar: SafeArea( bottomNavigationBar: ClipRRect(
minimum: const EdgeInsets.only(bottom: 30), child: BackdropFilter(
child: Container( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10), child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24), color: CustomTheme.boxColor.withValues(alpha: 0.8),
color: CustomTheme.primaryColor, ),
), child: SafeArea(
child: ClipRRect( child: SizedBox(
borderRadius: BorderRadius.circular(24), height: 70,
child: SizedBox( child: Row(
height: 60, mainAxisAlignment: MainAxisAlignment.spaceAround,
child: Row( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceAround, NavbarItem(
children: <Widget>[ index: 0,
NavbarItem( isSelected: currentIndex == 0,
index: 0, icon: Icons.home_rounded,
isSelected: currentIndex == 0, label: loc.home,
icon: Icons.home_rounded, onTabTapped: onTabTapped,
label: loc.home, ),
onTabTapped: onTabTapped, NavbarItem(
), index: 1,
NavbarItem( isSelected: currentIndex == 1,
index: 1, icon: Icons.gamepad_rounded,
isSelected: currentIndex == 1, label: loc.matches,
icon: Icons.gamepad_rounded, onTabTapped: onTabTapped,
label: loc.matches, ),
onTabTapped: onTabTapped, NavbarItem(
), index: 2,
NavbarItem( isSelected: currentIndex == 2,
index: 2, icon: Icons.group_rounded,
isSelected: currentIndex == 2, label: loc.groups,
icon: Icons.group_rounded, onTabTapped: onTabTapped,
label: loc.groups, ),
onTabTapped: onTabTapped, NavbarItem(
), index: 3,
NavbarItem( isSelected: currentIndex == 3,
index: 3, icon: Icons.bar_chart_rounded,
isSelected: currentIndex == 3, label: loc.statistics,
icon: Icons.bar_chart_rounded, onTabTapped: onTabTapped,
label: loc.statistics, ),
onTabTapped: onTabTapped, ],
), ),
],
), ),
), ),
), ),

View File

@@ -44,68 +44,66 @@ class _CreateGroupViewState extends State<CreateGroupView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return ScaffoldMessenger( return Scaffold(
child: Scaffold( backgroundColor: CustomTheme.backgroundColor,
backgroundColor: CustomTheme.backgroundColor, appBar: AppBar(title: Text(loc.create_new_group)),
appBar: AppBar(title: Text(loc.create_new_group)), body: SafeArea(
body: SafeArea( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
children: [ Container(
Container( margin: CustomTheme.standardMargin,
margin: CustomTheme.standardMargin, child: TextInputField(
child: TextInputField( controller: _groupNameController,
controller: _groupNameController, hintText: loc.group_name,
hintText: loc.group_name,
),
), ),
Expanded( ),
child: PlayerSelection( Expanded(
onChanged: (value) { child: PlayerSelection(
setState(() { onChanged: (value) {
selectedPlayers = [...value]; setState(() {
}); selectedPlayers = [...value];
}, });
), },
), ),
CustomWidthButton( ),
text: loc.create_group, CustomWidthButton(
sizeRelativeToWidth: 0.95, text: loc.create_group,
buttonType: ButtonType.primary, sizeRelativeToWidth: 0.95,
onPressed: buttonType: ButtonType.primary,
(_groupNameController.text.isEmpty || onPressed:
(selectedPlayers.length < 2)) (_groupNameController.text.isEmpty ||
? null (selectedPlayers.length < 2))
: () async { ? null
bool success = await db.groupDao.addGroup( : () async {
group: Group( bool success = await db.groupDao.addGroup(
name: _groupNameController.text.trim(), group: Group(
members: selectedPlayers, name: _groupNameController.text.trim(),
), members: selectedPlayers,
); ),
if (!context.mounted) return; );
if (success) { if (!context.mounted) return;
Navigator.pop(context); if (success) {
} else { Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( } else {
SnackBar( ScaffoldMessenger.of(context).showSnackBar(
backgroundColor: CustomTheme.boxColor, SnackBar(
content: Center( backgroundColor: CustomTheme.boxColor,
child: Text( content: Center(
AppLocalizations.of( child: Text(
context, AppLocalizations.of(
).error_creating_group, context,
style: const TextStyle(color: Colors.white), ).error_creating_group,
), style: const TextStyle(color: Colors.white),
), ),
), ),
); ),
} );
}, }
), },
const SizedBox(height: 20), ),
], const SizedBox(height: 20),
), ],
), ),
), ),
); );

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -86,7 +85,7 @@ class _GroupsViewState extends State<GroupsView> {
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
adaptivePageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return const CreateGroupView(); return const CreateGroupView();
}, },

View File

@@ -1,16 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -45,6 +43,7 @@ class _HomeViewState extends State<HomeView> {
Player(name: 'Skeleton Player 2'), Player(name: 'Skeleton Player 2'),
], ],
), ),
winner: Player(name: 'Skeleton Player 1'),
), ),
); );
@@ -91,92 +90,121 @@ class _HomeViewState extends State<HomeView> {
child: InfoTile( child: InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: loc.recent_matches, title: loc.recent_matches,
icon: Icons.history_rounded, icon: Icons.timer,
content: Column( content: Padding(
children: [ padding: const EdgeInsets.symmetric(horizontal: 40.0),
if (recentMatches.isNotEmpty) child: Visibility(
for (Match match in recentMatches) visible: !isLoading && loadedRecentMatches.isNotEmpty,
Padding( replacement: Center(
padding: const EdgeInsets.symmetric( heightFactor: 12,
vertical: 6.0, child: Text(
), AppLocalizations.of(
child: MatchTile( context,
compact: true, ).no_recent_matches_available,
width: constraints.maxWidth * 0.9,
match: match,
onTap: () async {
await Navigator.of(context).push(
adaptivePageRoute(
fullscreenDialog: true,
builder: (context) =>
MatchResultView(match: match),
),
);
await updatedWinnerinRecentMatches(match.id);
},
),
)
else
Center(
heightFactor: 5,
child: Text(loc.no_recent_matches_available),
), ),
], ),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MatchSummaryTile(
matchTitle: recentMatches[0].name,
game: 'Winner',
ruleset: 'Ruleset',
players: _getPlayerText(
recentMatches[0],
context,
),
winner: recentMatches[0].winner == null
? AppLocalizations.of(
context,
).match_in_progress
: recentMatches[0].winner!.name,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Divider(),
),
if (loadedRecentMatches.length > 1) ...[
MatchSummaryTile(
matchTitle: recentMatches[1].name,
game: 'Winner',
ruleset: 'Ruleset',
players: _getPlayerText(
recentMatches[1],
context,
),
winner: recentMatches[1].winner == null
? AppLocalizations.of(
context,
).match_in_progress
: recentMatches[1].winner!.name,
),
const SizedBox(height: 8),
] else ...[
Center(
heightFactor: 5.35,
child: Text(
AppLocalizations.of(
context,
).no_second_match_available,
),
),
],
],
),
),
), ),
), ),
), ),
Padding( InfoTile(
padding: EdgeInsets.zero, width: constraints.maxWidth * 0.95,
child: InfoTile( title: loc.quick_create,
width: constraints.maxWidth * 0.95, icon: Icons.add_box_rounded,
title: loc.quick_create, content: Column(
icon: Icons.add_box_rounded, children: [
content: Column( Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, QuickCreateButton(
children: [ text: 'Category 1',
QuickCreateButton( onPressed: () {},
text: 'Category 1', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 2',
QuickCreateButton( onPressed: () {},
text: 'Category 2', ),
onPressed: () {}, ],
), ),
], Row(
), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, QuickCreateButton(
children: [ text: 'Category 3',
QuickCreateButton( onPressed: () {},
text: 'Category 3', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 4',
QuickCreateButton( onPressed: () {},
text: 'Category 4', ),
onPressed: () {}, ],
), ),
], Row(
), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, QuickCreateButton(
children: [ text: 'Category 5',
QuickCreateButton( onPressed: () {},
text: 'Category 5', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 6',
QuickCreateButton( onPressed: () {},
text: 'Category 6', ),
onPressed: () {}, ],
), ),
], ],
),
],
),
), ),
), ),
SizedBox(height: MediaQuery.paddingOf(context).bottom),
], ],
), ),
), ),
@@ -187,7 +215,7 @@ class _HomeViewState extends State<HomeView> {
/// Loads the data for the HomeView from the database. /// Loads the data for the HomeView from the database.
/// This includes the match count, group count, and recent matches. /// This includes the match count, group count, and recent matches.
Future<void> loadHomeViewData() async { void loadHomeViewData() {
final db = Provider.of<AppDatabase>(context, listen: false); final db = Provider.of<AppDatabase>(context, listen: false);
Future.wait([ Future.wait([
db.matchDao.getMatchCount(), db.matchDao.getMatchCount(),
@@ -203,6 +231,11 @@ class _HomeViewState extends State<HomeView> {
..sort((a, b) => b.createdAt.compareTo(a.createdAt))) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
.take(2) .take(2)
.toList(); .toList();
if (loadedRecentMatches.length < 2) {
recentMatches.add(
Match(name: 'Dummy Match', winner: null, group: null, players: null),
);
}
if (mounted) { if (mounted) {
setState(() { setState(() {
isLoading = false; isLoading = false;
@@ -211,15 +244,18 @@ class _HomeViewState extends State<HomeView> {
}); });
} }
/// Updates the winner information for a specific match in the recent matches list. /// Generates a text representation of the players in the match.
Future<void> updatedWinnerinRecentMatches(String matchId) async { /// If the match has a group, it returns the group name and the number of additional players.
final db = Provider.of<AppDatabase>(context, listen: false); /// If there is no group, it returns the count of players.
final winner = await db.matchDao.getWinner(matchId: matchId); String _getPlayerText(Match game, context) {
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId); final loc = AppLocalizations.of(context);
if (matchIndex != -1) { if (game.group == null) {
setState(() { final playerCount = game.players?.length ?? 0;
recentMatches[matchIndex].winner = winner; return loc.players_count(playerCount);
});
} }
if (game.players == null || game.players!.isEmpty) {
return game.group!.name;
}
return '${game.group!.name} + ${game.players!.length}';
} }
} }

View File

@@ -140,11 +140,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
filteredGroups.clear(); filteredGroups.clear();
filteredGroups.addAll( filteredGroups.addAll(
widget.groups.where( widget.groups.where(
(group) => (group) => group.name.toLowerCase().contains(query.toLowerCase()),
group.name.toLowerCase().contains(query.toLowerCase()) ||
group.members.any(
(player) => player.name.toLowerCase().contains(query.toLowerCase()),
),
), ),
); );
} }

View File

@@ -1,5 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -119,142 +119,140 @@ class _CreateMatchViewState extends State<CreateMatchView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return ScaffoldMessenger( return Scaffold(
child: Scaffold( backgroundColor: CustomTheme.backgroundColor,
backgroundColor: CustomTheme.backgroundColor, appBar: AppBar(title: Text(loc.create_new_match)),
appBar: AppBar(title: Text(loc.create_new_match)), body: SafeArea(
body: SafeArea( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
children: [ Container(
Container( margin: CustomTheme.tileMargin,
margin: CustomTheme.tileMargin, child: TextInputField(
child: TextInputField( controller: _matchNameController,
controller: _matchNameController, hintText: hintText ?? '',
hintText: hintText ?? '',
),
), ),
ChooseTile( ),
title: loc.game, ChooseTile(
trailingText: selectedGameIndex == -1 title: loc.game,
? loc.none trailingText: selectedGameIndex == -1
: games[selectedGameIndex].$1, ? loc.none
onPressed: () async { : games[selectedGameIndex].$1,
selectedGameIndex = await Navigator.of(context).push( onPressed: () async {
adaptivePageRoute( selectedGameIndex = await Navigator.of(context).push(
builder: (context) => ChooseGameView( MaterialPageRoute(
games: games, builder: (context) => ChooseGameView(
initialGameIndex: selectedGameIndex, games: games,
), initialGameIndex: selectedGameIndex,
), ),
); ),
);
setState(() {
if (selectedGameIndex != -1) {
hintText = games[selectedGameIndex].$1;
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
} else {
hintText = loc.match_name;
selectedRuleset = null;
}
});
},
),
ChooseTile(
title: loc.ruleset,
trailingText: selectedRuleset == null
? loc.none
: translateRulesetToString(selectedRuleset!, context),
onPressed: () async {
selectedRuleset = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChooseRulesetView(
rulesets: _rulesets,
initialRulesetIndex: selectedRulesetIndex,
),
),
);
if (!mounted) return;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
selectedGameIndex = -1;
setState(() {});
},
),
ChooseTile(
title: loc.group,
trailingText: selectedGroup == null
? loc.none_group
: selectedGroup!.name,
onPressed: () async {
selectedGroup = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChooseGroupView(
groups: groupsList,
initialGroupId: selectedGroupId,
),
),
);
selectedGroupId = selectedGroup?.id ?? '';
if (selectedGroup != null) {
filteredPlayerList = playerList
.where(
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
)
.toList();
} else {
filteredPlayerList = List.from(playerList);
}
setState(() {});
},
),
Expanded(
child: PlayerSelection(
key: ValueKey(selectedGroup?.id ?? 'no_group'),
initialSelectedPlayers: selectedPlayers ?? [],
availablePlayers: filteredPlayerList,
onChanged: (value) {
setState(() { setState(() {
if (selectedGameIndex != -1) { selectedPlayers = value;
hintText = games[selectedGameIndex].$1;
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
} else {
hintText = loc.match_name;
selectedRuleset = null;
}
}); });
}, },
), ),
ChooseTile( ),
title: loc.ruleset, CustomWidthButton(
trailingText: selectedRuleset == null text: loc.create_match,
? loc.none sizeRelativeToWidth: 0.95,
: translateRulesetToString(selectedRuleset!, context), buttonType: ButtonType.primary,
onPressed: () async { onPressed: _enableCreateGameButton()
selectedRuleset = await Navigator.of(context).push( ? () async {
adaptivePageRoute( Match match = Match(
builder: (context) => ChooseRulesetView( name: _matchNameController.text.isEmpty
rulesets: _rulesets, ? (hintText ?? '')
initialRulesetIndex: selectedRulesetIndex, : _matchNameController.text.trim(),
), createdAt: DateTime.now(),
), group: selectedGroup,
); players: selectedPlayers,
if (!mounted) return; );
selectedRulesetIndex = _rulesets.indexWhere( await db.matchDao.addMatch(match: match);
(r) => r.$1 == selectedRuleset, if (context.mounted) {
); Navigator.pushReplacement(
selectedGameIndex = -1; context,
setState(() {}); CupertinoPageRoute(
}, fullscreenDialog: true,
), builder: (context) => MatchResultView(
ChooseTile( match: match,
title: loc.group, onWinnerChanged: widget.onWinnerChanged,
trailingText: selectedGroup == null
? loc.none_group
: selectedGroup!.name,
onPressed: () async {
selectedGroup = await Navigator.of(context).push(
adaptivePageRoute(
builder: (context) => ChooseGroupView(
groups: groupsList,
initialGroupId: selectedGroupId,
),
),
);
selectedGroupId = selectedGroup?.id ?? '';
if (selectedGroup != null) {
filteredPlayerList = playerList
.where(
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
)
.toList();
} else {
filteredPlayerList = List.from(playerList);
}
setState(() {});
},
),
Expanded(
child: PlayerSelection(
key: ValueKey(selectedGroup?.id ?? 'no_group'),
initialSelectedPlayers: selectedPlayers ?? [],
availablePlayers: filteredPlayerList,
onChanged: (value) {
setState(() {
selectedPlayers = value;
});
},
),
),
CustomWidthButton(
text: loc.create_match,
sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary,
onPressed: _enableCreateGameButton()
? () async {
Match match = Match(
name: _matchNameController.text.isEmpty
? (hintText ?? '')
: _matchNameController.text.trim(),
createdAt: DateTime.now(),
group: selectedGroup,
players: selectedPlayers,
);
await db.matchDao.addMatch(match: match);
if (context.mounted) {
Navigator.pushReplacement(
context,
adaptivePageRoute(
fullscreenDialog: true,
builder: (context) => MatchResultView(
match: match,
onWinnerChanged: widget.onWinnerChanged,
),
), ),
); ),
} );
} }
: null, }
), : null,
], ),
), ],
), ),
), ),
); );

View File

@@ -1,7 +1,7 @@
import 'dart:core' hide Match; import 'dart:core' hide Match;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -78,26 +78,20 @@ class _MatchViewState extends State<MatchView> {
height: MediaQuery.paddingOf(context).bottom - 20, height: MediaQuery.paddingOf(context).bottom - 20,
); );
} }
return Center( return MatchTile(
child: Padding( onTap: () async {
padding: const EdgeInsets.only(bottom: 12.0), Navigator.push(
child: MatchTile( context,
width: MediaQuery.sizeOf(context).width * 0.95, CupertinoPageRoute(
onTap: () async { fullscreenDialog: true,
Navigator.push( builder: (context) => MatchResultView(
context, match: matches[index],
adaptivePageRoute( onWinnerChanged: loadGames,
fullscreenDialog: true, ),
builder: (context) => MatchResultView( ),
match: matches[index], );
onWinnerChanged: loadGames, },
), match: matches[index],
),
);
},
match: matches[index],
),
),
); );
}, },
), ),
@@ -111,7 +105,7 @@ class _MatchViewState extends State<MatchView> {
onPressed: () async { onPressed: () async {
Navigator.push( Navigator.push(
context, context,
adaptivePageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
CreateMatchView(onWinnerChanged: loadGames), CreateMatchView(onWinnerChanged: loadGames),
), ),

View File

@@ -0,0 +1,197 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart';
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
backgroundColor: CustomTheme.backgroundColor,
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) =>
SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 10),
child: Text(
textAlign: TextAlign.start,
loc.menu,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 10,
),
child: Text(
textAlign: TextAlign.start,
loc.settings,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.export_data,
icon: Icons.upload_rounded,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final String json =
await DataTransferService.getAppDataAsJson(context);
final result = await DataTransferService.exportData(
json,
'game_tracker-data',
);
if (!context.mounted) return;
showExportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.import_data,
icon: Icons.download_rounded,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final result = await DataTransferService.importData(
context,
);
if (!context.mounted) return;
showImportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.delete_all_data,
icon: Icons.delete_rounded,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(loc.delete_all_data),
content: Text(loc.this_cannot_be_undone),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete),
),
],
),
).then((confirmed) {
if (confirmed == true && context.mounted) {
DataTransferService.deleteAllData(context);
showSnackbar(
context: context,
message: AppLocalizations.of(
context,
).data_successfully_deleted,
);
}
});
},
),
],
),
),
),
);
}
/// Displays a snackbar based on the import result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the import operation.
void showImportSnackBar({
required BuildContext context,
required ImportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ImportResult.success:
showSnackbar(context: context, message: loc.data_successfully_imported);
case ImportResult.invalidSchema:
showSnackbar(context: context, message: loc.invalid_schema);
case ImportResult.fileReadError:
showSnackbar(context: context, message: loc.error_reading_file);
case ImportResult.canceled:
showSnackbar(context: context, message: loc.import_canceled);
case ImportResult.formatException:
showSnackbar(context: context, message: loc.format_exception);
case ImportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar based on the export result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the export operation.
void showExportSnackBar({
required BuildContext context,
required ExportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ExportResult.success:
showSnackbar(context: context, message: loc.data_successfully_exported);
case ExportResult.canceled:
showSnackbar(context: context, message: loc.export_canceled);
case ExportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar with the given message and optional action.
///
/// [context] The BuildContext to show the snackbar in.
/// [message] The message to display in the snackbar.
/// [duration] The duration for which the snackbar is displayed.
/// [action] An optional callback function to execute when the action button is pressed.
void showSnackbar({
required BuildContext context,
required String message,
Duration duration = const Duration(seconds: 3),
VoidCallback? action,
}) {
final loc = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: CustomTheme.onBoxColor,
duration: duration,
action: action != null
? SnackBarAction(label: loc.undo, onPressed: action)
: null,
),
);
}
}

View File

@@ -1,124 +0,0 @@
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';
class LicenseDetailView extends StatelessWidget {
final Package package;
const LicenseDetailView({super.key, required this.package});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Lizenzdetails')),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Column(
children: [
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,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
package.name,
textAlign: TextAlign.left,
style: const TextStyle(
height: 0,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (package.version != null) ...[
Text(
'Version ${package.version}',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade300,
),
),
],
],
),
],
),
if (package.authors.isNotEmpty) ...[
const SizedBox(height: 8),
SelectableText(
package.authors.join(', '),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
if (package.homepage != null &&
package.homepage!.isNotEmpty) ...[
const SizedBox(height: 8),
SelectableText(
package.homepage!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
],
),
),
const SizedBox(height: 20),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
decoration: CustomTheme.standardBoxDecoration,
child: (package.license != null && package.license!.isNotEmpty)
? SelectableText(
package.license!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade300,
height: 1.5,
fontFamily: 'monospace',
),
)
: Center(
heightFactor: 25,
child: Text(
loc.no_license_text_available,
style: TextStyle(color: Colors.grey.shade500),
),
),
),
],
),
),
);
}
}

View File

@@ -1,32 +0,0 @@
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/tiles/license_tile.dart';
class LicensesView extends StatelessWidget {
const LicensesView({super.key});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(loc.licenses),
backgroundColor: CustomTheme.backgroundColor,
),
backgroundColor: CustomTheme.backgroundColor,
body: ListView.builder(
itemCount: allDependencies.length,
itemBuilder: (context, index) {
final package = allDependencies[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: LicenseTile(package: package),
);
},
),
);
}
}

View File

@@ -1,314 +0,0 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses_view.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
PackageInfo _packageInfo = PackageInfo(
appName: 'n.A.',
packageName: 'n.A.',
version: 'n.A.',
buildNumber: 'n.A.',
);
@override
void initState() {
super.initState();
_initPackageInfo();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return ScaffoldMessenger(
child: Scaffold(
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
backgroundColor: CustomTheme.backgroundColor,
body: SingleChildScrollView(
child: Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.settings,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.data,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.export_data,
icon: Icons.upload,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final String json =
await DataTransferService.getAppDataAsJson(context);
final result = await DataTransferService.exportData(
json,
'game_tracker-data',
);
if (!context.mounted) return;
showExportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.import_data,
icon: Icons.download,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final result = await DataTransferService.importData(
context,
);
if (!context.mounted) return;
showImportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.delete_all_data,
icon: Icons.delete,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('${loc.delete_all_data}?'),
content: Text(loc.this_cannot_be_undone),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete),
),
],
),
).then((confirmed) {
if (confirmed == true && context.mounted) {
DataTransferService.deleteAllData(context);
showSnackbar(
context: context,
message: AppLocalizations.of(
context,
).data_successfully_deleted,
);
}
});
},
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.legal,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.licenses,
icon: Icons.insert_drive_file,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const LicensesView(),
),
);
},
),
SettingsListTile(
title: loc.legal_notice,
icon: Icons.account_balance_sharp,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: null,
),
SettingsListTile(
title: loc.privacy_policy,
icon: Icons.gpp_good_rounded,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: null,
),
Padding(
padding: const EdgeInsets.only(top: 30, bottom: 20),
child: Center(
child: Column(
spacing: 4,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 40,
children: [
GestureDetector(
child: const Icon(Icons.language),
onTap: () => {
launchUrl(Uri.parse('https://liquid-dev.de')),
},
),
GestureDetector(
child: const FaIcon(FontAwesomeIcons.github),
onTap: () => {
launchUrl(
Uri.parse(
'https://github.com/liquiddevelopmentde',
),
),
},
),
GestureDetector(
child: Icon(
Platform.isIOS
? CupertinoIcons.mail_solid
: Icons.email,
),
onTap: () => launchUrl(
Uri.parse('mailto:hi@liquid-dev.de'),
),
),
],
),
),
Text(
'© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
Text(
'Version ${_packageInfo.version} (${_packageInfo.buildNumber})',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
],
),
),
),
),
);
}
/// Displays a snackbar based on the import result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the import operation.
void showImportSnackBar({
required BuildContext context,
required ImportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ImportResult.success:
showSnackbar(context: context, message: loc.data_successfully_imported);
case ImportResult.invalidSchema:
showSnackbar(context: context, message: loc.invalid_schema);
case ImportResult.fileReadError:
showSnackbar(context: context, message: loc.error_reading_file);
case ImportResult.canceled:
showSnackbar(context: context, message: loc.import_canceled);
case ImportResult.formatException:
showSnackbar(context: context, message: loc.format_exception);
case ImportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar based on the export result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the export operation.
void showExportSnackBar({
required BuildContext context,
required ExportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ExportResult.success:
showSnackbar(context: context, message: loc.data_successfully_exported);
case ExportResult.canceled:
showSnackbar(context: context, message: loc.export_canceled);
case ExportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar with the given message and optional action.
///
/// [context] The BuildContext to show the snackbar in.
/// [message] The message to display in the snackbar.
/// [duration] The duration for which the snackbar is displayed.
/// [action] An optional callback function to execute when the action button is pressed.
void showSnackbar({
required BuildContext context,
required String message,
Duration duration = const Duration(seconds: 3),
VoidCallback? action,
}) {
final loc = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: CustomTheme.onBoxColor,
duration: duration,
action: action != null
? SnackBarAction(label: loc.undo, onPressed: action)
: null,
),
);
}
/// Initializes the package information.
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
/// A navigation bar item widget that represents a single tab in a navigation bar. /// A navigation bar item widget that represents a single tab in a navigation bar.
/// - [index]: The index of the tab. /// - [index]: The index of the tab.
@@ -35,7 +36,45 @@ class NavbarItem extends StatefulWidget {
State<NavbarItem> createState() => _NavbarItemState(); State<NavbarItem> createState() => _NavbarItemState();
} }
class _NavbarItemState extends State<NavbarItem> { class _NavbarItemState extends State<NavbarItem>
with SingleTickerProviderStateMixin {
/// Animation controller for the scale animation
late AnimationController _animationController;
/// Scale animation for the icon when selected
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOutBack,
),
);
if (widget.isSelected) {
_animationController.forward();
}
}
// Retrigger animation on selection change
@override
void didUpdateWidget(NavbarItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isSelected && !oldWidget.isSelected) {
_animationController.forward();
} else if (!widget.isSelected && oldWidget.isSelected) {
_animationController.reverse();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
@@ -48,19 +87,29 @@ class _NavbarItemState extends State<NavbarItem> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( ScaleTransition(
widget.icon, scale: widget.isSelected
color: widget.isSelected ? Colors.white : Colors.black, ? _scaleAnimation
: const AlwaysStoppedAnimation(1.0),
child: Icon(
widget.icon,
color: widget.isSelected
? CustomTheme.navBarItemSelectedColor
: CustomTheme.navBarItemUnselectedColor,
size: 26,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
widget.label, widget.label,
style: TextStyle( style: TextStyle(
color: widget.isSelected ? Colors.white : Colors.black, color: widget.isSelected
fontSize: 12, ? CustomTheme.navBarItemSelectedColor
: CustomTheme.navBarItemUnselectedColor,
fontSize: widget.isSelected ? 12 : 11,
fontWeight: widget.isSelected fontWeight: widget.isSelected
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal, : FontWeight.w500,
), ),
), ),
], ],
@@ -69,4 +118,10 @@ class _NavbarItemState extends State<NavbarItem> {
), ),
); );
} }
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
} }

View File

@@ -1,106 +0,0 @@
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';
class LicenseTile extends StatelessWidget {
final Package package;
const LicenseTile({super.key, required this.package});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LicenseDetailView(package: package),
),
);
},
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(16),
decoration: CustomTheme.standardBoxDecoration.copyWith(
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.description,
color: CustomTheme.primaryColor,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
package.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
if (package.version != null &&
package.version!.isNotEmpty) ...[
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: CustomTheme.onBoxColor,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'v${package.version}',
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
),
],
],
),
const SizedBox(height: 4),
Text(
package.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade400,
height: 1.3,
),
),
],
),
),
const SizedBox(width: 12),
// Arrow Icon
Icon(Icons.chevron_right, color: Colors.grey.shade600, size: 24),
],
),
),
);
}
}

View File

@@ -10,16 +10,8 @@ import 'package:intl/intl.dart';
/// creation date, associated group, winner, and players. /// creation date, associated group, winner, and players.
/// - [match]: The match data to be displayed. /// - [match]: The match data to be displayed.
/// - [onTap]: The callback invoked when the tile is tapped. /// - [onTap]: The callback invoked when the tile is tapped.
/// - [width]: Optional width for the tile.
/// - [compact]: Whether to display the tile in a compact mode
class MatchTile extends StatefulWidget { class MatchTile extends StatefulWidget {
const MatchTile({ const MatchTile({super.key, required this.match, required this.onTap});
super.key,
required this.match,
required this.onTap,
this.width,
this.compact = false,
});
/// The match data to be displayed. /// The match data to be displayed.
final Match match; final Match match;
@@ -27,12 +19,6 @@ class MatchTile extends StatefulWidget {
/// The callback invoked when the tile is tapped. /// The callback invoked when the tile is tapped.
final VoidCallback onTap; final VoidCallback onTap;
/// Optional width for the tile.
final double? width;
/// Whether to display the tile in a compact mode
final bool compact;
@override @override
State<MatchTile> createState() => _MatchTileState(); State<MatchTile> createState() => _MatchTileState();
} }
@@ -55,10 +41,13 @@ class _MatchTileState extends State<MatchTile> {
return GestureDetector( return GestureDetector(
onTap: widget.onTap, onTap: widget.onTap,
child: Container( child: Container(
margin: EdgeInsets.zero, margin: CustomTheme.tileMargin,
width: widget.width,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: CustomTheme.standardBoxDecoration, decoration: BoxDecoration(
color: CustomTheme.boxColor,
border: Border.all(color: CustomTheme.boxBorder),
borderRadius: BorderRadius.circular(12),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -91,22 +80,7 @@ class _MatchTileState extends State<MatchTile> {
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
'${group.name}${widget.match.players != null ? ' + ${widget.match.players?.length}' : ''}', group.name,
style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 12),
] else if (widget.compact) ...[
Row(
children: [
const Icon(Icons.person, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Expanded(
child: Text(
'${widget.match.players!.length} ${loc.players}',
style: const TextStyle(fontSize: 14, color: Colors.grey), style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -153,46 +127,9 @@ class _MatchTileState extends State<MatchTile> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
] else ...[
Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
color: Colors.amber.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.amber.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
children: [
const Icon(
Icons.watch_later,
size: 20,
color: Colors.amber,
),
const SizedBox(width: 8),
Expanded(
child: Text(
loc.match_in_progress,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: CustomTheme.textColor,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(height: 12),
], ],
if (_allPlayers.isNotEmpty && widget.compact == false) ...[ if (_allPlayers.isNotEmpty) ...[
Text( Text(
loc.players, loc.players,
style: const TextStyle( style: const TextStyle(

View File

@@ -47,26 +47,13 @@ class SettingsListTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 28,
color: CustomTheme.primaryColor.withGreen(40),
),
),
/* Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: CustomTheme.primaryColor, color: CustomTheme.primaryColor,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(icon, size: 24), child: Icon(icon, size: 24),
),*/ ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text(title, style: const TextStyle(fontSize: 18)), Text(title, style: const TextStyle(fontSize: 18)),
], ],

View File

@@ -1,7 +1,7 @@
name: game_tracker name: game_tracker
description: "Game Tracking App for Card Games" description: "Game Tracking App for Card Games"
publish_to: 'none' publish_to: 'none'
version: 0.0.5+132 version: 0.0.1+69
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
@@ -9,32 +9,31 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations: material_symbols_icons: ^4.2815.1
sdk: flutter
clock: ^1.1.2 # The following adds the Cupertino Icons font to your application.
cupertino_icons: ^1.0.6 # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
drift: ^2.27.0 drift: ^2.27.0
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
file_picker: ^10.3.6
file_saver: ^0.3.1
font_awesome_flutter: ^10.12.0
intl: any
json_schema: ^5.2.2
package_info_plus: ^9.0.0
path_provider: ^2.1.5 path_provider: ^2.1.5
provider: ^6.1.5 provider: ^6.1.5
skeletonizer: ^2.1.0+1 skeletonizer: ^2.1.0+1
url_launcher: ^6.3.2
uuid: ^4.5.2 uuid: ^4.5.2
file_picker: ^10.3.6
json_schema: ^5.2.2
file_saver: ^0.3.1
clock: ^1.1.2
intl: any
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.5.4
dart_pubspec_licenses: ^3.0.14
drift_dev: ^2.27.0
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
drift_dev: ^2.27.0
build_runner: ^2.5.4
flutter: flutter:
uses-material-design: true uses-material-design: true