Merge branch 'development' into feature/202-live-edit-modus
This commit is contained in:
@@ -3,7 +3,6 @@ import 'package:tallee/core/adaptive_page_route.dart';
|
|||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart';
|
import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/home_view.dart';
|
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart';
|
import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/statistics_view.dart';
|
import 'package:tallee/presentation/views/main_menu/statistics_view.dart';
|
||||||
@@ -31,7 +30,6 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
// Pretty ugly but works
|
// Pretty ugly but works
|
||||||
final List<Widget> tabs = [
|
final List<Widget> tabs = [
|
||||||
KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()),
|
|
||||||
KeyedSubtree(
|
KeyedSubtree(
|
||||||
key: ValueKey('matches_$tabKeyCount'),
|
key: ValueKey('matches_$tabKeyCount'),
|
||||||
child: const MatchView(),
|
child: const MatchView(),
|
||||||
@@ -101,27 +99,20 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
NavbarItem(
|
NavbarItem(
|
||||||
index: 0,
|
index: 0,
|
||||||
isSelected: currentIndex == 0,
|
isSelected: currentIndex == 0,
|
||||||
icon: Icons.home_rounded,
|
|
||||||
label: loc.home,
|
|
||||||
onTabTapped: onTabTapped,
|
|
||||||
),
|
|
||||||
NavbarItem(
|
|
||||||
index: 1,
|
|
||||||
isSelected: currentIndex == 1,
|
|
||||||
icon: Icons.gamepad_rounded,
|
icon: Icons.gamepad_rounded,
|
||||||
label: loc.matches,
|
label: loc.matches,
|
||||||
onTabTapped: onTabTapped,
|
onTabTapped: onTabTapped,
|
||||||
),
|
),
|
||||||
NavbarItem(
|
NavbarItem(
|
||||||
index: 2,
|
index: 1,
|
||||||
isSelected: currentIndex == 2,
|
isSelected: currentIndex == 1,
|
||||||
icon: Icons.group_rounded,
|
icon: Icons.group_rounded,
|
||||||
label: loc.groups,
|
label: loc.groups,
|
||||||
onTabTapped: onTabTapped,
|
onTabTapped: onTabTapped,
|
||||||
),
|
),
|
||||||
NavbarItem(
|
NavbarItem(
|
||||||
index: 3,
|
index: 2,
|
||||||
isSelected: currentIndex == 3,
|
isSelected: currentIndex == 2,
|
||||||
icon: Icons.bar_chart_rounded,
|
icon: Icons.bar_chart_rounded,
|
||||||
label: loc.statistics,
|
label: loc.statistics,
|
||||||
onTabTapped: onTabTapped,
|
onTabTapped: onTabTapped,
|
||||||
@@ -145,12 +136,10 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
switch (currentIndex) {
|
switch (currentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return loc.home;
|
|
||||||
case 1:
|
|
||||||
return loc.matches;
|
return loc.matches;
|
||||||
case 2:
|
case 1:
|
||||||
return loc.groups;
|
return loc.groups;
|
||||||
case 3:
|
case 2:
|
||||||
return loc.statistics;
|
return loc.statistics;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
|
||||||
import 'package:tallee/core/constants.dart';
|
|
||||||
import 'package:tallee/core/enums.dart';
|
|
||||||
import 'package:tallee/data/db/database.dart';
|
|
||||||
import 'package:tallee/data/models/game.dart';
|
|
||||||
import 'package:tallee/data/models/group.dart';
|
|
||||||
import 'package:tallee/data/models/match.dart';
|
|
||||||
import 'package:tallee/data/models/player.dart';
|
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/buttons/quick_create_button.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/tiles/match_tile.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/tiles/quick_info_tile.dart';
|
|
||||||
|
|
||||||
class HomeView extends StatefulWidget {
|
|
||||||
/// The main home view of the application, displaying quick info,
|
|
||||||
/// recent matches, and quick create options.
|
|
||||||
const HomeView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HomeView> createState() => _HomeViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HomeViewState extends State<HomeView> {
|
|
||||||
bool isLoading = true;
|
|
||||||
|
|
||||||
/// Amount of matches in the database
|
|
||||||
int matchCount = 0;
|
|
||||||
|
|
||||||
/// Amount of groups in the database
|
|
||||||
int groupCount = 0;
|
|
||||||
|
|
||||||
/// Loaded recent matches from the database
|
|
||||||
List<Match> loadedRecentMatches = [];
|
|
||||||
|
|
||||||
/// Recent matches to display, initially filled with skeleton matches
|
|
||||||
List<Match> recentMatches = List.filled(
|
|
||||||
2,
|
|
||||||
Match(
|
|
||||||
name: 'Skeleton Match',
|
|
||||||
game: Game(
|
|
||||||
name: 'Skeleton Game',
|
|
||||||
ruleset: Ruleset.singleWinner,
|
|
||||||
description: 'This is a skeleton game description.',
|
|
||||||
color: GameColor.blue,
|
|
||||||
icon: '',
|
|
||||||
),
|
|
||||||
group: Group(
|
|
||||||
name: 'Skeleton Group',
|
|
||||||
description: 'This is a skeleton group description.',
|
|
||||||
members: [
|
|
||||||
Player(
|
|
||||||
name:
|
|
||||||
'Skeleton Player 1'
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
Player(
|
|
||||||
name:
|
|
||||||
'Skeleton Player 2'
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
notes: 'These are skeleton notes.',
|
|
||||||
players: [
|
|
||||||
Player(
|
|
||||||
name:
|
|
||||||
'Skeleton Player 1'
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
Player(
|
|
||||||
name:
|
|
||||||
'Skeleton Player 2'
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
loadHomeViewData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final loc = AppLocalizations.of(context);
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
return AppSkeleton(
|
|
||||||
fixLayoutBuilder: true,
|
|
||||||
enabled: isLoading,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QuickInfoTile(
|
|
||||||
width: constraints.maxWidth * 0.45,
|
|
||||||
height: constraints.maxHeight * 0.13,
|
|
||||||
title: loc.matches,
|
|
||||||
icon: Icons.groups_rounded,
|
|
||||||
value: matchCount,
|
|
||||||
),
|
|
||||||
SizedBox(width: constraints.maxWidth * 0.05),
|
|
||||||
QuickInfoTile(
|
|
||||||
width: constraints.maxWidth * 0.45,
|
|
||||||
height: constraints.maxHeight * 0.13,
|
|
||||||
title: loc.groups,
|
|
||||||
icon: Icons.groups_rounded,
|
|
||||||
value: groupCount,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
|
||||||
child: InfoTile(
|
|
||||||
width: constraints.maxWidth * 0.95,
|
|
||||||
title: loc.recent_matches,
|
|
||||||
icon: Icons.history_rounded,
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
if (recentMatches.isNotEmpty)
|
|
||||||
for (Match match in recentMatches)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.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 loadRecentMatches();
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
print('loaded');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Center(
|
|
||||||
heightFactor: 5,
|
|
||||||
child: Text(loc.no_recent_matches_available),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
child: InfoTile(
|
|
||||||
width: constraints.maxWidth * 0.95,
|
|
||||||
title: loc.quick_create,
|
|
||||||
icon: Icons.add_box_rounded,
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 1',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 2',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 3',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 4',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 5',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
QuickCreateButton(
|
|
||||||
text: 'Category 6',
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: MediaQuery.paddingOf(context).bottom),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads the data for the HomeView from the database.
|
|
||||||
/// This includes the match count, group count, and recent matches.
|
|
||||||
Future<void> loadHomeViewData() async {
|
|
||||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
|
||||||
Future.wait([
|
|
||||||
db.matchDao.getMatchCount(),
|
|
||||||
db.groupDao.getGroupCount(),
|
|
||||||
db.matchDao.getAllMatches(),
|
|
||||||
Future.delayed(Constants.MINIMUM_SKELETON_DURATION),
|
|
||||||
]).then((results) {
|
|
||||||
matchCount = results[0] as int;
|
|
||||||
groupCount = results[1] as int;
|
|
||||||
loadedRecentMatches = results[2] as List<Match>;
|
|
||||||
recentMatches =
|
|
||||||
(loadedRecentMatches
|
|
||||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
|
||||||
.take(2)
|
|
||||||
.toList();
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadRecentMatches() async {
|
|
||||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
|
||||||
final matches = await db.matchDao.getAllMatches();
|
|
||||||
recentMatches =
|
|
||||||
(matches..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
|
||||||
.take(2)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import 'package:tallee/data/models/game.dart';
|
|||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
import 'package:tallee/data/models/match.dart';
|
import 'package:tallee/data/models/match.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_detail_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/match_detail_view.dart';
|
||||||
@@ -30,8 +31,7 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
|
||||||
/// Loaded matches from the database,
|
/// Loaded matches from the database, initially filled with skeleton matches
|
||||||
/// initially filled with skeleton matches
|
|
||||||
List<Match> matches = List.filled(
|
List<Match> matches = List.filled(
|
||||||
4,
|
4,
|
||||||
Match(
|
Match(
|
||||||
@@ -46,7 +46,15 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
name: 'Group name',
|
name: 'Group name',
|
||||||
members: List.filled(5, Player(name: 'Player')),
|
members: List.filled(5, Player(name: 'Player')),
|
||||||
),
|
),
|
||||||
players: [Player(name: 'Player')],
|
players: [
|
||||||
|
Player(name: 'Player'),
|
||||||
|
Player(name: 'Player'),
|
||||||
|
Player(name: 'Player'),
|
||||||
|
Player(name: 'Player'),
|
||||||
|
Player(id: 'mvp_id', name: 'Player'),
|
||||||
|
],
|
||||||
|
scores: {'mvp_id': ScoreEntry(score: 1)},
|
||||||
|
endedAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -37676,12 +37676,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// tallee 0.0.25+259
|
/// tallee 0.0.26+260
|
||||||
const _tallee = Package(
|
const _tallee = Package(
|
||||||
name: 'tallee',
|
name: 'tallee',
|
||||||
description: 'Tracking App for Card Games',
|
description: 'Tracking App for Card Games',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.0.25+259',
|
version: '0.0.26+260',
|
||||||
spdxIdentifiers: ['LGPL-3.0'],
|
spdxIdentifiers: ['LGPL-3.0'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:tallee/data/models/match.dart';
|
|||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/quick_info_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ class StatisticsView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _StatisticsViewState extends State<StatisticsView> {
|
class _StatisticsViewState extends State<StatisticsView> {
|
||||||
|
int matchCount = 0;
|
||||||
|
int groupCount = 0;
|
||||||
|
|
||||||
List<(Player, int)> winCounts = List.filled(6, (
|
List<(Player, int)> winCounts = List.filled(6, (
|
||||||
Player(name: 'Skeleton Player'),
|
Player(name: 'Skeleton Player'),
|
||||||
1,
|
1,
|
||||||
@@ -53,7 +57,27 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: constraints.maxHeight * 0.01),
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
QuickInfoTile(
|
||||||
|
width: constraints.maxWidth * 0.45,
|
||||||
|
height: constraints.maxHeight * 0.13,
|
||||||
|
title: loc.matches,
|
||||||
|
icon: Icons.groups_rounded,
|
||||||
|
value: matchCount,
|
||||||
|
),
|
||||||
|
SizedBox(width: constraints.maxWidth * 0.05),
|
||||||
|
QuickInfoTile(
|
||||||
|
width: constraints.maxWidth * 0.45,
|
||||||
|
height: constraints.maxHeight * 0.13,
|
||||||
|
title: loc.groups,
|
||||||
|
icon: Icons.groups_rounded,
|
||||||
|
value: groupCount,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: constraints.maxHeight * 0.02),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible:
|
visible:
|
||||||
winCounts.isEmpty &&
|
winCounts.isEmpty &&
|
||||||
@@ -115,11 +139,17 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
Future.wait([
|
Future.wait([
|
||||||
db.matchDao.getAllMatches(),
|
db.matchDao.getAllMatches(),
|
||||||
db.playerDao.getAllPlayers(),
|
db.playerDao.getAllPlayers(),
|
||||||
|
db.matchDao.getMatchCount(),
|
||||||
|
db.groupDao.getGroupCount(),
|
||||||
Future.delayed(Constants.MINIMUM_SKELETON_DURATION),
|
Future.delayed(Constants.MINIMUM_SKELETON_DURATION),
|
||||||
]).then((results) async {
|
]).then((results) async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final matches = results[0] as List<Match>;
|
final matches = results[0] as List<Match>;
|
||||||
final players = results[1] as List<Player>;
|
final players = results[1] as List<Player>;
|
||||||
|
matchCount = results[2] as int;
|
||||||
|
groupCount = results[3] as int;
|
||||||
|
|
||||||
winCounts = _calculateWinsForAllPlayers(
|
winCounts = _calculateWinsForAllPlayers(
|
||||||
matches: matches,
|
matches: matches,
|
||||||
players: players,
|
players: players,
|
||||||
@@ -134,6 +164,7 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
winCounts: winCounts,
|
winCounts: winCounts,
|
||||||
matchCounts: matchCounts,
|
matchCounts: matchCounts,
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,24 +44,49 @@ class _NavbarItemState extends State<NavbarItem>
|
|||||||
/// Scale animation for the icon when selected
|
/// Scale animation for the icon when selected
|
||||||
late Animation<double> _scaleAnimation;
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
/// Color animation for the icon
|
||||||
|
late Animation<Color?> _iconColorAnimation;
|
||||||
|
|
||||||
|
/// Background color animation for the icon container
|
||||||
|
late Animation<Color?> _bgColorAnimation;
|
||||||
|
|
||||||
|
/// Font size animation for the label
|
||||||
|
late Animation<double> _fontSizeAnimation;
|
||||||
|
|
||||||
|
/// A simple double tween used to lerp between two font weights
|
||||||
|
late Animation<double> _fontWeightT;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_animationController = AnimationController(
|
_animationController = AnimationController(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
// Set initial value directly so the visual state matches widget.isSelected
|
||||||
|
value: widget.isSelected ? 1.0 : 0.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
|
final curved = CurvedAnimation(
|
||||||
CurvedAnimation(
|
parent: _animationController,
|
||||||
parent: _animationController,
|
curve: Curves.easeOut,
|
||||||
curve: Curves.easeInOutBack,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.isSelected) {
|
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(curved);
|
||||||
_animationController.forward();
|
|
||||||
}
|
_iconColorAnimation = ColorTween(
|
||||||
|
begin: CustomTheme.navBarItemUnselectedColor,
|
||||||
|
end: CustomTheme.navBarItemSelectedColor,
|
||||||
|
).animate(curved);
|
||||||
|
|
||||||
|
_bgColorAnimation = ColorTween(
|
||||||
|
begin: Colors.transparent,
|
||||||
|
end: CustomTheme.primaryColor.withAlpha(50),
|
||||||
|
).animate(curved);
|
||||||
|
|
||||||
|
_fontSizeAnimation = Tween<double>(begin: 11.0, end: 12.0).animate(curved);
|
||||||
|
|
||||||
|
// drives font weight interpolation
|
||||||
|
_fontWeightT = Tween<double>(begin: 0.0, end: 1.0).animate(curved);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrigger animation on selection change
|
// Retrigger animation on selection change
|
||||||
@@ -83,46 +108,44 @@ class _NavbarItemState extends State<NavbarItem>
|
|||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 5.0),
|
padding: const EdgeInsets.symmetric(vertical: 5.0),
|
||||||
child: Column(
|
child: AnimatedBuilder(
|
||||||
mainAxisSize: MainAxisSize.min,
|
animation: _animationController,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
builder: (context, child) {
|
||||||
children: [
|
final iconColor = _iconColorAnimation.value!;
|
||||||
AnimatedContainer(
|
final bgColor = _bgColorAnimation.value!;
|
||||||
width: 50,
|
final fontSize = _fontSizeAnimation.value;
|
||||||
height: 50,
|
final fontWeight = FontWeight.lerp(
|
||||||
decoration: BoxDecoration(
|
FontWeight.w500,
|
||||||
color: widget.isSelected
|
FontWeight.bold,
|
||||||
? CustomTheme.primaryColor.withAlpha(50)
|
_fontWeightT.value,
|
||||||
: Colors.transparent,
|
);
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
return Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
duration: const Duration(milliseconds: 200),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: ScaleTransition(
|
children: [
|
||||||
scale: widget.isSelected
|
Container(
|
||||||
? _scaleAnimation
|
width: 50,
|
||||||
: const AlwaysStoppedAnimation(1.0),
|
height: 50,
|
||||||
child: Icon(
|
decoration: BoxDecoration(
|
||||||
widget.icon,
|
color: bgColor,
|
||||||
color: widget.isSelected
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
? CustomTheme.navBarItemSelectedColor
|
),
|
||||||
: CustomTheme.navBarItemUnselectedColor,
|
child: ScaleTransition(
|
||||||
size: 32,
|
scale: _scaleAnimation,
|
||||||
|
child: Icon(widget.icon, color: iconColor, size: 32),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
),
|
widget.label,
|
||||||
Text(
|
style: TextStyle(
|
||||||
widget.label,
|
color: iconColor,
|
||||||
style: TextStyle(
|
fontSize: fontSize,
|
||||||
color: widget.isSelected
|
fontWeight: fontWeight,
|
||||||
? CustomTheme.navBarItemSelectedColor
|
),
|
||||||
: CustomTheme.navBarItemUnselectedColor,
|
),
|
||||||
fontSize: widget.isSelected ? 12 : 11,
|
],
|
||||||
fontWeight: widget.isSelected
|
);
|
||||||
? FontWeight.bold
|
},
|
||||||
: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: tallee
|
name: tallee
|
||||||
description: "Tracking App for Card Games"
|
description: "Tracking App for Card Games"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.25+259
|
version: 0.0.26+260
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user