11 Commits

Author SHA1 Message Date
gelbeinhalb
e2fe0c7d4d remove group_match_dao
Some checks failed
Pull Request Pipeline / lint (pull_request) Failing after 2m18s
Pull Request Pipeline / test (pull_request) Failing after 2m42s
2026-01-12 11:42:03 +01:00
gelbeinhalb
b72ab70e02 add score table
Some checks failed
Pull Request Pipeline / lint (pull_request) Failing after 2m22s
Pull Request Pipeline / test (pull_request) Failing after 2m52s
2026-01-12 11:38:38 +01:00
gelbeinhalb
189daf76dd move createdAt below description 2026-01-12 11:35:57 +01:00
gelbeinhalb
0f987f4c7a move match below ids 2026-01-12 11:34:02 +01:00
gelbeinhalb
5dd8f31942 add description to group_table.dart
Some checks failed
Pull Request Pipeline / lint (pull_request) Failing after 2m29s
Pull Request Pipeline / test (pull_request) Failing after 2m54s
2026-01-12 11:33:00 +01:00
gelbeinhalb
0394f5edf9 delete group_match_table.dart 2026-01-12 11:32:23 +01:00
gelbeinhalb
d8abad6fd8 add groupid gameid and notes to match 2026-01-12 11:30:37 +01:00
gelbeinhalb
7e6c309de0 add game table 2026-01-12 11:26:39 +01:00
gelbeinhalb
3344575132 add team id and score
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
2026-01-12 11:20:40 +01:00
gelbeinhalb
9b66e58dc0 add team table 2026-01-12 11:17:13 +01:00
gelbeinhalb
56562b22bb add description to player 2026-01-12 11:16:02 +01:00
16 changed files with 108 additions and 287 deletions

View File

@@ -11,8 +11,6 @@ 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

@@ -1,98 +0,0 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/group_match_table.dart';
import 'package:game_tracker/data/dto/group.dart';
part 'group_match_dao.g.dart';
@DriftAccessor(tables: [GroupMatchTable])
class GroupMatchDao extends DatabaseAccessor<AppDatabase>
with _$GroupMatchDaoMixin {
GroupMatchDao(super.db);
/// Associates a group with a match by inserting a record into the
/// [GroupMatchTable].
Future<void> addGroupToMatch({
required String matchId,
required String groupId,
}) async {
if (await matchHasGroup(matchId: matchId)) {
throw Exception('Match already has a group');
}
await into(groupMatchTable).insert(
GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId),
mode: InsertMode.insertOrIgnore,
);
}
/// Retrieves the [Group] associated with the given [matchId].
/// Returns `null` if no group is found.
Future<Group?> getGroupOfMatch({required String matchId}) async {
final result = await (select(
groupMatchTable,
)..where((g) => g.matchId.equals(matchId))).getSingleOrNull();
if (result == null) {
return null;
}
final group = await db.groupDao.getGroupById(groupId: result.groupId);
return group;
}
/// Checks if there is a group associated with the given [matchId].
/// Returns `true` if there is a group, otherwise `false`.
Future<bool> matchHasGroup({required String matchId}) async {
final count =
await (selectOnly(groupMatchTable)
..where(groupMatchTable.matchId.equals(matchId))
..addColumns([groupMatchTable.groupId.count()]))
.map((row) => row.read(groupMatchTable.groupId.count()))
.getSingle();
return (count ?? 0) > 0;
}
/// Checks if a specific group is associated with a specific match.
/// Returns `true` if the group is in the match, otherwise `false`.
Future<bool> isGroupInMatch({
required String matchId,
required String groupId,
}) async {
final count =
await (selectOnly(groupMatchTable)
..where(
groupMatchTable.matchId.equals(matchId) &
groupMatchTable.groupId.equals(groupId),
)
..addColumns([groupMatchTable.groupId.count()]))
.map((row) => row.read(groupMatchTable.groupId.count()))
.getSingle();
return (count ?? 0) > 0;
}
/// Removes the association of a group from a match based on [groupId] and
/// [matchId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removeGroupFromMatch({
required String matchId,
required String groupId,
}) async {
final query = delete(groupMatchTable)
..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Updates the group associated with a match to [newGroupId] based on
/// [matchId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateGroupOfMatch({
required String matchId,
required String newGroupId,
}) async {
final updatedRows =
await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId)))
.write(GroupMatchTableCompanion(groupId: Value(newGroupId)));
return updatedRows > 0;
}
}

View File

@@ -1,10 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'group_match_dao.dart';
// ignore_for_file: type=lint
mixin _$GroupMatchDaoMixin on DatabaseAccessor<AppDatabase> {
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable;
}

View File

@@ -1,12 +1,10 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart'; import 'package:drift_flutter/drift_flutter.dart';
import 'package:game_tracker/data/dao/group_dao.dart'; import 'package:game_tracker/data/dao/group_dao.dart';
import 'package:game_tracker/data/dao/group_match_dao.dart';
import 'package:game_tracker/data/dao/match_dao.dart'; import 'package:game_tracker/data/dao/match_dao.dart';
import 'package:game_tracker/data/dao/player_dao.dart'; import 'package:game_tracker/data/dao/player_dao.dart';
import 'package:game_tracker/data/dao/player_group_dao.dart'; import 'package:game_tracker/data/dao/player_group_dao.dart';
import 'package:game_tracker/data/dao/player_match_dao.dart'; import 'package:game_tracker/data/dao/player_match_dao.dart';
import 'package:game_tracker/data/db/tables/group_match_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/db/tables/match_table.dart'; import 'package:game_tracker/data/db/tables/match_table.dart';
import 'package:game_tracker/data/db/tables/player_group_table.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart';
@@ -22,16 +20,14 @@ part 'database.g.dart';
GroupTable, GroupTable,
MatchTable, MatchTable,
PlayerGroupTable, PlayerGroupTable,
PlayerMatchTable, PlayerMatchTable
GroupMatchTable,
], ],
daos: [ daos: [
PlayerDao, PlayerDao,
GroupDao, GroupDao,
MatchDao, MatchDao,
PlayerGroupDao, PlayerGroupDao,
PlayerMatchDao, PlayerMatchDao
GroupMatchDao,
], ],
) )
class AppDatabase extends _$AppDatabase { class AppDatabase extends _$AppDatabase {

View File

@@ -0,0 +1,14 @@
import 'package:drift/drift.dart';
class GameTable extends Table {
TextColumn get id => text()();
TextColumn get name => text()();
TextColumn get ruleset => text()();
TextColumn get description => text().nullable()();
TextColumn get color => text().nullable()();
TextColumn get icon => text().nullable()();
DateTimeColumn get createdAt => dateTime()();
@override
Set<Column<Object>> get primaryKey => {id};
}

View File

@@ -1,13 +0,0 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/db/tables/match_table.dart';
class GroupMatchTable extends Table {
TextColumn get groupId =>
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
TextColumn get matchId =>
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
@override
Set<Column<Object>> get primaryKey => {groupId, matchId};
}

View File

@@ -3,6 +3,7 @@ import 'package:drift/drift.dart';
class GroupTable extends Table { class GroupTable extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get name => text()(); TextColumn get name => text()();
TextColumn get description => text().nullable()();
DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get createdAt => dateTime()();
@override @override

View File

@@ -1,9 +1,15 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/game_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
class MatchTable extends Table { class MatchTable extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get name => text()(); TextColumn get gameId =>
late final winnerId = text().nullable()(); text().references(GameTable, #id, onDelete: KeyAction.cascade)();
TextColumn get groupId =>
text().references(GroupTable, #id, onDelete: KeyAction.cascade).nullable()(); // Nullable if not part of a group
TextColumn get name => text().nullable()();
TextColumn get notes => text().nullable()();
DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get createdAt => dateTime()();
@override @override

View File

@@ -1,12 +1,16 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/match_table.dart'; import 'package:game_tracker/data/db/tables/match_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart';
import 'package:game_tracker/data/db/tables/team_table.dart';
class PlayerMatchTable extends Table { class PlayerMatchTable extends Table {
TextColumn get playerId => TextColumn get playerId =>
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get matchId => TextColumn get matchId =>
text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
TextColumn get teamId =>
text().references(TeamTable, #id).nullable()();
IntColumn get score => integer()();
@override @override
Set<Column<Object>> get primaryKey => {playerId, matchId}; Set<Column<Object>> get primaryKey => {playerId, matchId};

View File

@@ -3,6 +3,7 @@ import 'package:drift/drift.dart';
class PlayerTable extends Table { class PlayerTable extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get name => text()(); TextColumn get name => text()();
TextColumn get description => text().nullable()();
DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get createdAt => dateTime()();
@override @override

View File

@@ -0,0 +1,16 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/match_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
class ScoreTable extends Table {
TextColumn get playerId =>
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get matchId =>
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
IntColumn get roundNumber => integer()();
IntColumn get score => integer()();
IntColumn get change => integer()();
@override
Set<Column<Object>> get primaryKey => {playerId, matchId, roundNumber};
}

View File

@@ -0,0 +1,9 @@
import 'package:drift/drift.dart';
class TeamTable extends Table {
TextColumn get id => text()();
TextColumn get name => text()();
@override
Set<Column<Object>> get primaryKey => {id};
}

View File

@@ -1,5 +1,3 @@
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/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
@@ -73,97 +71,53 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
body: tabs[currentIndex], body: tabs[currentIndex],
extendBody: true, extendBody: true,
bottomNavigationBar: SizedBox( bottomNavigationBar: SafeArea(
height: 70 + MediaQuery.of(context).padding.bottom, minimum: const EdgeInsets.only(bottom: 30),
child: Stack( child: Container(
children: [ margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10),
// Dynamisch generierte Blur-Layer für ultra-smooth Übergang decoration: BoxDecoration(
...List.generate(35, (index) { borderRadius: BorderRadius.circular(24),
// Verwende kubische Kurve für noch natürlicheren, weicheren Übergang color: CustomTheme.primaryColor,
final progress = index / 34.0; // 0.0 bis 1.0 ),
final cubic = progress * progress * progress; // Kubische Kurve child: ClipRRect(
final blurStrength = 0.5 + (cubic * 50.0); // Sehr sanft von 0.5 bis 50.5 borderRadius: BorderRadius.circular(24),
child: SizedBox(
// Höhe geht jetzt komplett von 100% bis 0% (ganz nach unten) height: 60,
// Mit extra Dichte am unteren Ende für weicheren Übergang child: Row(
final heightFactor = index < 25 mainAxisAlignment: MainAxisAlignment.spaceAround,
? 1.0 - (progress * 0.7) // Erste 25 Layer: 100% bis 30% children: <Widget>[
: 0.3 - ((index - 25) / 34.0); // Letzte 10 Layer: 30% bis 0% (dichter) NavbarItem(
index: 0,
return Positioned( isSelected: currentIndex == 0,
left: 0, icon: Icons.home_rounded,
right: 0, label: loc.home,
bottom: 0, onTabTapped: onTabTapped,
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,
// Gradient-Overlay icon: Icons.gamepad_rounded,
Positioned.fill( label: loc.matches,
child: Container( onTabTapped: onTabTapped,
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,8 +105,7 @@ class _MatchViewState extends State<MatchView> {
), ),
Positioned( Positioned(
bottom: MediaQuery.paddingOf(context).bottom, bottom: MediaQuery.paddingOf(context).bottom,
child: SizedBox.shrink() child: CustomWidthButton(
/* CustomWidthButton(
text: loc.create_match, text: loc.create_match,
sizeRelativeToWidth: 0.90, sizeRelativeToWidth: 0.90,
onPressed: () async { onPressed: () async {
@@ -119,7 +118,6 @@ class _MatchViewState extends State<MatchView> {
); );
}, },
), ),
*/
), ),
], ],
), ),

View File

@@ -1,5 +1,4 @@
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.
@@ -36,45 +35,7 @@ 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(
@@ -87,29 +48,19 @@ class _NavbarItemState extends State<NavbarItem>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ScaleTransition( Icon(
scale: widget.isSelected widget.icon,
? _scaleAnimation color: widget.isSelected ? Colors.white : Colors.black,
: 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 color: widget.isSelected ? Colors.white : Colors.black,
? CustomTheme.navBarItemSelectedColor fontSize: 12,
: CustomTheme.navBarItemUnselectedColor,
fontSize: widget.isSelected ? 12 : 11,
fontWeight: widget.isSelected fontWeight: widget.isSelected
? FontWeight.bold ? FontWeight.bold
: FontWeight.w500, : FontWeight.normal,
), ),
), ),
], ],
@@ -118,10 +69,4 @@ class _NavbarItemState extends State<NavbarItem>
), ),
); );
} }
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
} }

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.1+149 version: 0.0.4+101
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1