9 Commits

Author SHA1 Message Date
ab20bd764b implement draft blur navbar
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Failing after 2m6s
2026-01-11 11:30:00 +01:00
8ca4e3210e remove button in match view for testing 2026-01-11 11:28:21 +01:00
a480530919 Merge branch 'development' into enhancement/138-neues-navbar-design
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m31s
Pull Request Pipeline / lint (pull_request) Successful in 2m34s
# Conflicts:
#	pubspec.yaml
2026-01-10 22:18:54 +01:00
906c8d8450 Merge pull request 'Versions- und Build Nr in Einstellungen' (#149) from enhancement/146-versions-und-build-nr-in-einstellungen into development
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Successful in 2m5s
Reviewed-on: #149
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-10 20:56:25 +00:00
000bdc8cbc Merge remote-tracking branch 'origin/development' into enhancement/146-versions-und-build-nr-in-einstellungen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 21:54:09 +01:00
497f30421d Updated string
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m9s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-10 21:29:45 +01:00
2a72332bcd Updated error string
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-10 21:23:28 +01:00
7aa41abe61 Implemented version number 2026-01-10 16:20:21 +01:00
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
11 changed files with 277 additions and 151 deletions

View File

@@ -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;

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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 =>

View File

@@ -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 =>

View File

@@ -1,3 +1,5 @@
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';
@@ -71,53 +73,97 @@ 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,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
NavbarItem(
index: 0,
isSelected: currentIndex == 0,
icon: Icons.home_rounded,
label: loc.home,
onTabTapped: onTabTapped,
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: Container(
color: Colors.transparent,
),
),
NavbarItem(
index: 1,
isSelected: currentIndex == 1,
icon: Icons.gamepad_rounded,
label: loc.matches,
onTabTapped: onTabTapped,
),
);
}),
// 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],
),
NavbarItem(
index: 2,
isSelected: currentIndex == 2,
icon: Icons.group_rounded,
label: loc.groups,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 3,
isSelected: currentIndex == 3,
icon: Icons.bar_chart_rounded,
label: loc.statistics,
onTabTapped: onTabTapped,
),
],
),
),
),
),
// Navbar-Inhalt
SafeArea(
child: SizedBox(
height: 70,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
NavbarItem(
index: 0,
isSelected: currentIndex == 0,
icon: Icons.home_rounded,
label: loc.home,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 1,
isSelected: currentIndex == 1,
icon: Icons.gamepad_rounded,
label: loc.matches,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 2,
isSelected: currentIndex == 2,
icon: Icons.group_rounded,
label: loc.groups,
onTabTapped: onTabTapped,
),
NavbarItem(
index: 3,
isSelected: currentIndex == 3,
icon: Icons.bar_chart_rounded,
label: loc.statistics,
onTabTapped: onTabTapped,
),
],
),
),
),
],
),
),
);

View File

@@ -105,7 +105,8 @@ 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 {
@@ -118,6 +119,7 @@ class _MatchViewState extends State<MatchView> {
);
},
),
*/
),
],
),

View File

@@ -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,9 +14,16 @@ 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
@@ -25,102 +33,107 @@ class _SettingsViewState extends State<SettingsView> {
child: 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,
);
}
});
},
),
],
body: 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,
);
}
});
},
),
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,
),
),
),
),
],
),
),
);
@@ -196,4 +209,11 @@ class _SettingsViewState extends State<SettingsView> {
),
);
}
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: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(
widget.icon,
color: widget.isSelected ? Colors.white : Colors.black,
ScaleTransition(
scale: widget.isSelected
? _scaleAnimation
: const AlwaysStoppedAnimation(1.0),
child: Icon(
widget.icon,
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();
}
}

View File

@@ -1,7 +1,7 @@
name: game_tracker
description: "Game Tracking App for Card Games"
publish_to: 'none'
version: 0.0.3+93
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: