38 Commits

Author SHA1 Message Date
1ebcfc9e57 implement animation
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
2026-01-11 17:21:05 +01:00
22ce742d43 change button alignment & remove InkWell Animation
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m10s
2026-01-11 17:14:28 +01:00
3ceae8341b add const
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m30s
Pull Request Pipeline / lint (pull_request) Successful in 2m31s
2026-01-11 15:26:50 +01:00
76ce3af643 implement custom alert dialog
Some checks failed
Pull Request Pipeline / lint (pull_request) Failing after 2m34s
Pull Request Pipeline / test (pull_request) Successful in 2m33s
2026-01-11 15:26:10 +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 1m58s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s
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
adedb85eb2 Merge pull request 'Gruppen nach Spielern durchsuchen' (#147) from enhancement/136-gruppen-nach-spielern-durchsuchen into development
Reviewed-on: #147
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 20:27:53 +00: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
e1263d51ad Merge branch 'development' into enhancement/136-gruppen-nach-spielern-durchsuchen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-10 15:06:17 +00:00
8791b5296e Merge pull request 'MatchSummaryTile durch MatchTile ersetzen' (#142) from enhancement/137-matchsummarytile-durch-matchtile-ersetzen into development
Reviewed-on: #142
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-10 15:05:42 +00:00
f2a4327166 Implemented adaptive page route
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 15:55:19 +01:00
d34990ed50 Removed print 2026-01-10 15:54:41 +01:00
2ef671884d New method for specific winner refreshing 2026-01-10 15:54:11 +01:00
2ef8eb6534 Made winner field non final 2026-01-10 15:53:31 +01:00
6f0e5ba5c2 add ability to search for groups members in choose_group_view.dart
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m3s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 15:19:18 +01:00
1c07346aaf Updated version
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m20s
Pull Request Pipeline / lint (pull_request) Successful in 2m28s
2026-01-10 15:16:09 +01:00
830a64b5dd Small changes
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m29s
Pull Request Pipeline / lint (pull_request) Successful in 2m32s
2026-01-10 15:15:52 +01:00
9221f64fa5 Merge branch 'development' into enhancement/137-matchsummarytile-durch-matchtile-ersetzen
# Conflicts:
#	lib/presentation/views/main_menu/match_view/match_view.dart
#	pubspec.yaml
2026-01-10 15:15:14 +01:00
c4f6749882 Fixed state issue in home view
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m2s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-10 15:13:56 +01:00
14a043785e Merge pull request 'PageRoutes adaptable machen' (#145) from enhancement/128-PageRoutes-adaptable-machen into development
Reviewed-on: #145
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 14:06:10 +00:00
9eb9c0eb7f remove unneccessary import
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m18s
Pull Request Pipeline / lint (pull_request) Successful in 2m18s
2026-01-10 15:02:01 +01:00
54ec865f04 fix merge issues
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m14s
Pull Request Pipeline / lint (pull_request) Failing after 2m23s
2026-01-10 15:00:33 +01:00
d5e7a17127 Merge remote-tracking branch 'origin/development' into enhancement/128-PageRoutes-adaptable-machen
# Conflicts:
#	lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart
2026-01-10 14:57:33 +01:00
275f64b296 Merge pull request 'SnackBar Sichtbarkeit' (#144) from bug/130-Snackbar-Sichtbarkeit into development
Reviewed-on: #144
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 13:54:57 +00:00
97ca62b083 refactor: rename AdaptivePageRoute to adaptivePageRoute for lowerCamelCase
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m13s
Pull Request Pipeline / test (pull_request) Successful in 2m18s
2026-01-10 14:52:59 +01:00
32fb1550ff Merge remote-tracking branch 'origin/enhancement/128-PageRoutes-adaptable-machen' into enhancement/128-PageRoutes-adaptable-machen
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Failing after 2m10s
# Conflicts:
#	lib/main.dart
2026-01-10 14:44:53 +01:00
d67972624e replaced ternary operator solution with custom adaptive_page_route.dart 2026-01-10 14:44:33 +01:00
595cf6ead0 Changed skeleton data from 3 to 2 tiles
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m2s
Pull Request Pipeline / lint (pull_request) Successful in 2m4s
2026-01-10 14:20:09 +01:00
6faafe9fab Changed android page transition to Android U transition
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m5s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
2026-01-10 14:16:16 +01:00
66b90aac25 Fixed issue with android page transition
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m5s
2026-01-10 14:09:08 +01:00
d22855fc1a Fixed match view tile width
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 13:57:54 +01:00
1be86bc3c5 Added theme box design 2026-01-10 13:57:46 +01:00
db3e8215fa Added ios swipe back gesture
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 13:42:09 +01:00
2c4cef76d8 Reimplemented info tile & match tile into it
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-09 23:55:29 +01:00
a4ef9705f9 made PageRoutes adapt to os, default to MaterialPageRoute
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s
2026-01-09 22:22:26 +01:00
799c849570 wrap every view that uses a snackbar in ScaffoldMessenger
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-09 21:55:58 +01:00
19 changed files with 511 additions and 351 deletions

View 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,
);
}

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;
final Player? winner; Player? winner;
Match({ Match({
String? id, String? id,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,12 @@ 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,4 +1,5 @@
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';
@@ -56,7 +57,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute(builder: (_) => const SettingsView()), adaptivePageRoute(builder: (_) => const SettingsView()),
); );
setState(() { setState(() {
tabKeyCount++; tabKeyCount++;

View File

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

View File

@@ -1,4 +1,5 @@
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';
@@ -85,7 +86,7 @@ class _GroupsViewState extends State<GroupsView> {
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( adaptivePageRoute(
builder: (context) { builder: (context) {
return const CreateGroupView(); return const CreateGroupView();
}, },

View File

@@ -1,4 +1,5 @@
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';
@@ -44,7 +45,6 @@ class _HomeViewState extends State<HomeView> {
Player(name: 'Skeleton Player 2'), Player(name: 'Skeleton Player 2'),
], ],
), ),
winner: Player(name: 'Skeleton Player 1'),
), ),
); );
@@ -86,44 +86,47 @@ class _HomeViewState extends State<HomeView> {
), ),
], ],
), ),
if (recentMatches.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: MatchTile(
compact: true,
width: constraints.maxWidth * 0.95,
match: recentMatches[0],
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) =>
MatchResultView(match: recentMatches[0]),
),
);
},
),
),
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(
padding: const EdgeInsets.only(top: 8.0), 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.symmetric(
vertical: 6.0,
),
child: MatchTile(
compact: true,
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),
),
],
),
),
),
Padding(
padding: EdgeInsets.zero,
child: InfoTile( child: InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: loc.quick_create, 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. /// 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.
void loadHomeViewData() { Future<void> loadHomeViewData() async {
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(),
@@ -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;
});
}
}
} }

View File

@@ -140,7 +140,11 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
filteredGroups.clear(); filteredGroups.clear();
filteredGroups.addAll( filteredGroups.addAll(
widget.groups.where( 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()),
),
), ),
); );
} }

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,140 +119,142 @@ 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 Scaffold( return ScaffoldMessenger(
backgroundColor: CustomTheme.backgroundColor, child: Scaffold(
appBar: AppBar(title: Text(loc.create_new_match)), backgroundColor: CustomTheme.backgroundColor,
body: SafeArea( appBar: AppBar(title: Text(loc.create_new_match)),
child: Column( body: SafeArea(
mainAxisAlignment: MainAxisAlignment.start, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.start,
Container( children: [
margin: CustomTheme.tileMargin, Container(
child: TextInputField( margin: CustomTheme.tileMargin,
controller: _matchNameController, child: TextInputField(
hintText: hintText ?? '', controller: _matchNameController,
hintText: hintText ?? '',
),
), ),
), ChooseTile(
ChooseTile( title: loc.game,
title: loc.game, trailingText: selectedGameIndex == -1
trailingText: selectedGameIndex == -1 ? loc.none
? loc.none : games[selectedGameIndex].$1,
: games[selectedGameIndex].$1, onPressed: () async {
onPressed: () async { selectedGameIndex = await Navigator.of(context).push(
selectedGameIndex = await Navigator.of(context).push( adaptivePageRoute(
MaterialPageRoute( builder: (context) => ChooseGameView(
builder: (context) => ChooseGameView( games: games,
games: games, initialGameIndex: selectedGameIndex,
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(() {
selectedPlayers = value; 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(
CustomWidthButton( title: loc.ruleset,
text: loc.create_match, trailingText: selectedRuleset == null
sizeRelativeToWidth: 0.95, ? loc.none
buttonType: ButtonType.primary, : translateRulesetToString(selectedRuleset!, context),
onPressed: _enableCreateGameButton() onPressed: () async {
? () async { selectedRuleset = await Navigator.of(context).push(
Match match = Match( adaptivePageRoute(
name: _matchNameController.text.isEmpty builder: (context) => ChooseRulesetView(
? (hintText ?? '') rulesets: _rulesets,
: _matchNameController.text.trim(), initialRulesetIndex: selectedRulesetIndex,
createdAt: DateTime.now(), ),
group: selectedGroup, ),
players: selectedPlayers, );
); if (!mounted) return;
await db.matchDao.addMatch(match: match); selectedRulesetIndex = _rulesets.indexWhere(
if (context.mounted) { (r) => r.$1 == selectedRuleset,
Navigator.pushReplacement( );
context, selectedGameIndex = -1;
CupertinoPageRoute( setState(() {});
fullscreenDialog: true, },
builder: (context) => MatchResultView( ),
match: match, ChooseTile(
onWinnerChanged: widget.onWinnerChanged, title: loc.group,
), 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,22 +78,25 @@ class _MatchViewState extends State<MatchView> {
height: MediaQuery.paddingOf(context).bottom - 20, height: MediaQuery.paddingOf(context).bottom - 20,
); );
} }
return Padding( return Center(
padding: const EdgeInsets.only(bottom: 12.0), child: Padding(
child: MatchTile( padding: const EdgeInsets.only(bottom: 12.0),
onTap: () async { child: MatchTile(
Navigator.push( width: MediaQuery.sizeOf(context).width * 0.95,
context, onTap: () async {
CupertinoPageRoute( Navigator.push(
fullscreenDialog: true, context,
builder: (context) => MatchResultView( adaptivePageRoute(
match: matches[index], fullscreenDialog: true,
onWinnerChanged: loadGames, builder: (context) => MatchResultView(
match: matches[index],
onWinnerChanged: loadGames,
),
), ),
), );
); },
}, match: matches[index],
match: matches[index], ),
), ),
); );
}, },
@@ -108,7 +111,7 @@ class _MatchViewState extends State<MatchView> {
onPressed: () async { onPressed: () async {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( adaptivePageRoute(
builder: (context) => builder: (context) =>
CreateMatchView(onWinnerChanged: loadGames), CreateMatchView(onWinnerChanged: loadGames),
), ),

View File

@@ -4,6 +4,8 @@ import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.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/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:game_tracker/services/data_transfer_service.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart';
class SettingsView extends StatefulWidget { class SettingsView extends StatefulWidget {
const SettingsView({super.key}); const SettingsView({super.key});
@@ -13,113 +15,127 @@ class SettingsView extends StatefulWidget {
} }
class _SettingsViewState extends State<SettingsView> { class _SettingsViewState extends State<SettingsView> {
PackageInfo _packageInfo = PackageInfo(
appName: 'n.A.',
packageName: 'n.A.',
version: 'n.A.',
buildNumber: 'n.A.',
);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initPackageInfo();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return Scaffold( return ScaffoldMessenger(
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), child: Scaffold(
backgroundColor: CustomTheme.backgroundColor, appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
body: LayoutBuilder( backgroundColor: CustomTheme.backgroundColor,
builder: (BuildContext context, BoxConstraints constraints) => body: Column(
SingleChildScrollView( mainAxisAlignment: MainAxisAlignment.start,
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
crossAxisAlignment: CrossAxisAlignment.start, Padding(
children: [ padding: const EdgeInsets.fromLTRB(24, 0, 24, 10),
Padding( child: Text(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), textAlign: TextAlign.start,
child: Text( loc.menu,
textAlign: TextAlign.start, style: const TextStyle(
loc.menu, fontSize: 28,
style: const TextStyle( fontWeight: FontWeight.bold,
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,
);
}
});
},
),
],
), ),
), ),
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) => CustomAlertDialog(
title: '${loc.delete_all_data}?',
content: loc.this_cannot_be_undone,
actions: [
_PressableButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel, style: const TextStyle(color: CustomTheme.textColor)),
),
_PressableButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete, style: TextStyle(color: CustomTheme.secondaryColor)),
),
],
),
).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,
),
),
),
),
],
),
), ),
); );
} }
@@ -194,4 +210,67 @@ class _SettingsViewState extends State<SettingsView> {
), ),
); );
} }
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
} }
class _PressableButton extends StatefulWidget {
final VoidCallback onPressed;
final Widget child;
const _PressableButton({required this.onPressed, required this.child});
@override
State<_PressableButton> createState() => _PressableButtonState();
}
class _PressableButtonState extends State<_PressableButton> {
bool _isPressed = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _isPressed = true),
onTapUp: (_) => setState(() => _isPressed = false),
onTapCancel: () => setState(() => _isPressed = false),
onTap: widget.onPressed,
child: AnimatedScale(
scale: _isPressed ? 0.95 : 1.0,
duration: const Duration(milliseconds: 100),
child: AnimatedOpacity(
opacity: _isPressed ? 0.6 : 1.0,
duration: const Duration(milliseconds: 100),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: widget.child,
),
),
),
);
}
}
/*
TextButton(
style: TextButton.styleFrom(
splashFactory: NoSplash.splashFactory,
overlayColor: Colors.transparent,
),
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel, style: const TextStyle(color: CustomTheme.textColor),),
),
TextButton(
style: TextButton.styleFrom(
splashFactory: NoSplash.splashFactory,
overlayColor: Colors.transparent,
),
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete, style: TextStyle(color: CustomTheme.secondaryColor),),
),
*/

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
class CustomAlertDialog extends StatelessWidget {
final String title;
final String content;
final List<Widget> actions;
const CustomAlertDialog({
super.key,
required this.title,
required this.content,
required this.actions,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(title, style: const TextStyle(color: CustomTheme.textColor,),),
content: Text(content, style: const TextStyle(color: CustomTheme.textColor),),
actions: actions,
backgroundColor: CustomTheme.boxColor,
actionsAlignment: MainAxisAlignment.spaceAround,
shape: RoundedRectangleBorder(
borderRadius: CustomTheme.standardBorderRadiusAll,
side: BorderSide(color: CustomTheme.boxBorder),
),
);
}
}

View File

@@ -58,11 +58,7 @@ class _MatchTileState extends State<MatchTile> {
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
width: widget.width, width: widget.width,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: CustomTheme.standardBoxDecoration,
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: [

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.2+47 version: 0.0.4+101
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
@@ -22,6 +22,7 @@ dependencies:
intl: any intl: any
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
package_info_plus: ^9.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: