Compare commits
38 Commits
644728a9df
...
enhancemen
| Author | SHA1 | Date | |
|---|---|---|---|
| ab20bd764b | |||
| 8ca4e3210e | |||
| a480530919 | |||
| 906c8d8450 | |||
| 000bdc8cbc | |||
| 497f30421d | |||
| adedb85eb2 | |||
| 2a72332bcd | |||
| 7aa41abe61 | |||
| e1263d51ad | |||
| 8791b5296e | |||
| f2a4327166 | |||
| d34990ed50 | |||
| 2ef671884d | |||
| 2ef8eb6534 | |||
| 6f0e5ba5c2 | |||
| 1c07346aaf | |||
| 830a64b5dd | |||
| 9221f64fa5 | |||
| c4f6749882 | |||
| 14a043785e | |||
| 9eb9c0eb7f | |||
| 54ec865f04 | |||
| d5e7a17127 | |||
| 275f64b296 | |||
| 97ca62b083 | |||
| 32fb1550ff | |||
| d67972624e | |||
| 595cf6ead0 | |||
| 6faafe9fab | |||
| 66b90aac25 | |||
| d22855fc1a | |||
| 1be86bc3c5 | |||
| db3e8215fa | |||
| 2c4cef76d8 | |||
| a4ef9705f9 | |||
| 799c849570 | |||
| 799b7d8403 |
19
lib/core/adaptive_page_route.dart
Normal file
19
lib/core/adaptive_page_route.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,8 @@ class CustomTheme {
|
||||
static Color onBoxColor = const Color(0xFF181818);
|
||||
static Color boxBorder = const Color(0xFF272727);
|
||||
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 ====================
|
||||
static const double standardBorderRadius = 12.0;
|
||||
|
||||
@@ -9,7 +9,7 @@ class Match {
|
||||
final String name;
|
||||
final List<Player>? players;
|
||||
final Group? group;
|
||||
final Player? winner;
|
||||
Player? winner;
|
||||
|
||||
Match({
|
||||
String? id,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"data_successfully_imported": "Daten erfolgreich importiert",
|
||||
"days_ago": "vor {count} Tagen",
|
||||
"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_reading_file": "Fehler beim Lesen der Datei",
|
||||
"export_canceled": "Export abgebrochen",
|
||||
|
||||
@@ -277,7 +277,7 @@
|
||||
"data_successfully_imported": "Data successfully imported",
|
||||
"days_ago": "{count} days ago",
|
||||
"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_reading_file": "Error reading file",
|
||||
"export_canceled": "Export canceled",
|
||||
|
||||
@@ -209,7 +209,7 @@ abstract class AppLocalizations {
|
||||
/// Confirmation dialog for deleting all data
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete all data?'**
|
||||
/// **'Delete all data'**
|
||||
String get delete_all_data;
|
||||
|
||||
/// Error message when group creation fails
|
||||
|
||||
@@ -67,7 +67,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get delete => 'Löschen';
|
||||
|
||||
@override
|
||||
String get delete_all_data => 'Alle Daten löschen?';
|
||||
String get delete_all_data => 'Alle Daten löschen';
|
||||
|
||||
@override
|
||||
String get error_creating_group =>
|
||||
|
||||
@@ -67,7 +67,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get delete => 'Delete';
|
||||
|
||||
@override
|
||||
String get delete_all_data => 'Delete all data?';
|
||||
String get delete_all_data => 'Delete all data';
|
||||
|
||||
@override
|
||||
String get error_creating_group =>
|
||||
|
||||
@@ -44,6 +44,12 @@ class GameTracker extends StatelessWidget {
|
||||
seedColor: CustomTheme.primaryColor,
|
||||
brightness: Brightness.dark,
|
||||
).copyWith(surface: CustomTheme.backgroundColor),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
),
|
||||
home: const CustomNavigationBar(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
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/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
|
||||
@@ -56,7 +59,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SettingsView()),
|
||||
adaptivePageRoute(builder: (_) => const SettingsView()),
|
||||
);
|
||||
setState(() {
|
||||
tabKeyCount++;
|
||||
@@ -70,18 +73,61 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
body: tabs[currentIndex],
|
||||
extendBody: true,
|
||||
bottomNavigationBar: SafeArea(
|
||||
minimum: const EdgeInsets.only(bottom: 30),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: CustomTheme.primaryColor,
|
||||
bottomNavigationBar: SizedBox(
|
||||
height: 70 + MediaQuery.of(context).padding.bottom,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Dynamisch generierte Blur-Layer für ultra-smooth Übergang
|
||||
...List.generate(35, (index) {
|
||||
// Verwende kubische Kurve für noch natürlicheren, weicheren Übergang
|
||||
final progress = index / 34.0; // 0.0 bis 1.0
|
||||
final cubic = progress * progress * progress; // Kubische Kurve
|
||||
final blurStrength = 0.5 + (cubic * 50.0); // Sehr sanft von 0.5 bis 50.5
|
||||
|
||||
// Höhe geht jetzt komplett von 100% bis 0% (ganz nach unten)
|
||||
// Mit extra Dichte am unteren Ende für weicheren Übergang
|
||||
final heightFactor = index < 25
|
||||
? 1.0 - (progress * 0.7) // Erste 25 Layer: 100% bis 30%
|
||||
: 0.3 - ((index - 25) / 34.0); // Letzte 10 Layer: 30% bis 0% (dichter)
|
||||
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: (70 + MediaQuery.of(context).padding.bottom) * heightFactor.clamp(0.05, 1.0),
|
||||
child: ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: blurStrength,
|
||||
sigmaY: blurStrength,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
// Gradient-Overlay
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
CustomTheme.boxColor.withValues(alpha: 1),
|
||||
CustomTheme.boxColor.withValues(alpha: 0.0),
|
||||
],
|
||||
stops: const [0.4, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Navbar-Inhalt
|
||||
SafeArea(
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
height: 70,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
@@ -117,6 +163,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -44,7 +44,8 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Scaffold(
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(title: Text(loc.create_new_group)),
|
||||
body: SafeArea(
|
||||
@@ -106,6 +107,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
@@ -85,7 +86,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
adaptivePageRoute(
|
||||
builder: (context) {
|
||||
return const CreateGroupView();
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
@@ -44,7 +45,6 @@ class _HomeViewState extends State<HomeView> {
|
||||
Player(name: 'Skeleton Player 2'),
|
||||
],
|
||||
),
|
||||
winner: Player(name: 'Skeleton Player 1'),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -86,44 +86,47 @@ class _HomeViewState extends State<HomeView> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: InfoTile(
|
||||
width: constraints.maxWidth * 0.95,
|
||||
title: loc.recent_matches,
|
||||
icon: Icons.history_rounded,
|
||||
content: Column(
|
||||
children: [
|
||||
if (recentMatches.isNotEmpty)
|
||||
for (Match match in recentMatches)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
),
|
||||
child: MatchTile(
|
||||
compact: true,
|
||||
width: constraints.maxWidth * 0.95,
|
||||
match: recentMatches[0],
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
width: constraints.maxWidth * 0.9,
|
||||
match: match,
|
||||
onTap: () async {
|
||||
await Navigator.of(context).push(
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) =>
|
||||
MatchResultView(match: recentMatches[0]),
|
||||
MatchResultView(match: match),
|
||||
),
|
||||
);
|
||||
await updatedWinnerinRecentMatches(match.id);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Center(
|
||||
heightFactor: 5,
|
||||
child: Text(loc.no_recent_matches_available),
|
||||
),
|
||||
if (recentMatches.length > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: MatchTile(
|
||||
compact: true,
|
||||
width: constraints.maxWidth * 0.95,
|
||||
match: recentMatches[1],
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) =>
|
||||
MatchResultView(match: recentMatches[1]),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
padding: EdgeInsets.zero,
|
||||
child: InfoTile(
|
||||
width: constraints.maxWidth * 0.95,
|
||||
title: loc.quick_create,
|
||||
@@ -173,6 +176,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: MediaQuery.paddingOf(context).bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -183,7 +187,7 @@ 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() {
|
||||
Future<void> loadHomeViewData() async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
Future.wait([
|
||||
db.matchDao.getMatchCount(),
|
||||
@@ -206,4 +210,16 @@ class _HomeViewState extends State<HomeView> {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates the winner information for a specific match in the recent matches list.
|
||||
Future<void> updatedWinnerinRecentMatches(String matchId) async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
final winner = await db.matchDao.getWinner(matchId: matchId);
|
||||
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
|
||||
if (matchIndex != -1) {
|
||||
setState(() {
|
||||
recentMatches[matchIndex].winner = winner;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,11 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
filteredGroups.clear();
|
||||
filteredGroups.addAll(
|
||||
widget.groups.where(
|
||||
(group) => group.name.toLowerCase().contains(query.toLowerCase()),
|
||||
(group) =>
|
||||
group.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||
group.members.any(
|
||||
(player) => player.name.toLowerCase().contains(query.toLowerCase()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/cupertino.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/enums.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
@@ -119,7 +119,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Scaffold(
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(title: Text(loc.create_new_match)),
|
||||
body: SafeArea(
|
||||
@@ -140,7 +141,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
: games[selectedGameIndex].$1,
|
||||
onPressed: () async {
|
||||
selectedGameIndex = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseGameView(
|
||||
games: games,
|
||||
initialGameIndex: selectedGameIndex,
|
||||
@@ -168,7 +169,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
: translateRulesetToString(selectedRuleset!, context),
|
||||
onPressed: () async {
|
||||
selectedRuleset = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseRulesetView(
|
||||
rulesets: _rulesets,
|
||||
initialRulesetIndex: selectedRulesetIndex,
|
||||
@@ -190,7 +191,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
: selectedGroup!.name,
|
||||
onPressed: () async {
|
||||
selectedGroup = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseGroupView(
|
||||
groups: groupsList,
|
||||
initialGroupId: selectedGroupId,
|
||||
@@ -240,7 +241,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
match: match,
|
||||
@@ -255,6 +256,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:core' hide Match;
|
||||
|
||||
import 'package:flutter/cupertino.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/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
@@ -78,13 +78,15 @@ class _MatchViewState extends State<MatchView> {
|
||||
height: MediaQuery.paddingOf(context).bottom - 20,
|
||||
);
|
||||
}
|
||||
return Padding(
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: MatchTile(
|
||||
width: MediaQuery.sizeOf(context).width * 0.95,
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
match: matches[index],
|
||||
@@ -95,6 +97,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
},
|
||||
match: matches[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -102,19 +105,21 @@ class _MatchViewState extends State<MatchView> {
|
||||
),
|
||||
Positioned(
|
||||
bottom: MediaQuery.paddingOf(context).bottom,
|
||||
child: CustomWidthButton(
|
||||
child: SizedBox.shrink()
|
||||
/* CustomWidthButton(
|
||||
text: loc.create_match,
|
||||
sizeRelativeToWidth: 0.90,
|
||||
onPressed: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
adaptivePageRoute(
|
||||
builder: (context) =>
|
||||
CreateMatchView(onWinnerChanged: loadGames),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
*/
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class SettingsView extends StatefulWidget {
|
||||
const SettingsView({super.key});
|
||||
@@ -13,21 +14,26 @@ class SettingsView extends StatefulWidget {
|
||||
}
|
||||
|
||||
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 Scaffold(
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
body: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) =>
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -43,10 +49,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 10,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||
child: Text(
|
||||
textAlign: TextAlign.start,
|
||||
loc.settings,
|
||||
@@ -61,8 +64,9 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
icon: Icons.upload_rounded,
|
||||
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onPressed: () async {
|
||||
final String json =
|
||||
await DataTransferService.getAppDataAsJson(context);
|
||||
final String json = await DataTransferService.getAppDataAsJson(
|
||||
context,
|
||||
);
|
||||
final result = await DataTransferService.exportData(
|
||||
json,
|
||||
'game_tracker-data',
|
||||
@@ -76,9 +80,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
icon: Icons.download_rounded,
|
||||
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onPressed: () async {
|
||||
final result = await DataTransferService.importData(
|
||||
context,
|
||||
);
|
||||
final result = await DataTransferService.importData(context);
|
||||
if (!context.mounted) return;
|
||||
showImportSnackBar(context: context, result: result);
|
||||
},
|
||||
@@ -91,7 +93,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(loc.delete_all_data),
|
||||
title: Text('${loc.delete_all_data}?'),
|
||||
content: Text(loc.this_cannot_be_undone),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -117,10 +119,23 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Version ${_packageInfo.version} (${_packageInfo.buildNumber})',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,4 +209,11 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _initPackageInfo() async {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_packageInfo = info;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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.
|
||||
/// - [index]: The index of the tab.
|
||||
@@ -35,7 +36,45 @@ class NavbarItem extends StatefulWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
@@ -48,19 +87,29 @@ class _NavbarItemState extends State<NavbarItem> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
ScaleTransition(
|
||||
scale: widget.isSelected
|
||||
? _scaleAnimation
|
||||
: const AlwaysStoppedAnimation(1.0),
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
color: widget.isSelected ? Colors.white : Colors.black,
|
||||
color: widget.isSelected
|
||||
? CustomTheme.navBarItemSelectedColor
|
||||
: CustomTheme.navBarItemUnselectedColor,
|
||||
size: 26,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.label,
|
||||
style: TextStyle(
|
||||
color: widget.isSelected ? Colors.white : Colors.black,
|
||||
fontSize: 12,
|
||||
color: widget.isSelected
|
||||
? CustomTheme.navBarItemSelectedColor
|
||||
: CustomTheme.navBarItemUnselectedColor,
|
||||
fontSize: widget.isSelected ? 12 : 11,
|
||||
fontWeight: widget.isSelected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -69,4 +118,10 @@ class _NavbarItemState extends State<NavbarItem> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +58,7 @@ class _MatchTileState extends State<MatchTile> {
|
||||
margin: EdgeInsets.zero,
|
||||
width: widget.width,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
decoration: CustomTheme.standardBoxDecoration,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: game_tracker
|
||||
description: "Game Tracking App for Card Games"
|
||||
publish_to: 'none'
|
||||
version: 0.0.2+47
|
||||
version: 0.0.1+149
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
@@ -22,6 +22,7 @@ dependencies:
|
||||
intl: any
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
package_info_plus: ^9.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user