From 310b9aa43bf356731e3a5188e03c91ee66306c9d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 22:10:02 +0100 Subject: [PATCH 01/11] Implemented StatisticsWidget tile --- .../widgets/tiles/statistics_tile.dart | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 lib/presentation/widgets/tiles/statistics_tile.dart diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart new file mode 100644 index 0000000..0d01159 --- /dev/null +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -0,0 +1,100 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; + +class StatisticsTile extends StatelessWidget { + const StatisticsTile({ + super.key, + required this.icon, + required this.title, + required this.width, + required this.values, + required this.itemCount, + required this.barColor, + }); + + final IconData icon; + final String title; + final double width; + final List<(String, int)> values; + final int itemCount; + final Color barColor; + + @override + Widget build(BuildContext context) { + final maxBarWidth = MediaQuery.of(context).size.width * 0.7; + + return InfoTile( + width: width, + title: title, + icon: icon, + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Visibility( + visible: values.isNotEmpty, + replacement: const Center( + heightFactor: 4, + child: Text('No data available.'), + ), + child: Column( + children: List.generate(min(values.length, itemCount), (index) { + /// The maximum wins among all players + final maxGames = values.isNotEmpty ? values[0].$2 : 0; + + /// Fraction of wins + final double fraction = (maxGames > 0) + ? (values[index].$2 / maxGames) + : 0.0; + + /// Calculated width for current the bar + final double barWidth = maxBarWidth * fraction; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Stack( + children: [ + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barColor, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + values[index].$1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const Spacer(), + Center( + child: Text( + values[index].$2.toString(), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + }), + ), + ), + ), + ); + } +} From b2036e4e6811439da75dc5d0b3a15af2b41fb553 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 22:10:16 +0100 Subject: [PATCH 02/11] Implemented first version of statistics view --- .../views/main_menu/statistics_view.dart | 199 +++++++++++++++++- 1 file changed, 196 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 84ccf77..fc7b262 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,10 +1,203 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; -class StatisticsView extends StatelessWidget { +class StatisticsView extends StatefulWidget { const StatisticsView({super.key}); + @override + State createState() => _StatisticsViewState(); +} + +class _StatisticsViewState extends State { + late Future> _gamesFuture; + late Future> _playersFuture; + List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 5)); + List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 5)); + + bool isLoading = true; + + @override + void initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _gamesFuture = db.gameDao.getAllGames(); + _playersFuture = db.playerDao.getAllPlayers(); + + Future.wait([_gamesFuture, _playersFuture]).then((results) async { + await Future.delayed(const Duration(milliseconds: 500)); + final games = results[0] as List; + final players = results[1] as List; + winCounts = _calculateWinsForAllPlayers(games, players); + gameCounts = _calculateGameAmountsForAllPlayers(games, players); + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + @override Widget build(BuildContext context) { - return const Center(child: Text('Statistics View')); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: constraints.maxHeight * 0.01), + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins per Player', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 6, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Games per Player', + width: constraints.maxWidth * 0.95, + values: gameCounts, + itemCount: 6, + barColor: Colors.green, + ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), + ], + ), + ), + ), + ); + }, + ); + } + + /// Calculates the number of wins for each player + /// and returns a sorted list of tuples (playerName, winCount) + List<(String, int)> _calculateWinsForAllPlayers( + List games, + List players, + ) { + List<(String, int)> winCounts = []; + + // Getting the winners + for (var game in games) { + final winner = game.winner; + print('Game: ${game.id}, Winner: $winner'); + if (winner != null && winner.isNotEmpty) { + final index = winCounts.indexWhere((entry) => entry.$1 == winner); + if (index != -1) { + final current = winCounts[index].$2; + winCounts[index] = (winner, current + 1); + } else { + winCounts.add((winner, 1)); + } + } + } + + // Adding all players with zero wins + for (var player in players) { + final index = winCounts.indexWhere((entry) => entry.$1 == player.id); + if (index == -1) { + winCounts.add((player.id, 0)); + } + } + + // Replace player IDs with names + for (int i = 0; i < winCounts.length; i++) { + final playerId = winCounts[i].$1; + final player = players.firstWhere( + (p) => p.id == playerId, + orElse: () => Player(id: playerId, name: 'N.a.'), + ); + winCounts[i] = (player.name, winCounts[i].$2); + } + + winCounts.sort((a, b) => b.$2.compareTo(a.$2)); + + return winCounts; + } + + /// Calculates the number of games played for each player + /// and returns a sorted list of tuples (playerName, gameCount) + List<(String, int)> _calculateGameAmountsForAllPlayers( + List games, + List players, + ) { + List<(String, int)> gameCounts = []; + + // Counting games for each player + for (var game in games) { + if (game.group != null) { + final members = game.group!.members.map((p) => p.id).toList(); + for (var playerId in members) { + final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + if (index != -1) { + final current = gameCounts[index].$2; + gameCounts[index] = (playerId, current + 1); + } else { + gameCounts.add((playerId, 1)); + } + } + } + if (game.players != null) { + final members = game.players!.map((p) => p.id).toList(); + for (var playerId in members) { + final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + if (index != -1) { + final current = gameCounts[index].$2; + gameCounts[index] = (playerId, current + 1); + } else { + gameCounts.add((playerId, 1)); + } + } + } + } + + // Adding all players with zero games + for (var player in players) { + final index = gameCounts.indexWhere((entry) => entry.$1 == player.id); + if (index == -1) { + gameCounts.add((player.id, 0)); + } + } + + // Replace player IDs with names + for (int i = 0; i < gameCounts.length; i++) { + final playerId = gameCounts[i].$1; + final player = players.firstWhere( + (p) => p.id == playerId, + orElse: () => Player(id: playerId, name: 'N.a.'), + ); + gameCounts[i] = (player.name, gameCounts[i].$2); + } + + gameCounts.sort((a, b) => b.$2.compareTo(a.$2)); + + return gameCounts; } } From 59c041699dc3edd983959f207b33470b9a6673af Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:20:31 +0100 Subject: [PATCH 03/11] Changed values attribute & maxBarWidth --- lib/presentation/widgets/tiles/statistics_tile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 0d01159..279c492 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -17,13 +17,13 @@ class StatisticsTile extends StatelessWidget { final IconData icon; final String title; final double width; - final List<(String, int)> values; + final List<(String, num)> values; final int itemCount; final Color barColor; @override Widget build(BuildContext context) { - final maxBarWidth = MediaQuery.of(context).size.width * 0.7; + final maxBarWidth = MediaQuery.of(context).size.width * 0.65; return InfoTile( width: width, From e60961730f64ec78f2ab17da15297990bbdca4d0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:20:58 +0100 Subject: [PATCH 04/11] Added new metric & changed layout builder of Skeletonizer --- .../views/main_menu/statistics_view.dart | 92 +++++++++++++++---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index fc7b262..2a8dedf 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -16,9 +16,9 @@ class StatisticsView extends StatefulWidget { class _StatisticsViewState extends State { late Future> _gamesFuture; late Future> _playersFuture; - List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 5)); - List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 5)); - + List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); bool isLoading = true; @override @@ -29,11 +29,12 @@ class _StatisticsViewState extends State { _playersFuture = db.playerDao.getAllPlayers(); Future.wait([_gamesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 200)); final games = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(games, players); gameCounts = _calculateGameAmountsForAllPlayers(games, players); + winRates = computeWinRatePercent(wins: winCounts, games: gameCounts); if (mounted) { setState(() { isLoading = false; @@ -46,22 +47,31 @@ class _StatisticsViewState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), - child: SingleChildScrollView( + return SingleChildScrollView( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: SwitchAnimationConfig( + duration: const Duration(milliseconds: 1000), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: + (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + ), child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Column( @@ -78,6 +88,15 @@ class _StatisticsViewState extends State { barColor: Colors.blue, ), SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Winrate per Player', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 6, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, title: 'Games per Player', @@ -86,6 +105,7 @@ class _StatisticsViewState extends State { itemCount: 6, barColor: Colors.green, ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), @@ -200,4 +220,36 @@ class _StatisticsViewState extends State { return gameCounts; } + + // dart + List<(String, double)> computeWinRatePercent({ + required List<(String, int)> wins, // [(name, wins)] + required List<(String, int)> games, // [(name, games)] + }) { + final Map winsMap = {for (var e in wins) e.$1: e.$2}; + final Map gamesMap = {for (var e in games) e.$1: e.$2}; + + final names = {...winsMap.keys, ...gamesMap.keys}; + + final result = names.map((name) { + final int w = winsMap[name] ?? 0; + final int g = gamesMap[name] ?? 0; + final double percent = (g > 0) + ? double.parse(((w / g)).toStringAsFixed(2)) + : 0; + return (name, percent); + }).toList(); + + // Sort the result: first by winrate descending, + // then by wins descending in case of a tie + result.sort((a, b) { + final cmp = b.$2.compareTo(a.$2); + if (cmp != 0) return cmp; + final wa = winsMap[a.$1] ?? 0; + final wb = winsMap[b.$1] ?? 0; + return wb.compareTo(wa); + }); + + return result; + } } From fba35521cbcfddfc401c91c67f344ba4b6203ab5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:23:02 +0100 Subject: [PATCH 05/11] changed skeletonizer transition duration back to normal --- lib/presentation/views/main_menu/statistics_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 2a8dedf..a365b2e 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -57,7 +57,7 @@ class _StatisticsViewState extends State { enabled: isLoading, enableSwitchAnimation: true, switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 1000), + duration: const Duration(milliseconds: 200), switchInCurve: Curves.linear, switchOutCurve: Curves.linear, transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, From feb5fa061557bc8b4fa9d206bde076fc7e559095 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:30:24 +0100 Subject: [PATCH 06/11] Docs, small changes --- .../views/main_menu/statistics_view.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index a365b2e..8830118 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -84,7 +84,7 @@ class _StatisticsViewState extends State { title: 'Wins per Player', width: constraints.maxWidth * 0.95, values: winCounts, - itemCount: 6, + itemCount: 3, barColor: Colors.blue, ), SizedBox(height: constraints.maxHeight * 0.02), @@ -93,7 +93,7 @@ class _StatisticsViewState extends State { title: 'Winrate per Player', width: constraints.maxWidth * 0.95, values: winRates, - itemCount: 6, + itemCount: 5, barColor: Colors.orange[700]!, ), SizedBox(height: constraints.maxHeight * 0.02), @@ -102,7 +102,7 @@ class _StatisticsViewState extends State { title: 'Games per Player', width: constraints.maxWidth * 0.95, values: gameCounts, - itemCount: 6, + itemCount: 10, barColor: Colors.green, ), @@ -127,7 +127,6 @@ class _StatisticsViewState extends State { // Getting the winners for (var game in games) { final winner = game.winner; - print('Game: ${game.id}, Winner: $winner'); if (winner != null && winner.isNotEmpty) { final index = winCounts.indexWhere((entry) => entry.$1 == winner); if (index != -1) { @@ -223,17 +222,21 @@ class _StatisticsViewState extends State { // dart List<(String, double)> computeWinRatePercent({ - required List<(String, int)> wins, // [(name, wins)] - required List<(String, int)> games, // [(name, games)] + required List<(String, int)> wins, + required List<(String, int)> games, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; final Map gamesMap = {for (var e in games) e.$1: e.$2}; + // Get all unique player names final names = {...winsMap.keys, ...gamesMap.keys}; + // Calculate win rates final result = names.map((name) { final int w = winsMap[name] ?? 0; final int g = gamesMap[name] ?? 0; + // Calculate percentage and round to 2 decimal places + // Avoid division by zero final double percent = (g > 0) ? double.parse(((w / g)).toStringAsFixed(2)) : 0; From fa841e328ec63a13655e43a22b7c24bf11a1eac1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:11 +0100 Subject: [PATCH 07/11] Altered game class --- lib/data/dto/game.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 4188bc4..48ef902 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -9,7 +9,7 @@ class Game { final String name; final List? players; final Group? group; - final String? winner; + final Player? winner; Game({ String? id, @@ -17,7 +17,7 @@ class Game { required this.name, this.players, this.group, - this.winner = '', + this.winner, }) : id = id ?? const Uuid().v4(), createdAt = createdAt ?? clock.now(); @@ -37,7 +37,7 @@ class Game { .toList() : null, group = json['group'] != null ? Group.fromJson(json['group']) : null, - winner = json['winner'] ?? ''; + winner = json['winner'] != null ? Player.fromJson(json['winner']) : null; /// Converts the Game instance to a JSON object. Map toJson() => { @@ -46,6 +46,6 @@ class Game { 'name': name, 'players': players?.map((player) => player.toJson()).toList(), 'group': group?.toJson(), - 'winner': winner, + 'winner': winner?.toJson(), }; } From cfed05595ca9cbec81213b8128c52c9d442f923e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:27 +0100 Subject: [PATCH 08/11] Updated methods in gameDao --- lib/data/dao/game_dao.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index a211849..18792b5 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -20,13 +20,16 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { result.map((row) async { final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); + final winner = row.winnerId != null + ? await db.playerDao.getPlayerById(playerId: row.winnerId!) + : null; return Game( id: row.id, name: row.name, group: group, players: players, createdAt: row.createdAt, - winner: row.winnerId, + winner: winner, ); }), ); @@ -45,13 +48,17 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { group = await db.groupGameDao.getGroupOfGame(gameId: gameId); } + Player? winner; + if (result.winnerId != null) { + winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + } return Game( id: result.id, name: result.name, players: players, group: group, - winner: result.winnerId, + winner: winner, createdAt: result.createdAt, ); } @@ -64,7 +71,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: Value(game.winner), + winnerId: Value(game.winner?.id), createdAt: game.createdAt, ), mode: InsertMode.insertOrReplace, @@ -100,7 +107,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { id: game.id, name: game.name, createdAt: game.createdAt, - winnerId: Value(game.winner), + winnerId: Value(game.winner?.id), ), ) .toList(), From 338f4294dc17ee4f2e1288a3bcc07b654ef5a073 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:31 +0100 Subject: [PATCH 09/11] Updated json schema --- assets/schema.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 1883122..c80915c 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -88,13 +88,12 @@ ] }, "winner": { - "type": ["string","null"] + "type": ["object","null"] }, "required": [ "id", "createdAt", - "name", - "winner" + "name" ] } ] From 82b344a145f829e7be92f42f6c05a140975fc79f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:48 +0100 Subject: [PATCH 10/11] Changed winner access in statistics view --- lib/presentation/views/main_menu/statistics_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8830118..13e9aae 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -127,13 +127,13 @@ class _StatisticsViewState extends State { // Getting the winners for (var game in games) { final winner = game.winner; - if (winner != null && winner.isNotEmpty) { - final index = winCounts.indexWhere((entry) => entry.$1 == winner); + if (winner != null) { + final index = winCounts.indexWhere((entry) => entry.$1 == winner.id); if (index != -1) { final current = winCounts[index].$2; - winCounts[index] = (winner, current + 1); + winCounts[index] = (winner.id, current + 1); } else { - winCounts.add((winner, 1)); + winCounts.add((winner.id, 1)); } } } From 4ff131770e205f4c112dc703330c7ec69b25ea8d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:22:53 +0100 Subject: [PATCH 11/11] Adjust tests --- test/db_tests/game_test.dart | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0d33c1e..4cf6982 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -50,15 +50,18 @@ void main() { name: 'First Test Game', group: testGroup1, players: [testPlayer4, testPlayer5], + winner: testPlayer4, ); testGame2 = Game( name: 'Second Test Game', group: testGroup2, players: [testPlayer1, testPlayer2, testPlayer3], + winner: testPlayer2, ); testGameOnlyPlayers = Game( name: 'Test Game with Players', players: [testPlayer1, testPlayer2, testPlayer3], + winner: testPlayer3, ); testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); }); @@ -75,9 +78,16 @@ void main() { expect(result.id, testGame1.id); expect(result.name, testGame1.name); - expect(result.winner, testGame1.winner); expect(result.createdAt, testGame1.createdAt); + if (result.winner != null && testGame1.winner != null) { + expect(result.winner!.id, testGame1.winner!.id); + expect(result.winner!.name, testGame1.winner!.name); + expect(result.winner!.createdAt, testGame1.winner!.createdAt); + } else { + expect(result.winner, testGame1.winner); + } + if (result.group != null) { expect(result.group!.members.length, testGroup1.members.length); @@ -123,7 +133,13 @@ void main() { expect(game.id, testGame.id); expect(game.name, testGame.name); expect(game.createdAt, testGame.createdAt); - expect(game.winner, testGame.winner); + if (game.winner != null && testGame.winner != null) { + expect(game.winner!.id, testGame.winner!.id); + expect(game.winner!.name, testGame.winner!.name); + expect(game.winner!.createdAt, testGame.winner!.createdAt); + } else { + expect(game.winner, testGame.winner); + } // Group-Checks if (testGame.group != null) {