MVP #141
2
lib/core/constants.dart
Normal file
2
lib/core/constants.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
/// Minimum duration of all app skeletons
|
||||
Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
||||
@@ -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';
|
||||
@@ -18,15 +19,15 @@ class GroupsView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GroupsViewState extends State<GroupsView> {
|
||||
late Future<List<Group>> _allGroupsFuture;
|
||||
late final AppDatabase db;
|
||||
late List<Group> loadedGroups;
|
||||
bool isLoading = true;
|
||||
|
||||
final player = Player(name: 'Skeleton Player');
|
||||
late final List<Group> skeletonData = List.filled(
|
||||
List<Group> 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')),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,10 +35,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_allGroupsFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.groupDao.getAllGroups(),
|
||||
);
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -47,50 +45,30 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
FutureBuilder<List<Group>>(
|
||||
future: _allGroupsFuture,
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<List<Group>> 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<Group> 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,
|
||||
@@ -107,7 +85,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_allGroupsFuture = db.groupDao.getAllGroups();
|
||||
loadGroups();
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -116,4 +94,22 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void loadGroups() {
|
||||
Future.wait([
|
||||
db.groupDao.getAllGroups(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
loadedGroups = results[0] as List<Group>;
|
||||
setState(() {
|
||||
groups = loadedGroups
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,11 @@ class HomeView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomeViewState extends State<HomeView> {
|
||||
late Future<int> _matchCountFuture;
|
||||
late Future<int> _groupCountFuture;
|
||||
late Future<List<Match>> _recentMatchesFuture;
|
||||
bool isLoading = true;
|
||||
|
||||
late final List<Match> skeletonData = List.filled(
|
||||
int matchCount = 0;
|
||||
int groupCount = 0;
|
||||
List<Match> loadedRecentMatches = [];
|
||||
List<Match> recentMatches = List.filled(
|
||||
2,
|
||||
Match(
|
||||
name: 'Skeleton Match',
|
||||
@@ -39,19 +39,28 @@ class _HomeViewState extends State<HomeView> {
|
||||
);
|
||||
|
||||
@override
|
||||
initState() {
|
||||
void initState() {
|
||||
super.initState();
|
||||
final db = Provider.of<AppDatabase>(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;
|
||||
loadedRecentMatches = results[2] as List<Match>;
|
||||
recentMatches =
|
||||
(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;
|
||||
@@ -65,6 +74,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return AppSkeleton(
|
||||
fixLayoutBuilder: true,
|
||||
enabled: isLoading,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
@@ -73,38 +83,20 @@ class _HomeViewState extends State<HomeView> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FutureBuilder<int>(
|
||||
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<int>(
|
||||
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 +108,48 @@ class _HomeViewState extends State<HomeView> {
|
||||
icon: Icons.timer,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: FutureBuilder(
|
||||
future: _recentMatchesFuture,
|
||||
builder:
|
||||
(
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Match>> snapshot,
|
||||
) {
|
||||
if (snapshot.hasError) {
|
||||
return const Center(
|
||||
heightFactor: 4,
|
||||
child: Text(
|
||||
'Error while loading recent matches.',
|
||||
),
|
||||
);
|
||||
}
|
||||
final List<Match> 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 && loadedRecentMatches.isNotEmpty,
|
||||
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 (loadedRecentMatches.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: 5.35,
|
||||
child: Text('No second game available'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -199,7 +159,6 @@ class _HomeViewState extends State<HomeView> {
|
||||
title: 'Quick Create',
|
||||
icon: Icons.add_box_rounded,
|
||||
content: Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
|
||||
@@ -28,12 +28,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Reference to the app database
|
||||
late final AppDatabase db;
|
||||
|
||||
/// Futures to load all groups and players from the database
|
||||
late Future<List<Group>> _allGroupsFuture;
|
||||
|
||||
/// Future to load all players from the database
|
||||
late Future<List<Player>> _allPlayersFuture;
|
||||
|
||||
/// Controller for the game name input field
|
||||
final TextEditingController _gameNameController = TextEditingController();
|
||||
|
||||
@@ -107,14 +101,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
|
||||
db = Provider.of<AppDatabase>(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<Group>;
|
||||
playerList = result[1] as List<Player>;
|
||||
});
|
||||
|
||||
filteredPlayerList = List.from(playerList);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -23,16 +24,16 @@ class MatchView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MatchViewState extends State<MatchView> {
|
||||
late Future<List<Match>> _gameListFuture;
|
||||
late final AppDatabase db;
|
||||
bool isLoading = true;
|
||||
|
||||
late final List<Match> skeletonData = List.filled(
|
||||
List<Match> 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')],
|
||||
@@ -43,10 +44,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_gameListFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.matchDao.getAllMatches(),
|
||||
);
|
||||
loadGames();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,67 +54,44 @@ class _MatchViewState extends State<MatchView> {
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
FutureBuilder<List<Match>>(
|
||||
future: _gameListFuture,
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<List<Match>> 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<Match> 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 - 20,
|
||||
);
|
||||
}
|
||||
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,
|
||||
@@ -128,7 +103,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
CreateMatchView(onWinnerChanged: refreshGameList),
|
||||
CreateMatchView(onWinnerChanged: loadGames),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -139,9 +114,19 @@ class _MatchViewState extends State<MatchView> {
|
||||
);
|
||||
}
|
||||
|
||||
void refreshGameList() {
|
||||
setState(() {
|
||||
_gameListFuture = db.matchDao.getAllMatches();
|
||||
void loadGames() {
|
||||
Future.wait([
|
||||
db.matchDao.getAllMatches(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
final loadedMatches = results[0] as List<Match>;
|
||||
matches = loadedMatches
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<StatisticsView> {
|
||||
late Future<List<Match>> _matchesFuture;
|
||||
late Future<List<Player>> _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<StatisticsView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
final db = Provider.of<AppDatabase>(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<Match>;
|
||||
final players = results[1] as List<Player>;
|
||||
winCounts = _calculateWinsForAllPlayers(matches, players);
|
||||
|
||||
@@ -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';
|
||||
@@ -29,6 +30,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
List<Player> selectedPlayers = [];
|
||||
List<Player> suggestedPlayers = [];
|
||||
List<Player> allPlayers = [];
|
||||
bool isLoading = true;
|
||||
late final TextEditingController _searchBarController =
|
||||
TextEditingController();
|
||||
late final AppDatabase db;
|
||||
@@ -42,41 +44,44 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
suggestedPlayers = skeletonData;
|
||||
loadPlayerList();
|
||||
}
|
||||
|
||||
void loadPlayerList() {
|
||||
_allPlayersFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.playerDao.getAllPlayers(),
|
||||
);
|
||||
suggestedPlayers = skeletonData;
|
||||
_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];
|
||||
_allPlayersFuture = Future.wait([
|
||||
db.playerDao.getAllPlayers(),
|
||||
Future.delayed(minimumSkeletonDuration),
|
||||
]).then((results) => results[0] as List<Player>);
|
||||
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();
|
||||
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];
|
||||
}
|
||||
} 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
|
||||
@@ -162,75 +167,44 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
FutureBuilder(
|
||||
future: _allPlayersFuture,
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<List<Player>> 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.',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user