From 0eaf3d251bb7bb256121cdf115771f1a5f1a2bee Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:49:58 +0100 Subject: [PATCH 01/23] added constant minimumSkeletonDuration --- lib/core/constants.dart | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lib/core/constants.dart diff --git a/lib/core/constants.dart b/lib/core/constants.dart new file mode 100644 index 0000000..51b4c70 --- /dev/null +++ b/lib/core/constants.dart @@ -0,0 +1,2 @@ +/// Minimum duration of all app skeletons +Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file From d96494f608e483d61b553e239150ce1988e26cd8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:24 +0100 Subject: [PATCH 02/23] Changed futurebuilder logic in groups_view --- .../views/main_menu/group_view/groups_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5fd5e4b..67109e6 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -34,10 +35,10 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.groupDao.getAllGroups(), - ); + _allGroupsFuture = Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override From f05114a99e3ccf686e84b500bd5a89d3b6aef65f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:39 +0100 Subject: [PATCH 03/23] removed futurebuilder logic in groups_view --- .../views/main_menu/home_view.dart | 191 +++++++----------- 1 file changed, 72 insertions(+), 119 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 49bfa8f..31bedf5 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; @@ -18,12 +19,10 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { - late Future _matchCountFuture; - late Future _groupCountFuture; - late Future> _recentMatchesFuture; bool isLoading = true; - - late final List skeletonData = List.filled( + int matchCount = 0; + int groupCount = 0; + List recentMatches = List.filled( 2, Match( name: 'Skeleton Match', @@ -39,19 +38,24 @@ class _HomeViewState extends State { ); @override - initState() { + void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchCountFuture = db.matchDao.getMatchCount(); - _groupCountFuture = db.groupDao.getGroupCount(); - _recentMatchesFuture = db.matchDao.getAllMatches(); Future.wait([ - _matchCountFuture, - _groupCountFuture, - _recentMatchesFuture, - ]).then((_) async { - await Future.delayed(const Duration(milliseconds: 250)); + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + recentMatches = results[2] as List; + + recentMatches = + (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); if (mounted) { setState(() { isLoading = false; @@ -73,38 +77,20 @@ class _HomeViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - FutureBuilder( - future: _matchCountFuture, - builder: (context, snapshot) { - final int count = (snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Matches', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Matches', + icon: Icons.groups_rounded, + value: matchCount, ), SizedBox(width: constraints.maxWidth * 0.05), - FutureBuilder( - future: _groupCountFuture, - builder: (context, snapshot) { - final int count = - (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Groups', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: groupCount, ), ], ), @@ -116,80 +102,48 @@ class _HomeViewState extends State { icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), - child: FutureBuilder( - future: _recentMatchesFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - heightFactor: 4, - child: Text( - 'Error while loading recent matches.', - ), - ); - } - final List matches = - (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo( - a.createdAt, - ), - )) - .take(2) - .toList(); - if (matches.isNotEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MatchTile( - matchTitle: matches[0].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[0]), - winner: matches[0].winner == null - ? 'Match in progress...' - : matches[0].winner!.name, - ), - const Padding( - padding: EdgeInsets.symmetric( - vertical: 8.0, - ), - child: Divider(), - ), - if (matches.length > 1) ...[ - MatchTile( - matchTitle: matches[1].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[1]), - winner: matches[1].winner == null - ? 'Game in progress...' - : matches[1].winner!.name, - ), - const SizedBox(height: 8), - ] else ...[ - const Center( - heightFactor: 4, - child: Text( - 'No second game available.', - ), - ), - ], - ], - ); - } else { - return const Center( - heightFactor: 12, - child: Text('No recent games available.'), - ); - } - }, + child: Visibility( + visible: !isLoading, + replacement: const Center( + heightFactor: 12, + child: Text('No recent games available.'), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MatchTile( + matchTitle: recentMatches[0].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[0]), + winner: recentMatches[0].winner == null + ? 'Match in progress...' + : recentMatches[0].winner!.name, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + if (recentMatches.length > 1) ...[ + MatchTile( + matchTitle: recentMatches[1].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[1]), + winner: recentMatches[1].winner == null + ? 'Game in progress...' + : recentMatches[1].winner!.name, + ), + const SizedBox(height: 8), + ] else ...[ + const Center( + heightFactor: 4, + child: Text('No second game available.'), + ), + ], + ], + ), ), ), ), @@ -199,7 +153,6 @@ class _HomeViewState extends State { title: 'Quick Create', icon: Icons.add_box_rounded, content: Column( - spacing: 8, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, From 76186787e73d976ce13e7ffec44f098461cc92d7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:51:24 +0100 Subject: [PATCH 04/23] changed futurebuilder logic in player selection --- lib/presentation/widgets/player_selection.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e2114b2..96d9d9a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -46,10 +47,10 @@ class _PlayerSelectionState extends State { } void loadPlayerList() { - _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.playerDao.getAllPlayers(), - ); + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { From c8532adfde02960bca203825f1678cb92b70fbdf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:52:02 +0100 Subject: [PATCH 05/23] changed skeleton duration logic in statistics view and refactored --- .../views/main_menu/statistics_view.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6104e39..7df77a7 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -14,8 +15,6 @@ class StatisticsView extends StatefulWidget { } class _StatisticsViewState extends State { - late Future> _matchesFuture; - late Future> _playersFuture; List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); @@ -25,11 +24,12 @@ class _StatisticsViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchesFuture = db.matchDao.getAllMatches(); - _playersFuture = db.playerDao.getAllPlayers(); - Future.wait([_matchesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 250)); + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) async { final matches = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(matches, players); From 24b60bb18b937791f359b37544f30cb15887203b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:46:59 +0100 Subject: [PATCH 06/23] added minimumSkeletonDuration constant and changed future logic --- .../views/main_menu/match_view/match_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 92fd268..7622134 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -2,6 +2,7 @@ import 'dart:core' hide Match; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -43,10 +44,10 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.matchDao.getAllMatches(), - ); + _gameListFuture = Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override From 4f0a1eec6d12c28a178e8eeb8bce8a9d1004e31e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:50:34 +0100 Subject: [PATCH 07/23] refactor --- .../match_view/create_match/create_match_view.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 03c081c..27a1ce9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -28,12 +28,6 @@ class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; - /// Futures to load all groups and players from the database - late Future> _allGroupsFuture; - - /// Future to load all players from the database - late Future> _allPlayersFuture; - /// Controller for the game name input field final TextEditingController _gameNameController = TextEditingController(); @@ -107,10 +101,10 @@ class _CreateMatchViewState extends State { db = Provider.of(context, listen: false); - _allGroupsFuture = db.groupDao.getAllGroups(); - _allPlayersFuture = db.playerDao.getAllPlayers(); - - Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { + Future.wait([ + db.groupDao.getAllGroups(), + db.playerDao.getAllPlayers(), + ]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; }); From df0a5207c2b9b5d8c99612d4c62e7374c1ae249b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 20:01:48 +0100 Subject: [PATCH 08/23] add const --- lib/core/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 51b4c70..075b1ab 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,2 @@ /// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file +Duration minimumSkeletonDuration = const Duration(milliseconds: 250); From 9b3d61e5b0bd06497007d70102767dba6136241d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:36:11 +0100 Subject: [PATCH 09/23] remove futurebuilder from playerselection and refactor --- .../widgets/player_selection.dart | 109 +++++++----------- 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 96d9d9a..916ed37 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -30,6 +30,7 @@ class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; + bool isLoading = true; late final TextEditingController _searchBarController = TextEditingController(); late final AppDatabase db; @@ -43,6 +44,7 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); + suggestedPlayers = skeletonData; loadPlayerList(); } @@ -51,8 +53,8 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { + isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -163,75 +165,44 @@ class _PlayerSelectionState extends State { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - FutureBuilder( - future: _allPlayersFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), + /* + + */ + Expanded( + child: AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: suggestedPlayers.isNotEmpty, + replacement: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: allPlayers.isEmpty + ? 'No players created yet.' + : (selectedPlayers.length == allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + child: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add(suggestedPlayers[index]); + widget.onChanged([...selectedPlayers]); + suggestedPlayers.remove(suggestedPlayers[index]); + } + }); + }, ); - } - bool doneLoading = - snapshot.connectionState == ConnectionState.done; - bool snapshotDataEmpty = - !snapshot.hasData || snapshot.data!.isEmpty; - if (doneLoading && - (snapshotDataEmpty && allPlayers.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - return Expanded( - child: AppSkeleton( - enabled: isLoading, - child: Visibility( - visible: - (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), - replacement: ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index].name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - widget.onChanged([...selectedPlayers]); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); - }, - ), - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ), - ), - ), - ); - }, + }, + ), + ), + ), ), ], ), From 9ad5c4ad6fc7bd97ba91523c7a71be8ec02f4976 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:58:49 +0100 Subject: [PATCH 10/23] remove futurebuilder from groups view and refactor --- .../main_menu/group_view/groups_view.dart | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 67109e6..c2cb6aa 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -19,15 +19,15 @@ class GroupsView extends StatefulWidget { } class _GroupsViewState extends State { - late Future> _allGroupsFuture; late final AppDatabase db; + late List loadedGroups; + bool isLoading = true; - final player = Player(name: 'Skeleton Player'); - late final List skeletonData = List.filled( + List groups = List.filled( 7, Group( - name: 'Skeleton Match', - members: [player, player, player, player, player, player], + name: 'Skeleton Group', + members: List.filled(6, Player(name: 'Skeleton Player')), ), ); @@ -35,10 +35,17 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.wait([ + Future.wait([ db.groupDao.getAllGroups(), Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); } @override @@ -48,50 +55,30 @@ class _GroupsViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _allGroupsFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Group data couldn\'t\nbe loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: groups.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No groups created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: groups.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == groups.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No groups created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List groups = - isLoading ? skeletonData : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: groups.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == groups.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 20, - ); - } - return GroupTile(group: groups[index]); - }, - ), - ); + return GroupTile(group: groups[index]); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -108,7 +95,7 @@ class _GroupsViewState extends State { ), ); setState(() { - _allGroupsFuture = db.groupDao.getAllGroups(); + //_allGroupsFuture = db.groupDao.getAllGroups(); }); }, ), From 06a9c0cd84e8089161d4ca6ed43bd0a2281d7d2c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:59:01 +0100 Subject: [PATCH 11/23] remove futurebuilder from player selection and refactor --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 916ed37..fda5ea7 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -54,7 +54,6 @@ class _PlayerSelectionState extends State { Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); _allPlayersFuture.then((loadedPlayers) { - isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -79,6 +78,7 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); + isLoading = false; }); } From a747d91c5da6d696aa6524e371d36e64ee1c4574 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:16:57 +0100 Subject: [PATCH 12/23] refactor group loading into reusable method `loadGroups` and call it after adding a group --- .../main_menu/group_view/groups_view.dart | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c2cb6aa..5d303d5 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,17 +35,7 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - Future.wait([ - db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) { - loadedGroups = results[0] as List; - setState(() { - groups = loadedGroups - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - }); - isLoading = false; - }); + loadGroups(); } @override @@ -95,7 +85,7 @@ class _GroupsViewState extends State { ), ); setState(() { - //_allGroupsFuture = db.groupDao.getAllGroups(); + loadGroups(); }); }, ), @@ -104,4 +94,18 @@ class _GroupsViewState extends State { ), ); } + + void loadGroups() { + Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); + } } From 7732c6ceb9a67b011b44cc279f16f975c4c6ce6b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:17:27 +0100 Subject: [PATCH 13/23] remove futurebuilder from match view and refactor --- .../main_menu/match_view/match_view.dart | 118 ++++++++---------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 7622134..eb0e83d 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -24,16 +24,16 @@ class MatchView extends StatefulWidget { } class _MatchViewState extends State { - late Future> _gameListFuture; late final AppDatabase db; + bool isLoading = true; - late final List skeletonData = List.filled( + List matches = List.filled( 4, Match( name: 'Skeleton Gamename', group: Group( name: 'Groupname', - members: List.generate(5, (index) => Player(name: 'Player')), + members: List.filled(5, Player(name: 'Player')), ), winner: Player(name: 'Player'), players: [Player(name: 'Player')], @@ -44,10 +44,7 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.wait([ - db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + loadGames(); } @override @@ -57,67 +54,44 @@ class _MatchViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _gameListFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: matches.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Info', + message: 'No games created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: matches.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == matches.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Info', - message: 'No games created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List matches = - (isLoading ? skeletonData : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo(a.createdAt), - )) - .toList(); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: matches.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == matches.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, - ); - } - return GameHistoryTile( - onTap: () async { - Navigator.push( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => GameResultView( - match: matches[index], - onWinnerChanged: refreshGameList, - ), - ), - ); - }, - match: matches[index], - ); - }, - ), + return GameHistoryTile( + onTap: () async { + Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => GameResultView( + match: matches[index], + onWinnerChanged: loadGames, + ), + ), + ); + }, + match: matches[index], ); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -129,7 +103,7 @@ class _MatchViewState extends State { context, MaterialPageRoute( builder: (context) => - CreateMatchView(onWinnerChanged: refreshGameList), + CreateMatchView(onWinnerChanged: loadGames), ), ); }, @@ -140,9 +114,17 @@ class _MatchViewState extends State { ); } - void refreshGameList() { - setState(() { - _gameListFuture = db.matchDao.getAllMatches(); + void loadGames() { + Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + setState(() { + isLoading = false; + }); }); } } From 1d92084da6a5499e8dfc55b69ff8401e8bdf53d9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:53:37 +0100 Subject: [PATCH 14/23] fix rangeerror when only 1 or less matches exist --- .../views/main_menu/home_view.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 31bedf5..bb2acf6 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -22,6 +22,7 @@ class _HomeViewState extends State { bool isLoading = true; int matchCount = 0; int groupCount = 0; + List loadedRecentMatches = []; List recentMatches = List.filled( 2, Match( @@ -41,7 +42,6 @@ class _HomeViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - Future.wait([ db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), @@ -50,12 +50,17 @@ class _HomeViewState extends State { ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; - recentMatches = results[2] as List; - + loadedRecentMatches = results[2] as List; recentMatches = - (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .take(2) .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: "Dummy Match", winner: null, group: null, players: null), + ); + } if (mounted) { setState(() { isLoading = false; @@ -69,6 +74,7 @@ class _HomeViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( + fixLayoutBuilder: true, enabled: isLoading, child: SingleChildScrollView( child: Column( @@ -103,7 +109,7 @@ class _HomeViewState extends State { content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( - visible: !isLoading, + visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, child: Text('No recent games available.'), @@ -125,7 +131,7 @@ class _HomeViewState extends State { padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - if (recentMatches.length > 1) ...[ + if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, game: 'Winner', From b29cd6dff47e90611d9bfedd410c8895ae41e222 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:54:05 +0100 Subject: [PATCH 15/23] remove double quotes --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index bb2acf6..06b647c 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -58,7 +58,7 @@ class _HomeViewState extends State { .toList(); if (loadedRecentMatches.length < 2) { recentMatches.add( - Match(name: "Dummy Match", winner: null, group: null, players: null), + Match(name: 'Dummy Match', winner: null, group: null, players: null), ); } if (mounted) { From f2917a6813b80a64ee935e8e6e6bd8e81f7651cd Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:00:38 +0100 Subject: [PATCH 16/23] removed empty line --- .../main_menu/match_view/create_match/create_match_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 27a1ce9..787d200 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -108,7 +108,6 @@ class _CreateMatchViewState extends State { groupsList = result[0] as List; playerList = result[1] as List; }); - filteredPlayerList = List.from(playerList); } From f9722bc7628910c06ac5c7ded67100aa26d03541 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:05 +0100 Subject: [PATCH 17/23] wrap `isLoading = false` in a `mounted` check and `setState` call --- .../views/main_menu/group_view/groups_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5d303d5..b2243bc 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -105,7 +105,11 @@ class _GroupsViewState extends State { groups = loadedGroups ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } From 7eb25221d75bd989fe52b344be6dfcd63d06f359 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:16 +0100 Subject: [PATCH 18/23] add mounted check before calling setState in match_view --- .../views/main_menu/match_view/match_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index eb0e83d..97a765e 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -122,9 +122,11 @@ class _MatchViewState extends State { final loadedMatches = results[0] as List; matches = loadedMatches ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - setState(() { - isLoading = false; - }); + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } From 1e730cebe61e0c40faa6051a09bf82b2571309b3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:40 +0100 Subject: [PATCH 19/23] wrap isLoading into mounted and setState --- lib/presentation/widgets/player_selection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda5ea7..1e283fb 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -78,7 +78,11 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } From c73f37507f23b2705eab820994814e52de75d9bf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:05:49 +0100 Subject: [PATCH 20/23] put isLoading in existing setState and move mounted check up --- .../widgets/player_selection.dart | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 1e283fb..fda1da1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -53,37 +53,35 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided, use that list. - if (widget.availablePlayers.isNotEmpty) { - widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - }); - if (mounted) { + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { setState(() { + // If a list of available players is provided, use that list. + if (widget.availablePlayers.isNotEmpty) { + widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } isLoading = false; }); - } - }); + }); + } } @override From 7a80c1a792e2c208f0ef773522db94027242fdb5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:08:25 +0100 Subject: [PATCH 21/23] adjust heightFactor for empty game state in home view --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 06b647c..ade8f6e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -144,7 +144,7 @@ class _HomeViewState extends State { const SizedBox(height: 8), ] else ...[ const Center( - heightFactor: 4, + heightFactor: 5.35, child: Text('No second game available.'), ), ], From b0a5145490ef353b134b3aa2d2e16824c70db044 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:14:31 +0100 Subject: [PATCH 22/23] move setState to include match loading and sorting in MatchView --- lib/presentation/views/main_menu/match_view/match_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 97a765e..462e1b5 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -119,11 +119,11 @@ class _MatchViewState extends State { db.matchDao.getAllMatches(), Future.delayed(minimumSkeletonDuration), ]).then((results) { - final loadedMatches = results[0] as List; - matches = loadedMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); if (mounted) { setState(() { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); isLoading = false; }); } From 12856342a812f11f1d3b68efd950b5000082001c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 22:53:17 +0100 Subject: [PATCH 23/23] remove dots after sentences --- lib/presentation/views/main_menu/home_view.dart | 4 ++-- lib/presentation/widgets/player_selection.dart | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index ade8f6e..3e221e7 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -112,7 +112,7 @@ class _HomeViewState extends State { visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, - child: Text('No recent games available.'), + child: Text('No recent games available'), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -145,7 +145,7 @@ class _HomeViewState extends State { ] else ...[ const Center( heightFactor: 5.35, - child: Text('No second game available.'), + child: Text('No second game available'), ), ], ], diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda1da1..01c0338 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -179,10 +179,10 @@ class _PlayerSelectionState extends State { icon: Icons.info, title: 'Info', message: allPlayers.isEmpty - ? 'No players created yet.' + ? 'No players created yet' : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', + ? 'No more players to add' + : 'No players found with that name', ), child: ListView.builder( itemCount: suggestedPlayers.length,