StatisticsView + HomeView zusammenlegen #208

Merged
sneeex merged 11 commits from enhancement/192-statisticsview-+-homeview-zusammenlegen into development 2026-05-08 21:59:35 +00:00
4 changed files with 49 additions and 280 deletions

View File

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

View File

@@ -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();
}
}

View File

@@ -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'),
sneeex marked this conversation as resolved
Review

warum hier jetzt so viele spieler auf einmal?

warum hier jetzt so viele spieler auf einmal?
Review

damit man einen mvp hat, der auf den matchtiles angezeigt wird. der brauch eine id für die scores

damit man einen mvp hat, der auf den matchtiles angezeigt wird. der brauch eine id für die scores
],
scores: {'mvp_id': ScoreEntry(score: 1)},
endedAt: DateTime.now(),
), ),
); );

View File

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