Compare commits
4 Commits
feature/88
...
ab20bd764b
| Author | SHA1 | Date | |
|---|---|---|---|
| ab20bd764b | |||
| 8ca4e3210e | |||
| a480530919 | |||
| 799b7d8403 |
@@ -11,6 +11,8 @@ class CustomTheme {
|
|||||||
static Color onBoxColor = const Color(0xFF181818);
|
static Color onBoxColor = const Color(0xFF181818);
|
||||||
static Color boxBorder = const Color(0xFF272727);
|
static Color boxBorder = const Color(0xFF272727);
|
||||||
static const Color textColor = Colors.white;
|
static const Color textColor = Colors.white;
|
||||||
|
static Color navBarItemSelectedColor = primaryColor.withValues(green: 0.4);
|
||||||
|
static Color navBarItemUnselectedColor = Colors.white.withValues(alpha: 0.6);
|
||||||
|
|
||||||
// ==================== Border Radius ====================
|
// ==================== Border Radius ====================
|
||||||
static const double standardBorderRadius = 12.0;
|
static const double standardBorderRadius = 12.0;
|
||||||
|
|||||||
98
lib/data/dao/group_match_dao.dart
Normal file
98
lib/data/dao/group_match_dao.dart
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lib/data/dao/group_match_dao.g.dart
Normal file
10
lib/data/dao/group_match_dao.g.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
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';
|
||||||
@@ -20,14 +22,16 @@ 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 {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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};
|
|
||||||
}
|
|
||||||
13
lib/data/db/tables/group_match_table.dart
Normal file
13
lib/data/db/tables/group_match_table.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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};
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ 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
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
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 gameId =>
|
TextColumn get name => text()();
|
||||||
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
late final winnerId = text().nullable()();
|
||||||
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
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
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};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
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};
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
class TeamTable extends Table {
|
|
||||||
TextColumn get id => text()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
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';
|
||||||
@@ -71,53 +73,97 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
backgroundColor: CustomTheme.backgroundColor,
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
body: tabs[currentIndex],
|
body: tabs[currentIndex],
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
bottomNavigationBar: SafeArea(
|
bottomNavigationBar: SizedBox(
|
||||||
minimum: const EdgeInsets.only(bottom: 30),
|
height: 70 + MediaQuery.of(context).padding.bottom,
|
||||||
child: Container(
|
child: Stack(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10),
|
children: [
|
||||||
decoration: BoxDecoration(
|
// Dynamisch generierte Blur-Layer für ultra-smooth Übergang
|
||||||
borderRadius: BorderRadius.circular(24),
|
...List.generate(35, (index) {
|
||||||
color: CustomTheme.primaryColor,
|
// Verwende kubische Kurve für noch natürlicheren, weicheren Übergang
|
||||||
),
|
final progress = index / 34.0; // 0.0 bis 1.0
|
||||||
child: ClipRRect(
|
final cubic = progress * progress * progress; // Kubische Kurve
|
||||||
borderRadius: BorderRadius.circular(24),
|
final blurStrength = 0.5 + (cubic * 50.0); // Sehr sanft von 0.5 bis 50.5
|
||||||
child: SizedBox(
|
|
||||||
height: 60,
|
// Höhe geht jetzt komplett von 100% bis 0% (ganz nach unten)
|
||||||
child: Row(
|
// Mit extra Dichte am unteren Ende für weicheren Übergang
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
final heightFactor = index < 25
|
||||||
children: <Widget>[
|
? 1.0 - (progress * 0.7) // Erste 25 Layer: 100% bis 30%
|
||||||
NavbarItem(
|
: 0.3 - ((index - 25) / 34.0); // Letzte 10 Layer: 30% bis 0% (dichter)
|
||||||
index: 0,
|
|
||||||
isSelected: currentIndex == 0,
|
return Positioned(
|
||||||
icon: Icons.home_rounded,
|
left: 0,
|
||||||
label: loc.home,
|
right: 0,
|
||||||
onTabTapped: onTabTapped,
|
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,
|
// Gradient-Overlay
|
||||||
label: loc.matches,
|
Positioned.fill(
|
||||||
onTabTapped: onTabTapped,
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: MediaQuery.paddingOf(context).bottom,
|
bottom: MediaQuery.paddingOf(context).bottom,
|
||||||
child: CustomWidthButton(
|
child: SizedBox.shrink()
|
||||||
|
/* CustomWidthButton(
|
||||||
text: loc.create_match,
|
text: loc.create_match,
|
||||||
sizeRelativeToWidth: 0.90,
|
sizeRelativeToWidth: 0.90,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -118,6 +119,7 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
*/
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:game_tracker/core/custom_theme.dart';
|
||||||
|
|
||||||
/// A navigation bar item widget that represents a single tab in a navigation bar.
|
/// A navigation bar item widget that represents a single tab in a navigation bar.
|
||||||
/// - [index]: The index of the tab.
|
/// - [index]: The index of the tab.
|
||||||
@@ -35,7 +36,45 @@ class NavbarItem extends StatefulWidget {
|
|||||||
State<NavbarItem> createState() => _NavbarItemState();
|
State<NavbarItem> createState() => _NavbarItemState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NavbarItemState extends State<NavbarItem> {
|
class _NavbarItemState extends State<NavbarItem>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
/// Animation controller for the scale animation
|
||||||
|
late AnimationController _animationController;
|
||||||
|
|
||||||
|
/// Scale animation for the icon when selected
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOutBack,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (widget.isSelected) {
|
||||||
|
_animationController.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrigger animation on selection change
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(NavbarItem oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.isSelected && !oldWidget.isSelected) {
|
||||||
|
_animationController.forward();
|
||||||
|
} else if (!widget.isSelected && oldWidget.isSelected) {
|
||||||
|
_animationController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
@@ -48,19 +87,29 @@ class _NavbarItemState extends State<NavbarItem> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
ScaleTransition(
|
||||||
widget.icon,
|
scale: widget.isSelected
|
||||||
color: widget.isSelected ? Colors.white : Colors.black,
|
? _scaleAnimation
|
||||||
|
: const AlwaysStoppedAnimation(1.0),
|
||||||
|
child: Icon(
|
||||||
|
widget.icon,
|
||||||
|
color: widget.isSelected
|
||||||
|
? CustomTheme.navBarItemSelectedColor
|
||||||
|
: CustomTheme.navBarItemUnselectedColor,
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.label,
|
widget.label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: widget.isSelected ? Colors.white : Colors.black,
|
color: widget.isSelected
|
||||||
fontSize: 12,
|
? CustomTheme.navBarItemSelectedColor
|
||||||
|
: CustomTheme.navBarItemUnselectedColor,
|
||||||
|
fontSize: widget.isSelected ? 12 : 11,
|
||||||
fontWeight: widget.isSelected
|
fontWeight: widget.isSelected
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -69,4 +118,10 @@ class _NavbarItemState extends State<NavbarItem> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.4+101
|
version: 0.0.1+149
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user