From 3ca081419b9f3ec43d741727fad5b60ef0b4e689 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:04 +0100 Subject: [PATCH 01/53] Implemented new SettingsListTile --- .../widgets/tiles/settings_list_tile.dart | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lib/presentation/widgets/tiles/settings_list_tile.dart diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart new file mode 100644 index 0000000..1174627 --- /dev/null +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class SettingsListTile extends StatelessWidget { + final VoidCallback? onPressed; + final IconData icon; + final String title; + final Widget? suffixWidget; + const SettingsListTile({ + super.key, + required this.title, + required this.icon, + this.suffixWidget, + this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.95, + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: GestureDetector( + onTap: onPressed ?? () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: CustomTheme.primaryColor, + shape: BoxShape.circle, + ), + child: Icon(icon, size: 24), + ), + const SizedBox(width: 16), + Text(title, style: const TextStyle(fontSize: 18)), + ], + ), + if (suffixWidget != null) + suffixWidget! + else + const SizedBox.shrink(), + ], + ), + ), + ), + ), + ), + ); + } +} From 62acc87e0e804c3a366145d2af32ab0d7ecb9ab0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:41 +0100 Subject: [PATCH 02/53] Moved game tile in tiles folder --- lib/presentation/views/main_menu/home_view.dart | 2 +- lib/presentation/widgets/{ => tiles}/game_tile.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/presentation/widgets/{ => tiles}/game_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index cf6288a..53816d0 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart similarity index 100% rename from lib/presentation/widgets/game_tile.dart rename to lib/presentation/widgets/tiles/game_tile.dart From 2076e45fd505f1405d71e6efba05d87f86a9354d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:52 +0100 Subject: [PATCH 03/53] Created new layout for settings view --- .../views/main_menu/settings_view.dart | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c3e75f3..75ea163 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; class SettingsView extends StatelessWidget { const SettingsView({super.key}); @@ -6,8 +8,77 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Einstellungen')), - body: const Center(child: Text('Settings View')), + appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), + backgroundColor: CustomTheme.backgroundColor, + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(24, 0, 24, 10), + child: Text( + textAlign: TextAlign.start, + 'MenĂ¼', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + 'Einstellungen', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + + SettingsListTile( + title: 'Export Data', + icon: Icons.upload_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Export Data'), + ), + SettingsListTile( + title: 'Import Data', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Import Data'), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + 'Example Headline', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: 'Example Tile', + icon: Icons.upload_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Example Tile'), + ), + SettingsListTile( + title: 'Example Tile', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Example Tile'), + ), + ], + ), + ), + ), ); } } From 178aaa964323e4ea7fbcb2dae09fc53a4c892b94 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:25:12 +0100 Subject: [PATCH 04/53] Added function headers --- .../views/main_menu/settings_view.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 75ea163..b05ae8d 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -21,7 +21,7 @@ class SettingsView extends StatelessWidget { padding: EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - 'MenĂ¼', + 'Menu', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -32,7 +32,7 @@ class SettingsView extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), child: Text( textAlign: TextAlign.start, - 'Einstellungen', + 'Settings', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -44,13 +44,13 @@ class SettingsView extends StatelessWidget { title: 'Export Data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Export Data'), + onPressed: () => exportData(), ), SettingsListTile( title: 'Import Data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Import Data'), + onPressed: () => importData(), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), @@ -81,4 +81,10 @@ class SettingsView extends StatelessWidget { ), ); } + + // TODO: Implement export functionality + void exportData() {} + + // TODO: Implement import functionality + void importData() {} } From a8a81c21513049fa6391517ad90ef6b322e8bb6d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 22:33:46 +0100 Subject: [PATCH 05/53] Fixed gesture detector area --- .../widgets/tiles/settings_list_tile.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 1174627..d5c421f 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -21,16 +21,16 @@ class SettingsListTile extends StatelessWidget { child: Center( child: SizedBox( width: MediaQuery.of(context).size.width * 0.95, - child: Container( - margin: EdgeInsets.zero, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: GestureDetector( - onTap: onPressed ?? () {}, + child: GestureDetector( + onTap: onPressed ?? () {}, + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ From d86de0904285a7acfd8685f20af68b554cdd8d52 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:16:57 +0100 Subject: [PATCH 06/53] Added fromJson, toJson --- lib/data/dto/game.dart | 17 +++++++++++++++++ lib/data/dto/group.dart | 13 +++++++++++++ lib/data/dto/player.dart | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index c84779d..a52ee29 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -21,4 +21,21 @@ class Game { String toString() { return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; } + + /// Creates a Game instance from a JSON object. + Game.fromJson(Map json) + : id = json['id'], + name = json['name'], + players = json['players'] != null + ? (json['players'] as List) + .map((playerJson) => Player.fromJson(playerJson)) + .toList() + : null, + group = json['group'] != null ? Group.fromJson(json['group']) : null, + winner = json['winner'] ?? ''; + + /// Converts the Game instance to a JSON object. + String toJson() { + return 'Game{id: $id,name: $name,players: $players,group: $group,winner: $winner}'; + } } diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 0420477..0546dbd 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -13,4 +13,17 @@ class Group { String toString() { return 'Group{id: $id, name: $name,members: $members}'; } + + /// Creates a Group instance from a JSON object. + Group.fromJson(Map json) + : id = json['id'], + name = json['name'], + members = (json['members'] as List) + .map((memberJson) => Player.fromJson(memberJson)) + .toList(); + + /// Converts the Group instance to a JSON object. + String toJson() { + return 'Group{id: $id, name: $name,members: $members}'; + } } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 1b00c2c..9f10729 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -10,4 +10,14 @@ class Player { String toString() { return 'Player{id: $id,name: $name}'; } + + /// Creates a Player instance from a JSON object. + Player.fromJson(Map json) + : id = json['id'], + name = json['name']; + + /// Converts the Player instance to a JSON object. + String toJson() { + return 'Player{id: $id,name: $name}'; + } } From 2da2e28cb66fd31f2074befb3fa0fcc6686b767f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:20:26 +0100 Subject: [PATCH 07/53] Added json schema --- assets/schema.json | 0 pubspec.yaml | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 assets/schema.json diff --git a/assets/schema.json b/assets/schema.json new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.yaml b/pubspec.yaml index fbbc01a..c7e55d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,3 +30,6 @@ dev_dependencies: flutter: uses-material-design: true + +assets: + - assets/schema.json From fd86f5193fbe68df9a5bf32fb00a0ad50030011f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:46:32 +0100 Subject: [PATCH 08/53] Fixed toJson methods --- lib/data/dto/game.dart | 10 +++++++--- lib/data/dto/group.dart | 8 +++++--- lib/data/dto/player.dart | 4 +--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index a52ee29..bec22fb 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -35,7 +35,11 @@ class Game { winner = json['winner'] ?? ''; /// Converts the Game instance to a JSON object. - String toJson() { - return 'Game{id: $id,name: $name,players: $players,group: $group,winner: $winner}'; - } + Map toJson() => { + 'id': id, + 'name': name, + 'players': players?.map((player) => player.toJson()).toList(), + 'group': group?.toJson(), + 'winner': winner, + }; } diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 0546dbd..71347b1 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -23,7 +23,9 @@ class Group { .toList(); /// Converts the Group instance to a JSON object. - String toJson() { - return 'Group{id: $id, name: $name,members: $members}'; - } + Map toJson() => { + 'id': id, + 'name': name, + 'members': members.map((member) => member.toJson()).toList(), + }; } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 9f10729..cc73f87 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -17,7 +17,5 @@ class Player { name = json['name']; /// Converts the Player instance to a JSON object. - String toJson() { - return 'Player{id: $id,name: $name}'; - } + Map toJson() => {'id': id, 'name': name}; } From 08fcaa35ee31ee83e891b343ab6c03ca4a4dbdd3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:59:18 +0100 Subject: [PATCH 09/53] Added methods for deleting all entities --- lib/data/dao/game_dao.dart | 8 ++++++++ lib/data/dao/group_dao.dart | 8 ++++++++ lib/data/dao/player_dao.dart | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index fc931ad..a5946f2 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -88,4 +88,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final result = await query.getSingleOrNull(); return result != null; } + + /// Deletes all games from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllGames() async { + final query = delete(gameTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..39f8c45 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -103,4 +103,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final result = await query.getSingleOrNull(); return result != null; } + + /// Deletes all groups from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllGroups() async { + final query = delete(groupTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 591634c..e0aa165 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -70,4 +70,12 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { .getSingle(); return count ?? 0; } + + /// Deletes all players from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllPlayers() async { + final query = delete(playerTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } From 42ce69f4d3faf4fd210364bf504543d2887c6290 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:59:28 +0100 Subject: [PATCH 10/53] Added schema.json --- assets/schema.json | 137 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/assets/schema.json b/assets/schema.json index e69de29..c33fab2 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -0,0 +1,137 @@ + + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "games": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "players": { + "type": "null" + }, + "group": { + "type": "null" + }, + "winner": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "players", + "group", + "winner" + ] + } + ] + }, + "groups": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "members": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + ] + } + }, + "required": [ + "id", + "name", + "members" + ] + } + ] + }, + "players": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + ] + } + }, + "required": [ + "games", + "groups", + "players" + ] +} + From 69c95ca672731b7562b28ec7aab7c9b905f510e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:22:13 +0100 Subject: [PATCH 11/53] Added custom statement for cascade deleting --- lib/data/db/database.dart | 9 ++ lib/data/db/database.g.dart | 304 +++++------------------------------- 2 files changed, 52 insertions(+), 261 deletions(-) diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 73ad73e..704e1f0 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -40,6 +40,15 @@ class AppDatabase extends _$AppDatabase { @override int get schemaVersion => 1; + @override + MigrationStrategy get migration { + return MigrationStrategy( + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + }, + ); + } + static QueryExecutor _openConnection() { return driftDatabase( name: 'gametracker_db', diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 03b7a10..20e4cc1 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -444,12 +444,9 @@ class $GameTableTable extends GameTable late final GeneratedColumn winnerId = GeneratedColumn( 'winner_id', aliasedName, - false, + true, type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id) ON DELETE CASCADE', - ), + requiredDuringInsert: false, ); @override List get $columns => [id, name, winnerId]; @@ -483,8 +480,6 @@ class $GameTableTable extends GameTable _winnerIdMeta, winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), ); - } else if (isInserting) { - context.missing(_winnerIdMeta); } return context; } @@ -506,7 +501,7 @@ class $GameTableTable extends GameTable winnerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}winner_id'], - )!, + ), ); } @@ -519,18 +514,16 @@ class $GameTableTable extends GameTable class GameTableData extends DataClass implements Insertable { final String id; final String name; - final String winnerId; - const GameTableData({ - required this.id, - required this.name, - required this.winnerId, - }); + final String? winnerId; + const GameTableData({required this.id, required this.name, this.winnerId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); - map['winner_id'] = Variable(winnerId); + if (!nullToAbsent || winnerId != null) { + map['winner_id'] = Variable(winnerId); + } return map; } @@ -538,7 +531,9 @@ class GameTableData extends DataClass implements Insertable { return GameTableCompanion( id: Value(id), name: Value(name), - winnerId: Value(winnerId), + winnerId: winnerId == null && nullToAbsent + ? const Value.absent() + : Value(winnerId), ); } @@ -550,7 +545,7 @@ class GameTableData extends DataClass implements Insertable { return GameTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), + winnerId: serializer.fromJson(json['winnerId']), ); } @override @@ -559,16 +554,19 @@ class GameTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), + 'winnerId': serializer.toJson(winnerId), }; } - GameTableData copyWith({String? id, String? name, String? winnerId}) => - GameTableData( - id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, - ); + GameTableData copyWith({ + String? id, + String? name, + Value winnerId = const Value.absent(), + }) => GameTableData( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId.present ? winnerId.value : this.winnerId, + ); GameTableData copyWithCompanion(GameTableCompanion data) { return GameTableData( id: data.id.present ? data.id.value : this.id, @@ -601,7 +599,7 @@ class GameTableData extends DataClass implements Insertable { class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; - final Value winnerId; + final Value winnerId; final Value rowid; const GameTableCompanion({ this.id = const Value.absent(), @@ -612,11 +610,10 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion.insert({ required String id, required String name, - required String winnerId, + this.winnerId = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), - name = Value(name), - winnerId = Value(winnerId); + name = Value(name); static Insertable custom({ Expression? id, Expression? name, @@ -634,7 +631,7 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion copyWith({ Value? id, Value? name, - Value? winnerId, + Value? winnerId, Value? rowid, }) { return GameTableCompanion( @@ -1381,13 +1378,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ - WritePropagation( - on: TableUpdateQuery.onTableName( - 'player_table', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('game_table', kind: UpdateKind.delete)], - ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', @@ -1450,24 +1440,6 @@ final class $$PlayerTableTableReferences extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$GameTableTable, List> - _gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.gameTable, - aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId), - ); - - $$GameTableTableProcessedTableManager get gameTableRefs { - final manager = $$GameTableTableTableManager( - $_db, - $_db.gameTable, - ).filter((f) => f.winnerId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } - static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -1534,31 +1506,6 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression gameTableRefs( - Expression Function($$GameTableTableFilterComposer f) f, - ) { - final $$GameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -1645,31 +1592,6 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - Expression gameTableRefs( - Expression Function($$GameTableTableAnnotationComposer a) f, - ) { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -1735,7 +1657,6 @@ class $$PlayerTableTableTableManager (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -1773,42 +1694,16 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - gameTableRefs = false, - playerGroupTableRefs = false, - playerGameTableRefs = false, - }) { + ({playerGroupTableRefs = false, playerGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (gameTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - GameTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._gameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).gameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.winnerId == item.id, - ), - typedResults: items, - ), if (playerGroupTableRefs) await $_getPrefetchedData< PlayerTableData, @@ -1872,7 +1767,6 @@ typedef $$PlayerTableTableProcessedTableManager = (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -2227,14 +2121,14 @@ typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ required String id, required String name, - required String winnerId, + Value winnerId, Value rowid, }); typedef $$GameTableTableUpdateCompanionBuilder = GameTableCompanion Function({ Value id, Value name, - Value winnerId, + Value winnerId, Value rowid, }); @@ -2242,25 +2136,6 @@ final class $$GameTableTableReferences extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PlayerTableTable _winnerIdTable(_$AppDatabase db) => - db.playerTable.createAlias( - $_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id), - ); - - $$PlayerTableTableProcessedTableManager get winnerId { - final $_column = $_itemColumn('winner_id')!; - - final manager = $$PlayerTableTableTableManager( - $_db, - $_db.playerTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_winnerIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - static MultiTypedResultKey<$PlayerGameTableTable, List> _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGameTable, @@ -2319,28 +2194,10 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - $$PlayerTableTableFilterComposer get winnerId { - final $$PlayerTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableFilterComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + ColumnFilters get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnFilters(column), + ); Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableFilterComposer f) f, @@ -2412,28 +2269,10 @@ class $$GameTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - $$PlayerTableTableOrderingComposer get winnerId { - final $$PlayerTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableOrderingComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + ColumnOrderings get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnOrderings(column), + ); } class $$GameTableTableAnnotationComposer @@ -2451,28 +2290,8 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - $$PlayerTableTableAnnotationComposer get winnerId { - final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableAnnotationComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + GeneratedColumn get winnerId => + $composableBuilder(column: $table.winnerId, builder: (column) => column); Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableAnnotationComposer a) f, @@ -2539,7 +2358,6 @@ class $$GameTableTableTableManager (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) @@ -2559,7 +2377,7 @@ class $$GameTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), - Value winnerId = const Value.absent(), + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion( id: id, @@ -2571,7 +2389,7 @@ class $$GameTableTableTableManager ({ required String id, required String name, - required String winnerId, + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion.insert( id: id, @@ -2588,49 +2406,14 @@ class $$GameTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - winnerId = false, - playerGameTableRefs = false, - groupGameTableRefs = false, - }) { + ({playerGameTableRefs = false, groupGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, ], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (winnerId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.winnerId, - referencedTable: $$GameTableTableReferences - ._winnerIdTable(db), - referencedColumn: $$GameTableTableReferences - ._winnerIdTable(db) - .id, - ) - as T; - } - - return state; - }, + addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (playerGameTableRefs) @@ -2696,7 +2479,6 @@ typedef $$GameTableTableProcessedTableManager = (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) From f6ebda7984414ac429c5e45227da508272070d30 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:22:35 +0100 Subject: [PATCH 12/53] Changed table column because of importing issues --- lib/data/db/tables/game_table.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 9651a79..b2dc6ba 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -1,11 +1,9 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; class GameTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); - TextColumn get winnerId => - text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + late final winnerId = text().nullable()(); @override Set> get primaryKey => {id}; From 5dcd0826bdd5b221170b4780c7f425be09706247 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:23:28 +0100 Subject: [PATCH 13/53] Adjusted attributes to table definition --- lib/data/dao/game_dao.dart | 2 +- lib/data/dao/game_dao.g.dart | 1 - lib/data/dao/group_game_dao.g.dart | 1 - lib/data/dto/game.dart | 11 +++-------- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index a5946f2..7df03b0 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -57,7 +57,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: game.winner, + winnerId: Value(game.winner), ), mode: InsertMode.insertOrReplace, ); diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart index ebf5524..b5a29fe 100644 --- a/lib/data/dao/game_dao.g.dart +++ b/lib/data/dao/game_dao.g.dart @@ -4,6 +4,5 @@ part of 'game_dao.dart'; // ignore_for_file: type=lint mixin _$GameDaoMixin on DatabaseAccessor { - $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; } diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart index 426f192..735a35f 100644 --- a/lib/data/dao/group_game_dao.g.dart +++ b/lib/data/dao/group_game_dao.g.dart @@ -5,7 +5,6 @@ part of 'group_game_dao.dart'; // ignore_for_file: type=lint mixin _$GroupGameDaoMixin on DatabaseAccessor { $GroupTableTable get groupTable => attachedDatabase.groupTable; - $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; } diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index bec22fb..60bcff0 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -7,15 +7,10 @@ class Game { final String name; final List? players; final Group? group; - final String winner; + final String? winner; - Game({ - String? id, - required this.name, - this.players, - this.group, - this.winner = '', - }) : id = id ?? const Uuid().v4(); + Game({String? id, required this.name, this.players, this.group, this.winner}) + : id = id ?? const Uuid().v4(); @override String toString() { From f2a749cb0fe7b43f6b232437f8487c43e07f3e3c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:24:08 +0100 Subject: [PATCH 14/53] First version of settings view --- .../views/main_menu/settings_view.dart | 218 +++++++++++++++--- pubspec.yaml | 8 +- 2 files changed, 192 insertions(+), 34 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index b05ae8d..2a1d193 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,10 +1,55 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'dart:convert'; +import 'dart:io'; -class SettingsView extends StatelessWidget { +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:json_schema/json_schema.dart'; +import 'package:provider/provider.dart'; + +class SettingsView extends StatefulWidget { const SettingsView({super.key}); + @override + State createState() => _SettingsViewState(); + + /// Helper method to read file content from either bytes or path + static Future _readFileContent(PlatformFile file) async { + if (file.bytes != null) return utf8.decode(file.bytes!); + if (file.path != null) return await File(file.path!).readAsString(); + + throw Exception('Die Datei hat keinen lesbaren Inhalt'); + } + + static Future validateJsonSchema(String jsonString) async { + final String schemaString; + + schemaString = await rootBundle.loadString('assets/schema.json'); + + try { + final schema = JsonSchema.create(json.decode(schemaString)); + final jsonData = json.decode(jsonString); + final result = schema.validate(jsonData); + + if (result.isValid) { + return true; + } + return false; + } catch (e, stack) { + print('[validateJsonSchema] $e'); + print(stack); + return false; + } + } +} + +class _SettingsViewState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -41,39 +86,31 @@ class SettingsView extends StatelessWidget { ), SettingsListTile( - title: 'Export Data', + title: 'Export data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => exportData(), + onPressed: () async { + final String json = await _getAppDataAsJson(context); + await exportData(json, 'export'); + }, ), SettingsListTile( - title: 'Import Data', + title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => importData(), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), - child: Text( - textAlign: TextAlign.start, - 'Example Headline', - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), + onPressed: () => importData(context), ), SettingsListTile( - title: 'Example Tile', + title: 'Delete all data', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => deleteAllData(context), + ), + SettingsListTile( + title: 'Add Sample Data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Example Tile'), - ), - SettingsListTile( - title: 'Example Tile', - icon: Icons.download_outlined, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Example Tile'), + onPressed: () => addSampleData(context), ), ], ), @@ -82,9 +119,128 @@ class SettingsView extends StatelessWidget { ); } - // TODO: Implement export functionality - void exportData() {} + Future deleteAllData(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.gameDao.deleteAllGames(); + await db.groupDao.deleteAllGroups(); + await db.playerDao.deleteAllPlayers(); + print('[deleteAllData] All data deleted'); + } - // TODO: Implement import functionality - void importData() {} + Future addSampleData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final player1 = Player(name: 'Alice'); + final player2 = Player(name: 'Bob'); + final group = Group(name: 'Friends', members: [player1, player2]); + final game = Game(name: 'Sample Game', group: group, winner: 'Alice'); + + await db.playerDao.addPlayer(player: player1); + await db.playerDao.addPlayer(player: player2); + await db.groupDao.addGroup(group: group); + await db.gameDao.addGame(game: game); + } + + Future _getAppDataAsJson(BuildContext context) async { + final db = Provider.of(context, listen: false); + final games = await db.gameDao.getAllGames(); + final groups = await db.groupDao.getAllGroups(); + final players = await db.playerDao.getAllPlayers(); + + // Construct a JSON representation of the data + final Map jsonMap = { + 'games': games.map((game) => game.toJson()).toList(), + 'groups': groups.map((group) => group.toJson()).toList(), + 'players': players.map((player) => player.toJson()).toList(), + }; + + return json.encode(jsonMap); + } + + Future exportData(String jsonString, String fileName) async { + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + await FilePicker.platform.saveFile( + fileName: '$fileName.json', + bytes: bytes, + ); + return true; + } catch (e, stack) { + print('[exportData] $e'); + print(stack); + return false; + } + } + + Future importData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final path = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + + if (path == null) { + print('[importData] No file selected'); + return; + } + + try { + final jsonString = await SettingsView._readFileContent(path.files.single); + + // Checks if the JSON String is in the gameList format + if (await SettingsView.validateJsonSchema(jsonString)) { + final Map jsonData = + json.decode(jsonString) as Map; + print('[importData] : $jsonData'); + final List? gamesJson = jsonData['games'] as List?; + final List? groupsJson = jsonData['groups'] as List?; + final List? playersJson = + jsonData['players'] as List?; + + final List importedGames = + gamesJson + ?.map((g) => Game.fromJson(g as Map)) + .toList() ?? + []; + final List importedGroups = + groupsJson + ?.map((g) => Group.fromJson(g as Map)) + .toList() ?? + []; + final List importedPlayers = + playersJson + ?.map((p) => Player.fromJson(p as Map)) + .toList() ?? + []; + + for (Player player in importedPlayers) { + await db.playerDao.addPlayer(player: player); + } + + for (Group group in importedGroups) { + await db.groupDao.addGroup(group: group); + } + + for (Game game in importedGames) { + await db.gameDao.addGame(game: game); + } + } else { + print('[importData] Invalid JSON schema'); + return; + } + print('[importData] Data imported successfully'); + return; + } on FormatException catch (e, stack) { + print('[importData] FormatException'); + print('[importData] $e'); + print(stack); + return; + } on Exception catch (e, stack) { + print('[importData] Exception'); + print('[importData] $e'); + print(stack); + return; + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index c7e55d7..bce31f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,9 @@ dependencies: provider: ^6.1.5 skeletonizer: ^2.1.0+1 uuid: ^4.5.2 + file_picker: ^10.3.6 + json_schema: ^5.2.2 + file_saver: ^0.3.1 dev_dependencies: flutter_test: @@ -30,6 +33,5 @@ dev_dependencies: flutter: uses-material-design: true - -assets: - - assets/schema.json + assets: + - assets/schema.json From 82e28b7509a29bc23dd59f8a01a0bc4a156f1ea8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:32:16 +0100 Subject: [PATCH 15/53] Refactored whole export/import methods in DataTransferService --- .../views/main_menu/settings_view.dart | 160 +----------------- lib/services/data_transfer_service.dart | 135 +++++++++++++++ 2 files changed, 144 insertions(+), 151 deletions(-) create mode 100644 lib/services/data_transfer_service.dart diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 2a1d193..f0a530b 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,17 +1,11 @@ import 'dart:convert'; -import 'dart:io'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:json_schema/json_schema.dart'; -import 'package:provider/provider.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -19,14 +13,6 @@ class SettingsView extends StatefulWidget { @override State createState() => _SettingsViewState(); - /// Helper method to read file content from either bytes or path - static Future _readFileContent(PlatformFile file) async { - if (file.bytes != null) return utf8.decode(file.bytes!); - if (file.path != null) return await File(file.path!).readAsString(); - - throw Exception('Die Datei hat keinen lesbaren Inhalt'); - } - static Future validateJsonSchema(String jsonString) async { final String schemaString; @@ -84,33 +70,30 @@ class _SettingsViewState extends State { ), ), ), - SettingsListTile( title: 'Export data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { - final String json = await _getAppDataAsJson(context); - await exportData(json, 'export'); + final String json = + await DataTransferService.getAppDataAsJson(context); + await DataTransferService.exportData( + json, + 'exported_data', + ); }, ), SettingsListTile( title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => importData(context), + onPressed: () => DataTransferService.importData(context), ), SettingsListTile( title: 'Delete all data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => deleteAllData(context), - ), - SettingsListTile( - title: 'Add Sample Data', - icon: Icons.upload_outlined, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => addSampleData(context), + onPressed: () => DataTransferService.deleteAllData(context), ), ], ), @@ -118,129 +101,4 @@ class _SettingsViewState extends State { ), ); } - - Future deleteAllData(BuildContext context) async { - final db = Provider.of(context, listen: false); - await db.gameDao.deleteAllGames(); - await db.groupDao.deleteAllGroups(); - await db.playerDao.deleteAllPlayers(); - print('[deleteAllData] All data deleted'); - } - - Future addSampleData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final player1 = Player(name: 'Alice'); - final player2 = Player(name: 'Bob'); - final group = Group(name: 'Friends', members: [player1, player2]); - final game = Game(name: 'Sample Game', group: group, winner: 'Alice'); - - await db.playerDao.addPlayer(player: player1); - await db.playerDao.addPlayer(player: player2); - await db.groupDao.addGroup(group: group); - await db.gameDao.addGame(game: game); - } - - Future _getAppDataAsJson(BuildContext context) async { - final db = Provider.of(context, listen: false); - final games = await db.gameDao.getAllGames(); - final groups = await db.groupDao.getAllGroups(); - final players = await db.playerDao.getAllPlayers(); - - // Construct a JSON representation of the data - final Map jsonMap = { - 'games': games.map((game) => game.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), - }; - - return json.encode(jsonMap); - } - - Future exportData(String jsonString, String fileName) async { - try { - final bytes = Uint8List.fromList(utf8.encode(jsonString)); - await FilePicker.platform.saveFile( - fileName: '$fileName.json', - bytes: bytes, - ); - return true; - } catch (e, stack) { - print('[exportData] $e'); - print(stack); - return false; - } - } - - Future importData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final path = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['json'], - ); - - if (path == null) { - print('[importData] No file selected'); - return; - } - - try { - final jsonString = await SettingsView._readFileContent(path.files.single); - - // Checks if the JSON String is in the gameList format - if (await SettingsView.validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; - print('[importData] : $jsonData'); - final List? gamesJson = jsonData['games'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; - - final List importedGames = - gamesJson - ?.map((g) => Game.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; - - for (Player player in importedPlayers) { - await db.playerDao.addPlayer(player: player); - } - - for (Group group in importedGroups) { - await db.groupDao.addGroup(group: group); - } - - for (Game game in importedGames) { - await db.gameDao.addGame(game: game); - } - } else { - print('[importData] Invalid JSON schema'); - return; - } - print('[importData] Data imported successfully'); - return; - } on FormatException catch (e, stack) { - print('[importData] FormatException'); - print('[importData] $e'); - print(stack); - return; - } on Exception catch (e, stack) { - print('[importData] Exception'); - print('[importData] $e'); - print(stack); - return; - } - } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart new file mode 100644 index 0000000..839c0e0 --- /dev/null +++ b/lib/services/data_transfer_service.dart @@ -0,0 +1,135 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.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/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:provider/provider.dart'; + +class DataTransferService { + /// Deletes all data from the database. + static Future deleteAllData(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.gameDao.deleteAllGames(); + await db.groupDao.deleteAllGroups(); + await db.playerDao.deleteAllPlayers(); + print('[deleteAllData] All data deleted'); + } + + static Future getAppDataAsJson(BuildContext context) async { + final db = Provider.of(context, listen: false); + final games = await db.gameDao.getAllGames(); + final groups = await db.groupDao.getAllGroups(); + final players = await db.playerDao.getAllPlayers(); + + // Construct a JSON representation of the data + final Map jsonMap = { + 'games': games.map((game) => game.toJson()).toList(), + 'groups': groups.map((group) => group.toJson()).toList(), + 'players': players.map((player) => player.toJson()).toList(), + }; + + return json.encode(jsonMap); + } + + /// Exports the given JSON string to a file with the specified name. + static Future exportData(String jsonString, String fileName) async { + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + await FilePicker.platform.saveFile( + fileName: '$fileName.json', + bytes: bytes, + ); + return true; + } catch (e, stack) { + print('[exportData] $e'); + print(stack); + return false; + } + } + + /// Imports data from a selected JSON file into the database. + static Future importData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final path = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + + if (path == null) { + print('[importData] No file selected'); + return; + } + + try { + final jsonString = await _readFileContent(path.files.single); + + if (await SettingsView.validateJsonSchema(jsonString)) { + final Map jsonData = + json.decode(jsonString) as Map; + + final List? gamesJson = jsonData['games'] as List?; + final List? groupsJson = jsonData['groups'] as List?; + final List? playersJson = + jsonData['players'] as List?; + + final List importedGames = + gamesJson + ?.map((g) => Game.fromJson(g as Map)) + .toList() ?? + []; + final List importedGroups = + groupsJson + ?.map((g) => Group.fromJson(g as Map)) + .toList() ?? + []; + final List importedPlayers = + playersJson + ?.map((p) => Player.fromJson(p as Map)) + .toList() ?? + []; + + for (Player player in importedPlayers) { + await db.playerDao.addPlayer(player: player); + } + + for (Group group in importedGroups) { + await db.groupDao.addGroup(group: group); + } + + for (Game game in importedGames) { + await db.gameDao.addGame(game: game); + } + } else { + print('[importData] Invalid JSON schema'); + return; + } + print('[importData] Data imported successfully'); + return; + } on FormatException catch (e, stack) { + print('[importData] FormatException'); + print('[importData] $e'); + print(stack); + return; + } on Exception catch (e, stack) { + print('[importData] Exception'); + print('[importData] $e'); + print(stack); + return; + } + } + + /// Helper method to read file content from either bytes or path + static Future _readFileContent(PlatformFile file) async { + if (file.bytes != null) return utf8.decode(file.bytes!); + if (file.path != null) return await File(file.path!).readAsString(); + + throw Exception('Die Datei hat keinen lesbaren Inhalt'); + } +} From 322c51a7646da556e4c8a92924c5aa9ad237d872 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:44:48 +0100 Subject: [PATCH 16/53] Added documentation and feedback in snackbar --- .../views/main_menu/settings_view.dart | 97 ++++++++++++++++++- lib/services/data_transfer_service.dart | 56 +++++++---- 2 files changed, 133 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index f0a530b..1a762ce 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -77,23 +77,37 @@ class _SettingsViewState extends State { onPressed: () async { final String json = await DataTransferService.getAppDataAsJson(context); - await DataTransferService.exportData( + final result = await DataTransferService.exportData( json, 'exported_data', ); + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); }, ), SettingsListTile( title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => DataTransferService.importData(context), + onPressed: () async { + final result = await DataTransferService.importData( + context, + ); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, ), SettingsListTile( title: 'Delete all data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => DataTransferService.deleteAllData(context), + onPressed: () { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: 'Data successfully deleted', + ); + }, ), ], ), @@ -101,4 +115,81 @@ class _SettingsViewState extends State { ), ); } + + /// Displays a snackbar based on the import result. + /// + /// [context] The BuildContext to show the snackbar in. + /// [result] The result of the import operation. + void showImportSnackBar({ + required BuildContext context, + required ImportResult result, + }) { + switch (result) { + case ImportResult.success: + showSnackbar(context: context, message: 'Data successfully imported'); + case ImportResult.invalidSchema: + showSnackbar(context: context, message: 'Invalid Schema'); + case ImportResult.fileReadError: + showSnackbar(context: context, message: 'Error reading file'); + case ImportResult.canceled: + showSnackbar(context: context, message: 'Import canceled'); + case ImportResult.formatException: + showSnackbar( + context: context, + message: 'Format Exception (see console)', + ); + case ImportResult.unknownException: + showSnackbar( + context: context, + message: 'Unknown Exception (see console)', + ); + } + } + + /// Displays a snackbar based on the export result. + /// + /// [context] The BuildContext to show the snackbar in. + /// [result] The result of the export operation. + void showExportSnackBar({ + required BuildContext context, + required ExportResult result, + }) { + switch (result) { + case ExportResult.success: + showSnackbar(context: context, message: 'Data successfully exported'); + case ExportResult.canceled: + showSnackbar(context: context, message: 'Export canceled'); + case ExportResult.unknownException: + showSnackbar( + context: context, + message: 'Unknown Exception (see console)', + ); + } + } + + /// Displays a snackbar with the given message and optional action. + /// + /// [context] The BuildContext to show the snackbar in. + /// [message] The message to display in the snackbar. + /// [duration] The duration for which the snackbar is displayed. + /// [action] An optional callback function to execute when the action button is pressed. + void showSnackbar({ + required BuildContext context, + required String message, + Duration duration = const Duration(seconds: 3), + VoidCallback? action, + }) { + final messenger = ScaffoldMessenger.of(context); + messenger.hideCurrentSnackBar(); + messenger.showSnackBar( + SnackBar( + content: Text(message, style: const TextStyle(color: Colors.white)), + backgroundColor: CustomTheme.onBoxColor, + duration: duration, + action: action != null + ? SnackBarAction(label: 'RĂ¼ckgängig', onPressed: action) + : null, + ), + ); + } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 839c0e0..6eba1ee 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -11,6 +11,17 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:provider/provider.dart'; +enum ImportResult { + success, + canceled, + fileReadError, + invalidSchema, + formatException, + unknownException, +} + +enum ExportResult { success, canceled, unknownException } + class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { @@ -18,9 +29,10 @@ class DataTransferService { await db.gameDao.deleteAllGames(); await db.groupDao.deleteAllGroups(); await db.playerDao.deleteAllPlayers(); - print('[deleteAllData] All data deleted'); } + /// Retrieves all application data and converts it to a JSON string. + /// Returns the JSON string representation of the data. static Future getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); final games = await db.gameDao.getAllGames(); @@ -38,23 +50,34 @@ class DataTransferService { } /// Exports the given JSON string to a file with the specified name. - static Future exportData(String jsonString, String fileName) async { + /// Returns an [ExportResult] indicating the outcome. + /// + /// [jsonString] The JSON string to be exported. + /// [fileName] The desired name for the exported file (without extension). + static Future exportData( + String jsonString, + String fileName, + ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); - await FilePicker.platform.saveFile( + final path = await FilePicker.platform.saveFile( fileName: '$fileName.json', bytes: bytes, ); - return true; + if (path == null) { + return ExportResult.canceled; + } else { + return ExportResult.success; + } } catch (e, stack) { print('[exportData] $e'); print(stack); - return false; + return ExportResult.unknownException; } } /// Imports data from a selected JSON file into the database. - static Future importData(BuildContext context) async { + static Future importData(BuildContext context) async { final db = Provider.of(context, listen: false); final path = await FilePicker.platform.pickFiles( @@ -63,12 +86,14 @@ class DataTransferService { ); if (path == null) { - print('[importData] No file selected'); - return; + return ImportResult.canceled; } try { final jsonString = await _readFileContent(path.files.single); + if (jsonString == null) { + return ImportResult.fileReadError; + } if (await SettingsView.validateJsonSchema(jsonString)) { final Map jsonData = @@ -107,29 +132,26 @@ class DataTransferService { await db.gameDao.addGame(game: game); } } else { - print('[importData] Invalid JSON schema'); - return; + return ImportResult.invalidSchema; } - print('[importData] Data imported successfully'); - return; + return ImportResult.success; } on FormatException catch (e, stack) { print('[importData] FormatException'); print('[importData] $e'); print(stack); - return; + return ImportResult.formatException; } on Exception catch (e, stack) { print('[importData] Exception'); print('[importData] $e'); print(stack); - return; + return ImportResult.unknownException; } } /// Helper method to read file content from either bytes or path - static Future _readFileContent(PlatformFile file) async { + static Future _readFileContent(PlatformFile file) async { if (file.bytes != null) return utf8.decode(file.bytes!); if (file.path != null) return await File(file.path!).readAsString(); - - throw Exception('Die Datei hat keinen lesbaren Inhalt'); + return null; } } From 87b1a7d57f547db37ad2ff868e3476d862605672 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 19:26:35 +0100 Subject: [PATCH 17/53] Moved ImportStatus & ExportStatus to enums.dart --- lib/core/enums.dart | 22 +++++++++++++++++++ .../views/main_menu/settings_view.dart | 1 + lib/services/data_transfer_service.dart | 12 +--------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 320eaf7..8c809b0 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -1,2 +1,24 @@ /// Button types used for styling the [CustomWidthButton] enum ButtonType { primary, secondary, tertiary } + +/// Result types for import operations in the [SettingsView] +/// - [ImportResult.success]: The import operation was successful. +/// - [ImportResult.canceled]: The import operation was canceled by the user. +/// - [ImportResult.fileReadError]: There was an error reading the selected file. +/// - [ImportResult.invalidSchema]: The JSON schema of the imported data is invalid. +/// - [ImportResult.formatException]: A format exception occurred during import. +/// - [ImportResult.unknownException]: An exception occurred during import. +enum ImportResult { + success, + canceled, + fileReadError, + invalidSchema, + formatException, + unknownException, +} + +/// Result types for export operations in the [SettingsView] +/// - [ExportResult.success]: The export operation was successful. +/// - [ExportResult.canceled]: The export operation was canceled by the user. +/// - [ExportResult.unknownException]: An exception occurred during export. +enum ExportResult { success, canceled, unknownException } diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 1a762ce..e679467 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:json_schema/json_schema.dart'; diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 6eba1ee..93c788d 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -11,17 +12,6 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:provider/provider.dart'; -enum ImportResult { - success, - canceled, - fileReadError, - invalidSchema, - formatException, - unknownException, -} - -enum ExportResult { success, canceled, unknownException } - class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { From 9434282ed194b089c1c075ea5410c5c549301728 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:20:21 +0100 Subject: [PATCH 18/53] Added createdAt attribute in dto classes and json schema --- assets/schema.json | 45 ++++++++++++---------------------------- lib/data/dto/game.dart | 4 +++- lib/data/dto/group.dart | 4 +++- lib/data/dto/player.dart | 9 ++++++-- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index c33fab2..eedfb05 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -13,6 +13,9 @@ "id": { "type": "string" }, + "createdAt": { + "type": "string" + }, "name": { "type": "string" }, @@ -28,6 +31,7 @@ }, "required": [ "id", + "createdAt", "name", "players", "group", @@ -45,6 +49,9 @@ "id": { "type": "string" }, + "createdAt": { + "type": "string" + }, "name": { "type": "string" }, @@ -57,19 +64,7 @@ "id": { "type": "string" }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "name" - ] - }, - { - "type": "object", - "properties": { - "id": { + "createdAt": { "type": "string" }, "name": { @@ -78,6 +73,7 @@ }, "required": [ "id", + "createdAt", "name" ] } @@ -86,6 +82,7 @@ }, "required": [ "id", + "createdAt", "name", "members" ] @@ -101,19 +98,7 @@ "id": { "type": "string" }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "name" - ] - }, - { - "type": "object", - "properties": { - "id": { + "createdAt": { "type": "string" }, "name": { @@ -122,16 +107,12 @@ }, "required": [ "id", + "createdAt", "name" ] } ] } - }, - "required": [ - "games", - "groups", - "players" - ] + } } diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 30898e5..4188bc4 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -5,11 +5,11 @@ import 'package:uuid/uuid.dart'; class Game { final String id; + final DateTime createdAt; final String name; final List? players; final Group? group; final String? winner; - final DateTime createdAt; Game({ String? id, @@ -30,6 +30,7 @@ class Game { Game.fromJson(Map json) : id = json['id'], name = json['name'], + createdAt = DateTime.parse(json['createdAt']), players = json['players'] != null ? (json['players'] as List) .map((playerJson) => Player.fromJson(playerJson)) @@ -41,6 +42,7 @@ class Game { /// Converts the Game instance to a JSON object. Map toJson() => { 'id': id, + 'createdAt': createdAt.toIso8601String(), 'name': name, 'players': players?.map((player) => player.toJson()).toList(), 'group': group?.toJson(), diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 3f00bf5..92dbd09 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -4,9 +4,9 @@ import 'package:uuid/uuid.dart'; class Group { final String id; + final DateTime createdAt; final String name; final List members; - final DateTime createdAt; Group({ String? id, @@ -24,6 +24,7 @@ class Group { /// Creates a Group instance from a JSON object. Group.fromJson(Map json) : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), name = json['name'], members = (json['members'] as List) .map((memberJson) => Player.fromJson(memberJson)) @@ -32,6 +33,7 @@ class Group { /// Converts the Group instance to a JSON object. Map toJson() => { 'id': id, + 'createdAt': createdAt.toIso8601String(), 'name': name, 'members': members.map((member) => member.toJson()).toList(), }; diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index f7e05d2..cfb4f4b 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -3,8 +3,8 @@ import 'package:uuid/uuid.dart'; class Player { final String id; - final String name; final DateTime createdAt; + final String name; Player({String? id, DateTime? createdAt, required this.name}) : id = id ?? const Uuid().v4(), @@ -18,8 +18,13 @@ class Player { /// Creates a Player instance from a JSON object. Player.fromJson(Map json) : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), name = json['name']; /// Converts the Player instance to a JSON object. - Map toJson() => {'id': id, 'name': name}; + Map toJson() => { + 'id': id, + 'createdAt': createdAt.toIso8601String(), + 'name': name, + }; } From f7073a83a42c334a404e1ed21bf69d717f3d2345 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:21:30 +0100 Subject: [PATCH 19/53] Added insert mode --- lib/data/dao/player_game_dao.dart | 1 + lib/data/dao/player_group_dao.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 8f367f8..d58417e 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -46,6 +46,7 @@ class PlayerGameDao extends DatabaseAccessor }) async { await into(playerGameTable).insert( PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, ); } } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index fe067ae..e200958 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -56,6 +56,7 @@ class PlayerGroupDao extends DatabaseAccessor await into(playerGroupTable).insert( PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + mode: InsertMode.insertOrReplace, ); return true; From 412cfff9f53bebe9ea2df6a453c1c3687b440f2d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:21:55 +0100 Subject: [PATCH 20/53] Added methods for inserting list of players, groups, games --- lib/data/dao/game_dao.dart | 116 +++++++++- lib/data/dao/group_dao.dart | 50 +++- lib/data/dao/player_dao.dart | 24 ++ lib/data/db/database.g.dart | 293 +++--------------------- lib/services/data_transfer_service.dart | 14 +- 5 files changed, 220 insertions(+), 277 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 9ed9849..018866a 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -50,14 +50,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Also adds associated players and group if they exist. Future addGame({required Game game}) async { await db.transaction(() async { - for (final p in game.players ?? []) { - await db.playerDao.addPlayer(player: p); - await db.playerGameDao.addPlayerToGame(gameId: game.id, playerId: p.id); - } - if (game.group != null) { - await db.groupDao.addGroup(group: game.group!); - await db.groupGameDao.addGroupToGame(game.id, game.group!.id); - } await into(gameTable).insert( GameTableCompanion.insert( id: game.id, @@ -67,6 +59,114 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ), mode: InsertMode.insertOrReplace, ); + + if (game.players != null) { + await db.playerDao.addPlayers(players: game.players!); + for (final p in game.players ?? []) { + await db.playerGameDao.addPlayerToGame( + gameId: game.id, + playerId: p.id, + ); + } + } + + if (game.group != null) { + await db.groupDao.addGroup(group: game.group!); + await db.groupGameDao.addGroupToGame(game.id, game.group!.id); + } + }); + } + + Future addGames({required List games}) async { + if (games.isEmpty) return; + await db.transaction(() async { + // Add all games in batch + await db.batch( + (b) => b.insertAll( + gameTable, + games + .map( + (game) => GameTableCompanion.insert( + id: game.id, + name: game.name, + createdAt: game.createdAt, + winnerId: Value(game.winner), + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + // Add all groups of the games in batch + await db.batch( + (b) => b.insertAll( + db.groupTable, + games + .where((game) => game.group != null) + .map( + (game) => GroupTableCompanion.insert( + id: game.group!.id, + name: game.group!.name, + createdAt: game.group!.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all players of the games in batch + await db.batch((b) async { + for (final game in games) { + if (game.players != null) { + for (final p in game.players ?? []) { + b.insert( + db.playerGameTable, + PlayerGameTableCompanion.insert( + gameId: game.id, + playerId: p.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all group-game associations in batch + await db.batch((b) async { + for (final game in games) { + if (game.group != null) { + b.insert( + db.groupGameTable, + GroupGameTableCompanion.insert( + gameId: game.id, + groupId: game.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + }); + + // Add all player-game associations in batch + await db.batch((b) async { + for (final game in games) { + if (game.players != null) { + for (final p in game.players ?? []) { + b.insert( + db.playerTable, + PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); }); } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 6eaea09..695d78a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -57,6 +57,10 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { name: group.name, createdAt: group.createdAt, ), + mode: InsertMode.insertOrReplace, + ); + await Future.wait( + group.members.map((player) => db.playerDao.addPlayer(player: player)), ); await db.batch( (b) => b.insertAll( @@ -69,17 +73,57 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), + mode: InsertMode.insertOrReplace, ), ); - await Future.wait( - group.members.map((player) => db.playerDao.addPlayer(player: player)), - ); }); return true; } return false; } + /// Adds multiple groups to the database. + /// Also adds the group's members to the [PlayerGroupTable]. + Future addGroups({required List groups}) async { + if (groups.isEmpty) return; + await db.transaction(() async { + await db.batch( + (b) => b.insertAll( + groupTable, + groups + .map( + (group) => GroupTableCompanion.insert( + id: group.id, + name: group.name, + createdAt: group.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + for (final group in groups) { + await db.playerDao.addPlayers(players: group.members); + + await db.batch( + (b) => b.insertAll( + db.playerGroupTable, + group.members + .map( + (member) => PlayerGroupTableCompanion.insert( + playerId: member.id, + groupId: group.id, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + }); + } + /// Deletes the group with the given [id] from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future deleteGroup({required String groupId}) async { diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 353819c..53e251f 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -42,12 +42,36 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { name: player.name, createdAt: player.createdAt, ), + mode: InsertMode.insertOrReplace, ); return true; } return false; } + /// Adds multiple [players] to the database in a batch operation. + Future addPlayers({required List players}) async { + if (players.isEmpty) return false; + + await db.batch( + (b) => b.insertAll( + playerTable, + players + .map( + (player) => PlayerTableCompanion.insert( + id: player.id, + name: player.name, + createdAt: player.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + return true; + } + /// Deletes the player with the given [id] from the database. /// Returns `true` if the player was deleted, `false` if the player did not exist. Future deletePlayer({required String playerId}) async { diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 3f10169..f211d0c 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -552,12 +552,9 @@ class $GameTableTable extends GameTable late final GeneratedColumn winnerId = GeneratedColumn( 'winner_id', aliasedName, - false, + true, type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id) ON DELETE CASCADE', - ), + requiredDuringInsert: false, ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', @@ -602,8 +599,6 @@ class $GameTableTable extends GameTable _winnerIdMeta, winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), ); - } else if (isInserting) { - context.missing(_winnerIdMeta); } if (data.containsKey('created_at')) { context.handle( @@ -633,7 +628,7 @@ class $GameTableTable extends GameTable winnerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}winner_id'], - )!, + ), createdAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}created_at'], @@ -650,12 +645,12 @@ class $GameTableTable extends GameTable class GameTableData extends DataClass implements Insertable { final String id; final String name; - final String winnerId; + final String? winnerId; final DateTime createdAt; const GameTableData({ required this.id, required this.name, - required this.winnerId, + this.winnerId, required this.createdAt, }); @override @@ -663,7 +658,9 @@ class GameTableData extends DataClass implements Insertable { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); - map['winner_id'] = Variable(winnerId); + if (!nullToAbsent || winnerId != null) { + map['winner_id'] = Variable(winnerId); + } map['created_at'] = Variable(createdAt); return map; } @@ -672,7 +669,9 @@ class GameTableData extends DataClass implements Insertable { return GameTableCompanion( id: Value(id), name: Value(name), - winnerId: Value(winnerId), + winnerId: winnerId == null && nullToAbsent + ? const Value.absent() + : Value(winnerId), createdAt: Value(createdAt), ); } @@ -685,7 +684,7 @@ class GameTableData extends DataClass implements Insertable { return GameTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), + winnerId: serializer.fromJson(json['winnerId']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -695,7 +694,7 @@ class GameTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), + 'winnerId': serializer.toJson(winnerId), 'createdAt': serializer.toJson(createdAt), }; } @@ -703,12 +702,12 @@ class GameTableData extends DataClass implements Insertable { GameTableData copyWith({ String? id, String? name, - String? winnerId, + Value winnerId = const Value.absent(), DateTime? createdAt, }) => GameTableData( id: id ?? this.id, name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, + winnerId: winnerId.present ? winnerId.value : this.winnerId, createdAt: createdAt ?? this.createdAt, ); GameTableData copyWithCompanion(GameTableCompanion data) { @@ -746,7 +745,7 @@ class GameTableData extends DataClass implements Insertable { class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; - final Value winnerId; + final Value winnerId; final Value createdAt; final Value rowid; const GameTableCompanion({ @@ -759,12 +758,11 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion.insert({ required String id, required String name, - required String winnerId, + this.winnerId = const Value.absent(), required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), name = Value(name), - winnerId = Value(winnerId), createdAt = Value(createdAt); static Insertable custom({ Expression? id, @@ -785,7 +783,7 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion copyWith({ Value? id, Value? name, - Value? winnerId, + Value? winnerId, Value? createdAt, Value? rowid, }) { @@ -1538,13 +1536,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ - WritePropagation( - on: TableUpdateQuery.onTableName( - 'player_table', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('game_table', kind: UpdateKind.delete)], - ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', @@ -1609,24 +1600,6 @@ final class $$PlayerTableTableReferences extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$GameTableTable, List> - _gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.gameTable, - aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId), - ); - - $$GameTableTableProcessedTableManager get gameTableRefs { - final manager = $$GameTableTableTableManager( - $_db, - $_db.gameTable, - ).filter((f) => f.winnerId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } - static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -1698,31 +1671,6 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression gameTableRefs( - Expression Function($$GameTableTableFilterComposer f) f, - ) { - final $$GameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -1817,31 +1765,6 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - Expression gameTableRefs( - Expression Function($$GameTableTableAnnotationComposer a) f, - ) { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -1907,7 +1830,6 @@ class $$PlayerTableTableTableManager (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -1956,42 +1878,16 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - gameTableRefs = false, - playerGroupTableRefs = false, - playerGameTableRefs = false, - }) { + ({playerGroupTableRefs = false, playerGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (gameTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - GameTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._gameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).gameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.winnerId == item.id, - ), - typedResults: items, - ), if (playerGroupTableRefs) await $_getPrefetchedData< PlayerTableData, @@ -2055,7 +1951,6 @@ typedef $$PlayerTableTableProcessedTableManager = (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -2436,7 +2331,7 @@ typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ required String id, required String name, - required String winnerId, + Value winnerId, required DateTime createdAt, Value rowid, }); @@ -2444,7 +2339,7 @@ typedef $$GameTableTableUpdateCompanionBuilder = GameTableCompanion Function({ Value id, Value name, - Value winnerId, + Value winnerId, Value createdAt, Value rowid, }); @@ -2453,25 +2348,6 @@ final class $$GameTableTableReferences extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PlayerTableTable _winnerIdTable(_$AppDatabase db) => - db.playerTable.createAlias( - $_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id), - ); - - $$PlayerTableTableProcessedTableManager get winnerId { - final $_column = $_itemColumn('winner_id')!; - - final manager = $$PlayerTableTableTableManager( - $_db, - $_db.playerTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_winnerIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - static MultiTypedResultKey<$PlayerGameTableTable, List> _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGameTable, @@ -2530,34 +2406,16 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column), ); - $$PlayerTableTableFilterComposer get winnerId { - final $$PlayerTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableFilterComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableFilterComposer f) f, ) { @@ -2628,33 +2486,15 @@ class $$GameTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column), ); - - $$PlayerTableTableOrderingComposer get winnerId { - final $$PlayerTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableOrderingComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } } class $$GameTableTableAnnotationComposer @@ -2672,32 +2512,12 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get winnerId => + $composableBuilder(column: $table.winnerId, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - $$PlayerTableTableAnnotationComposer get winnerId { - final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableAnnotationComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableAnnotationComposer a) f, ) { @@ -2763,7 +2583,6 @@ class $$GameTableTableTableManager (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) @@ -2783,7 +2602,7 @@ class $$GameTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), - Value winnerId = const Value.absent(), + Value winnerId = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion( @@ -2797,7 +2616,7 @@ class $$GameTableTableTableManager ({ required String id, required String name, - required String winnerId, + Value winnerId = const Value.absent(), required DateTime createdAt, Value rowid = const Value.absent(), }) => GameTableCompanion.insert( @@ -2816,49 +2635,14 @@ class $$GameTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - winnerId = false, - playerGameTableRefs = false, - groupGameTableRefs = false, - }) { + ({playerGameTableRefs = false, groupGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, ], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (winnerId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.winnerId, - referencedTable: $$GameTableTableReferences - ._winnerIdTable(db), - referencedColumn: $$GameTableTableReferences - ._winnerIdTable(db) - .id, - ) - as T; - } - - return state; - }, + addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (playerGameTableRefs) @@ -2924,7 +2708,6 @@ typedef $$GameTableTableProcessedTableManager = (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 93c788d..13eb658 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -110,17 +110,9 @@ class DataTransferService { .toList() ?? []; - for (Player player in importedPlayers) { - await db.playerDao.addPlayer(player: player); - } - - for (Group group in importedGroups) { - await db.groupDao.addGroup(group: group); - } - - for (Game game in importedGames) { - await db.gameDao.addGame(game: game); - } + await db.playerDao.addPlayers(players: importedPlayers); + await db.groupDao.addGroups(groups: importedGroups); + await db.gameDao.addGames(games: importedGames); } else { return ImportResult.invalidSchema; } From f7c1d6e975c6719122810426c47b9fd691f1b8e1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:13:29 +0100 Subject: [PATCH 21/53] Added missing await --- lib/data/dao/player_group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index e200958..93acf0b 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -51,7 +51,7 @@ class PlayerGroupDao extends DatabaseAccessor } if (await db.playerDao.playerExists(playerId: player.id) == false) { - db.playerDao.addPlayer(player: player); + await db.playerDao.addPlayer(player: player); } await into(playerGroupTable).insert( From cf71b40718d3a13005fb8f277b96ee9fcd351e0a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:19:54 +0100 Subject: [PATCH 22/53] changed export file name --- lib/presentation/views/main_menu/settings_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index e679467..a6e66fa 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -80,7 +80,7 @@ class _SettingsViewState extends State { await DataTransferService.getAppDataAsJson(context); final result = await DataTransferService.exportData( json, - 'exported_data', + 'game_tracker-data', ); if (!context.mounted) return; showExportSnackBar(context: context, result: result); From a8d4e640cfaeee074deff45956fec538c550039c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:39:01 +0100 Subject: [PATCH 23/53] Tabs update themselves after settings view --- .../main_menu/custom_navigation_bar.dart | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 20ced7a..9dd0658 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -17,12 +17,7 @@ class CustomNavigationBar extends StatefulWidget { class _CustomNavigationBarState extends State with SingleTickerProviderStateMixin { int currentIndex = 0; - final List tabs = [ - const HomeView(), - const GameHistoryView(), - const GroupsView(), - const StatisticsView(), - ]; + int tabKeyCount = 0; @override void initState() { @@ -31,6 +26,22 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { + // Pretty ugly but works + final List tabs = [ + KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), + KeyedSubtree( + key: ValueKey('games_$tabKeyCount'), + child: const GameHistoryView(), + ), + KeyedSubtree( + key: ValueKey('groups_$tabKeyCount'), + child: const GroupsView(), + ), + KeyedSubtree( + key: ValueKey('stats_$tabKeyCount'), + child: const StatisticsView(), + ), + ]; return Scaffold( appBar: AppBar( centerTitle: true, @@ -42,10 +53,15 @@ class _CustomNavigationBarState extends State scrolledUnderElevation: 0, actions: [ IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsView()), - ), + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), + ); + setState(() { + tabKeyCount++; + }); + }, icon: const Icon(Icons.settings), ), ], From 822bc03c83363789ef412f7bcdfbecf3d1ebcf45 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:41:30 +0100 Subject: [PATCH 24/53] Added dialog --- .../views/main_menu/settings_view.dart | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index a6e66fa..70f7663 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -103,11 +103,31 @@ class _SettingsViewState extends State { icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { - DataTransferService.deleteAllData(context); - showSnackbar( + showDialog( context: context, - message: 'Data successfully deleted', - ); + builder: (context) => AlertDialog( + title: const Text('Delete all data?'), + content: const Text('This can\'t be undone'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Löschen'), + ), + ], + ), + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: 'Daten erfolgreich gelöscht', + ); + } + }); }, ), ], From f40a9ad09b0c0f119a35e788cb2c44e6c38ebc28 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:42:07 +0100 Subject: [PATCH 25/53] Updated schema --- assets/schema.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index eedfb05..4bbd5d3 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -20,10 +20,10 @@ "type": "string" }, "players": { - "type": "null" + "type": "object" }, "group": { - "type": "null" + "type": "object" }, "winner": { "type": "string" @@ -33,8 +33,6 @@ "id", "createdAt", "name", - "players", - "group", "winner" ] } From 45650133a7daa9b41bdd7a989e1dd6a95339865d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:51:13 +0100 Subject: [PATCH 26/53] Corrected schema again --- assets/schema.json | 84 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 4bbd5d3..69f889b 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -1,5 +1,3 @@ - - { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", @@ -20,15 +18,78 @@ "type": "string" }, "players": { - "type": "object" - }, - "group": { - "type": "object" - }, - "winner": { - "type": "string" + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] } }, + "group": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "members": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] + } + ] + } + }, + "required": [ + "id", + "createdAt", + "name", + "members" + ] + }, + "winner": { + "type": "string" + }, "required": [ "id", "createdAt", @@ -91,7 +152,10 @@ "type": "array", "items": [ { - "type": "object", + "type": [ + "object", + "null" + ], "properties": { "id": { "type": "string" From a61818dd77613c3ed6294086963a1de23e0dbf78 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:40:56 +0100 Subject: [PATCH 27/53] Fixed error in getAllGames method --- lib/data/dao/game_dao.dart | 22 +++++++++++++++++----- lib/data/dao/group_game_dao.dart | 9 +++++++-- lib/data/dao/player_game_dao.dart | 8 ++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 94d010c..20c90c7 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -15,11 +15,23 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { Future> getAllGames() async { final query = select(gameTable); final result = await query.get(); - return result - .map( - (row) => Game(id: row.id, name: row.name, createdAt: row.createdAt), - ) - .toList(); + + return Future.wait( + result.map((row) async { + final group = await db.groupGameDao.getGroupByGameId(gameId: row.id); + final player = await db.playerGameDao.getPlayersByGameId( + gameId: row.id, + ); + return Game( + id: row.id, + name: row.name, + group: group, + players: player, + createdAt: row.createdAt, + winner: row.winnerId, + ); + }), + ); } /// Retrieves a [Game] by its [gameId]. diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index d3b30ca..66ebdec 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -23,10 +23,15 @@ class GroupGameDao extends DatabaseAccessor } /// Retrieves the [Group] associated with the given [gameId]. - Future getGroupByGameId({required String gameId}) async { + /// Returns `null` if no group is found. + Future getGroupByGameId({required String gameId}) async { final result = await (select( groupGameTable, - )..where((g) => g.gameId.equals(gameId))).getSingle(); + )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); + + if (result == null) { + return null; + } final group = await db.groupDao.getGroupById(groupId: result.groupId); return group; diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 8f367f8..05c9e10 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -23,19 +23,19 @@ class PlayerGameDao extends DatabaseAccessor } /// Retrieves a list of [Player]s associated with the given [gameId]. - /// Returns an empty list if no players are found. - Future> getPlayersByGameId({required String gameId}) async { + /// Returns null if no players are found. + Future?> getPlayersByGameId({required String gameId}) async { final result = await (select( playerGameTable, )..where((p) => p.gameId.equals(gameId))).get(); - if (result.isEmpty) return []; + if (result.isEmpty) return null; final futures = result.map( (row) => db.playerDao.getPlayerById(playerId: row.playerId), ); final players = await Future.wait(futures); - return players.whereType().toList(); + return players; } /// Associates a player with a game by inserting a record into the From 72067863c2c7d04f781159411e4b094726a08a72 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:14:44 +0100 Subject: [PATCH 28/53] Updated insert modes --- lib/data/dao/group_game_dao.dart | 2 +- lib/data/dao/player_game_dao.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 66ebdec..8a55b91 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -42,6 +42,6 @@ class GroupGameDao extends DatabaseAccessor Future addGroupToGame(String gameId, String groupId) async { await into( groupGameTable, - ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId)); + ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace); } } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 05c9e10..92937cb 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -46,6 +46,7 @@ class PlayerGameDao extends DatabaseAccessor }) async { await into(playerGameTable).insert( PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, ); } } From 89b3f1ff69cc5e5e8fecad8e5536f602f1edb03a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:40:46 +0100 Subject: [PATCH 29/53] Overhauled tests --- test/db_tests/game_test.dart | 161 ++++++++++++++++++++++++++++++--- test/db_tests/group_test.dart | 64 +++++++++---- test/db_tests/player_test.dart | 81 ++++++++++++----- 3 files changed, 251 insertions(+), 55 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d726425..0b68648 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -15,7 +15,9 @@ void main() { late Player player4; late Player player5; late Group testgroup; + late Group testgroup2; late Game testgame; + late Game testgame2; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -38,19 +40,28 @@ void main() { name: 'Test Group', members: [player1, player2, player3], ); + testgroup2 = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); testgame = Game( name: 'Test Game', group: testgroup, players: [player4, player5], ); + testgame2 = Game( + name: 'Second Test Game', + group: testgroup2, + players: [player1, player2, player3], + ); }); }); tearDown(() async { await database.close(); }); - group('game tests', () { - test('game is added correctly', () async { + group('Game Tests', () { + test('Adding and fetching single game works correclty', () async { await database.gameDao.addGame(game: testgame); final result = await database.gameDao.getGameById(gameId: testgame.id); @@ -83,7 +94,116 @@ void main() { } }); - test('game is deleted correctly', () async { + // TODO: Use upcoming addGames() method + test('Adding and fetching multiple games works correclty', () async { + await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame2); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 2); + + final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame.id); + // game checks + expect(fetchedGame1.id, testgame.id); + expect(fetchedGame1.name, testgame.name); + expect(fetchedGame1.createdAt, testgame.createdAt); + expect(fetchedGame1.winner, testgame.winner); + + // group checks + expect(fetchedGame1.group!.id, testgame.group!.id); + expect(fetchedGame1.group!.name, testgame.group!.name); + expect(fetchedGame1.group!.createdAt, testgame.group!.createdAt); + // group members checks + expect( + fetchedGame1.group!.members.length, + testgame.group!.members.length, + ); + for (int i = 0; i < testgame.group!.members.length; i++) { + expect( + fetchedGame1.group!.members[i].id, + testgame.group!.members[i].id, + ); + expect( + fetchedGame1.group!.members[i].name, + testgame.group!.members[i].name, + ); + expect( + fetchedGame1.group!.members[i].createdAt, + testgame.group!.members[i].createdAt, + ); + } + + // players checks + for (int i = 0; i < fetchedGame1.players!.length; i++) { + expect(fetchedGame1.players![i].id, testgame.players![i].id); + expect(fetchedGame1.players![i].name, testgame.players![i].name); + expect( + fetchedGame1.players![i].createdAt, + testgame.players![i].createdAt, + ); + } + + final fetchedGame2 = allGames.firstWhere((g) => g.id == testgame2.id); + // game checks + expect(fetchedGame2.id, testgame2.id); + expect(fetchedGame2.name, testgame2.name); + expect(fetchedGame2.createdAt, testgame2.createdAt); + expect(fetchedGame2.winner, testgame2.winner); + + // group checks + expect(fetchedGame2.group!.id, testgame2.group!.id); + expect(fetchedGame2.group!.name, testgame2.group!.name); + expect(fetchedGame2.group!.createdAt, testgame2.group!.createdAt); + // group members checks + expect( + fetchedGame2.group!.members.length, + testgame2.group!.members.length, + ); + for (int i = 0; i < testgame2.group!.members.length; i++) { + expect( + fetchedGame2.group!.members[i].id, + testgame2.group!.members[i].id, + ); + expect( + fetchedGame2.group!.members[i].name, + testgame2.group!.members[i].name, + ); + expect( + fetchedGame2.group!.members[i].createdAt, + testgame2.group!.members[i].createdAt, + ); + } + + // players checks + for (int i = 0; i < fetchedGame2.players!.length; i++) { + expect(fetchedGame2.players![i].id, testgame2.players![i].id); + expect(fetchedGame2.players![i].name, testgame2.players![i].name); + expect( + fetchedGame2.players![i].createdAt, + testgame2.players![i].createdAt, + ); + } + }); + + test('Adding the same game twice does not create duplicates', () async { + await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame); + + final gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); + }); + + test('Game existence check works correctly', () async { + var gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, false); + + await database.gameDao.addGame(game: testgame); + + gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, true); + }); + + test('Deleting a game works correclty', () async { await database.gameDao.addGame(game: testgame); final gameDeleted = await database.gameDao.deleteGame( @@ -95,22 +215,35 @@ void main() { expect(gameExists, false); }); - test('get game count works correctly', () async { - final initialCount = await database.gameDao.getGameCount(); - expect(initialCount, 0); + test('Getting the game count works correctly', () async { + var gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 0); await database.gameDao.addGame(game: testgame); - final gameAdded = await database.gameDao.getGameCount(); - expect(gameAdded, 1); + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); - final gameRemoved = await database.gameDao.deleteGame( - gameId: testgame.id, - ); - expect(gameRemoved, true); + await database.gameDao.addGame(game: testgame2); - final finalCount = await database.gameDao.getGameCount(); - expect(finalCount, 0); + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 2); + + await database.gameDao.deleteGame(gameId: testgame.id); + + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); + + await database.gameDao.deleteGame(gameId: testgame2.id); + + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 0); }); + + // TODO: Implement + test('Adding a player to a game works correclty', () async {}); + + // TODO: Implement + test('Adding a group to a game works correclty', () async {}); }); } diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index a076ab0..2572f52 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -45,8 +45,31 @@ void main() { tearDown(() async { await database.close(); }); - group('group tests', () { - test('all groups get fetched correctly', () async { + group('Group Tests', () { + test('Adding and fetching a single group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + + expect(fetchedGroup.id, testgroup.id); + expect(fetchedGroup.name, testgroup.name); + expect(fetchedGroup.createdAt, testgroup.createdAt); + + expect(fetchedGroup.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(fetchedGroup.members[i].id, testgroup.members[i].id); + expect(fetchedGroup.members[i].name, testgroup.members[i].name); + expect( + fetchedGroup.members[i].createdAt, + testgroup.members[i].createdAt, + ); + } + }); + + // TODO: Use upcoming addGroups() method + test('Adding and fetching a single group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); @@ -66,26 +89,27 @@ void main() { expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); }); - test('group and group members gets added correctly', () async { + test('Adding the same group twice does not create duplicates', () async { + await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - - expect(result.id, testgroup.id); - expect(result.name, testgroup.name); - expect(result.createdAt, testgroup.createdAt); - - expect(result.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.members[i].id, testgroup.members[i].id); - expect(result.members[i].name, testgroup.members[i].name); - expect(result.members[i].createdAt, testgroup.members[i].createdAt); - } + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); }); - test('group gets deleted correctly', () async { + test('Group existence check works correctly', () async { + var groupExists = await database.groupDao.groupExists( + groupId: testgroup.id, + ); + expect(groupExists, false); + + await database.groupDao.addGroup(group: testgroup); + + groupExists = await database.groupDao.groupExists(groupId: testgroup.id); + expect(groupExists, true); + }); + + test('Deleting a group works correclty', () async { await database.groupDao.addGroup(group: testgroup); final groupDeleted = await database.groupDao.deleteGroup( @@ -99,7 +123,7 @@ void main() { expect(groupExists, false); }); - test('group name gets updated correcly ', () async { + test('Updating a group name works correcly', () async { await database.groupDao.addGroup(group: testgroup); const newGroupName = 'new group name'; @@ -167,7 +191,7 @@ void main() { expect(playerExists, false); }); - test('get group count works correctly', () async { + test('Getting the group count works correctly', () async { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index fa65f67..d894836 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -31,7 +31,7 @@ void main() { }); group('player tests', () { - test('all players get fetched correctly', () async { + test('Adding and fetching single player works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); @@ -51,18 +51,50 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('players get inserted correcly ', () async { + // TODO: Use upcoming addPlayers() method + test('Adding and fetching multiple players works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); - final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, - ); + await database.playerDao.addPlayer(player: testPlayer2); - expect(result.id, testPlayer.id); - expect(result.name, testPlayer.name); - expect(result.createdAt, testPlayer.createdAt); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer.id, + ); + expect(fetchedPlayer1.name, testPlayer.name); + expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('players get deleted correcly ', () async { + test('Adding the same player twice does not create duplicates', () async { + await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 1); + }); + + test('Player existence check works correctly', () async { + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, false); + + await database.playerDao.addPlayer(player: testPlayer); + + playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, true); + }); + + test('Deleting a player works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); final playerDeleted = await database.playerDao.deletePlayer( playerId: testPlayer.id, @@ -75,7 +107,7 @@ void main() { expect(playerExists, false); }); - test('player name gets updated correcly ', () async { + test('Updating a player name works correcly', () async { await database.playerDao.addPlayer(player: testPlayer); const newPlayerName = 'new player name'; @@ -91,22 +123,29 @@ void main() { expect(result.name, newPlayerName); }); - test('get player count works correctly', () async { - final initialCount = await database.playerDao.getPlayerCount(); - expect(initialCount, 0); + test('Getting the player count works correctly', () async { + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); await database.playerDao.addPlayer(player: testPlayer); - final playerAdded = await database.playerDao.getPlayerCount(); - expect(playerAdded, 1); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); - final playerRemoved = await database.playerDao.deletePlayer( - playerId: testPlayer.id, - ); - expect(playerRemoved, true); + await database.playerDao.addPlayer(player: testPlayer2); - final finalCount = await database.playerDao.getPlayerCount(); - expect(finalCount, 0); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 2); + + await database.playerDao.deletePlayer(playerId: testPlayer.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.deletePlayer(playerId: testPlayer2.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); }); }); } From 8e63a01705868772df469f43ed2afc6f788bb482 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:49:57 +0100 Subject: [PATCH 30/53] Added methods for multiple inserts to tests --- test/db_tests/game_test.dart | 4 +--- test/db_tests/group_test.dart | 4 +--- test/db_tests/player_test.dart | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0b68648..477965f 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -94,10 +94,8 @@ void main() { } }); - // TODO: Use upcoming addGames() method test('Adding and fetching multiple games works correclty', () async { - await database.gameDao.addGame(game: testgame); - await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGames(games: [testgame, testgame2]); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 2); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2572f52..62416fd 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -68,10 +68,8 @@ void main() { } }); - // TODO: Use upcoming addGroups() method test('Adding and fetching a single group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); + await database.groupDao.addGroups(groups: [testgroup, testgroup2]); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index d894836..bc95533 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -51,10 +51,8 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - // TODO: Use upcoming addPlayers() method test('Adding and fetching multiple players works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer2); + await database.playerDao.addPlayers(players: [testPlayer, testPlayer2]); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 2); From 31589855f28e6eb8c6f2d402f389df8f9bfcf28b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:06:09 +0100 Subject: [PATCH 31/53] Added methods of todos --- lib/data/dao/group_game_dao.dart | 57 +++++++++++++++++++-------- lib/data/dao/player_game_dao.dart | 64 +++++++++++++++++++++++-------- test/db_tests/game_test.dart | 59 ++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 37 deletions(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 8a55b91..633bb1c 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -10,16 +10,13 @@ class GroupGameDao extends DatabaseAccessor with _$GroupGameDaoMixin { GroupGameDao(super.db); - /// Checks if there is a group associated with the given [gameId]. - /// Returns `true` if there is a group, otherwise `false`. - Future hasGameGroup({required String gameId}) async { - final count = - await (selectOnly(groupGameTable) - ..where(groupGameTable.gameId.equals(gameId)) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; + /// Associates a group with a game by inserting a record into the + /// [GroupGameTable]. + Future addGroupToGame(String gameId, String groupId) async { + await into(groupGameTable).insert( + GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), + mode: InsertMode.insertOrReplace, + ); } /// Retrieves the [Group] associated with the given [gameId]. @@ -37,11 +34,39 @@ class GroupGameDao extends DatabaseAccessor return group; } - /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. - Future addGroupToGame(String gameId, String groupId) async { - await into( - groupGameTable, - ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace); + /// Checks if there is a group associated with the given [gameId]. + /// Returns `true` if there is a group, otherwise `false`. + Future gameHasGroup({required String gameId}) async { + final count = + await (selectOnly(groupGameTable) + ..where(groupGameTable.gameId.equals(gameId)) + ..addColumns([groupGameTable.groupId.count()])) + .map((row) => row.read(groupGameTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific group is associated with a specific game. + /// Returns `true` if the group is in the game, otherwise `false`. + Future isGroupInGame({ + required String gameId, + required String groupId, + }) async { + final count = + await (selectOnly(groupGameTable) + ..where( + groupGameTable.gameId.equals(gameId) & + groupGameTable.groupId.equals(groupId), + ) + ..addColumns([groupGameTable.groupId.count()])) + .map((row) => row.read(groupGameTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + Future removeGroupFromGame({required String gameId}) async { + final query = delete(groupGameTable)..where((g) => g.gameId.equals(gameId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 92937cb..87fd1d0 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -10,16 +10,16 @@ class PlayerGameDao extends DatabaseAccessor with _$PlayerGameDaoMixin { PlayerGameDao(super.db); - /// Checks if there are any players associated with the given [gameId]. - /// Returns `true` if there are players, otherwise `false`. - Future gameHasPlayers({required String gameId}) async { - final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) - .getSingle(); - return (count ?? 0) > 0; + /// Associates a player with a game by inserting a record into the + /// [PlayerGameTable]. + Future addPlayerToGame({ + required String gameId, + required String playerId, + }) async { + await into(playerGameTable).insert( + PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, + ); } /// Retrieves a list of [Player]s associated with the given [gameId]. @@ -38,15 +38,45 @@ class PlayerGameDao extends DatabaseAccessor return players; } - /// Associates a player with a game by inserting a record into the - /// [PlayerGameTable]. - Future addPlayerToGame({ + /// Checks if there are any players associated with the given [gameId]. + /// Returns `true` if there are players, otherwise `false`. + Future gameHasPlayers({required String gameId}) async { + final count = + await (selectOnly(playerGameTable) + ..where(playerGameTable.gameId.equals(gameId)) + ..addColumns([playerGameTable.playerId.count()])) + .map((row) => row.read(playerGameTable.playerId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific player is associated with a specific game. + /// Returns `true` if the player is in the game, otherwise `false`. + Future isPlayerInGame({ required String gameId, required String playerId, }) async { - await into(playerGameTable).insert( - PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), - mode: InsertMode.insertOrReplace, - ); + final count = + await (selectOnly(playerGameTable) + ..where(playerGameTable.gameId.equals(gameId)) + ..where(playerGameTable.playerId.equals(playerId)) + ..addColumns([playerGameTable.playerId.count()])) + .map((row) => row.read(playerGameTable.playerId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Removes the association of a player with a game by deleting the record + /// from the [PlayerGameTable]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removePlayerFromGame({ + required String gameId, + required String playerId, + }) async { + final query = delete(playerGameTable) + ..where((pg) => pg.gameId.equals(gameId)) + ..where((pg) => pg.playerId.equals(playerId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0b68648..05cf9d0 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -14,10 +14,12 @@ void main() { late Player player3; late Player player4; late Player player5; + late Player player6; late Group testgroup; late Group testgroup2; late Game testgame; late Game testgame2; + late Game testgame3; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -36,6 +38,7 @@ void main() { player3 = Player(name: 'Charlie'); player4 = Player(name: 'Diana'); player5 = Player(name: 'Eve'); + player6 = Player(name: 'Frank'); testgroup = Group( name: 'Test Group', members: [player1, player2, player3], @@ -54,6 +57,7 @@ void main() { group: testgroup2, players: [player1, player2, player3], ); + testgame3 = Game(name: 'Third Test Game', players: [player4, player5]); }); }); tearDown(() async { @@ -240,10 +244,57 @@ void main() { expect(gameCount, 0); }); - // TODO: Implement - test('Adding a player to a game works correclty', () async {}); + test( + 'Adding and removing player to and from a game works correclty', + () async { + database.gameDao.addGame(game: testgame); + database.playerDao.addPlayer(player: player6); + database.playerGameDao.addPlayerToGame( + gameId: testgame.id, + playerId: player6.id, + ); - // TODO: Implement - test('Adding a group to a game works correclty', () async {}); + var playerInGame = await database.playerGameDao.isPlayerInGame( + gameId: testgame.id, + playerId: player6.id, + ); + + expect(playerInGame, true); + + final playerRemoved = await database.playerGameDao.removePlayerFromGame( + gameId: testgame.id, + playerId: player6.id, + ); + + expect(playerRemoved, true); + + playerInGame = await database.playerGameDao.isPlayerInGame( + gameId: testgame.id, + playerId: player6.id, + ); + expect(playerInGame, false); + }, + ); + + test( + 'Adding and removing a group to and from a game works correclty', + () async { + database.gameDao.addGame(game: testgame3); + database.groupDao.addGroup(group: testgroup); + database.groupGameDao.addGroupToGame(testgame3.id, testgroup.id); + var gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: testgame3.id, + ); + expect(gameHasGroup, true); + final groupRemoved = await database.groupGameDao.removeGroupFromGame( + gameId: testgame3.id, + ); + expect(groupRemoved, true); + gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: testgame3.id, + ); + expect(gameHasGroup, false); + }, + ); }); } From 6055eb63a85ffb43143a63f4e9083aff1a718c1d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:07:34 +0100 Subject: [PATCH 32/53] Refactoring --- lib/data/dao/game_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 20c90c7..2024eca 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -44,7 +44,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); } Group? group; - if (await db.groupGameDao.hasGameGroup(gameId: gameId)) { + if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { group = await db.groupGameDao.getGroupByGameId(gameId: gameId); } From b21ca5467232c9e35b341ea5c374353c8ee77210 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:10:29 +0100 Subject: [PATCH 33/53] Refactoring --- lib/data/dao/game_dao.dart | 10 +++---- lib/data/dao/group_dao.dart | 4 +-- lib/data/dao/group_game_dao.dart | 2 +- lib/data/dao/player_game_dao.dart | 2 +- lib/data/dao/player_group_dao.dart | 48 +++++++++++++++--------------- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 2024eca..f29d553 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -18,10 +18,8 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return Future.wait( result.map((row) async { - final group = await db.groupGameDao.getGroupByGameId(gameId: row.id); - final player = await db.playerGameDao.getPlayersByGameId( - gameId: row.id, - ); + final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); + final player = await db.playerGameDao.getPlayersOfGame(gameId: row.id); return Game( id: row.id, name: row.name, @@ -41,11 +39,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { List? players; if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) { - players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); + players = await db.playerGameDao.getPlayersOfGame(gameId: gameId); } Group? group; if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { - group = await db.groupGameDao.getGroupByGameId(gameId: gameId); + group = await db.groupGameDao.getGroupOfGame(gameId: gameId); } return Game( diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 3378948..9b16801 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -16,7 +16,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final result = await query.get(); return Future.wait( result.map((groupData) async { - final members = await db.playerGroupDao.getPlayersOfGroupById( + final members = await db.playerGroupDao.getPlayersOfGroup( groupId: groupData.id, ); return Group( @@ -34,7 +34,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final query = select(groupTable)..where((g) => g.id.equals(groupId)); final result = await query.getSingle(); - List members = await db.playerGroupDao.getPlayersOfGroupById( + List members = await db.playerGroupDao.getPlayersOfGroup( groupId: groupId, ); diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 633bb1c..8081c6f 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -21,7 +21,7 @@ class GroupGameDao extends DatabaseAccessor /// Retrieves the [Group] associated with the given [gameId]. /// Returns `null` if no group is found. - Future getGroupByGameId({required String gameId}) async { + Future getGroupOfGame({required String gameId}) async { final result = await (select( groupGameTable, )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 87fd1d0..ef15a80 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -24,7 +24,7 @@ class PlayerGameDao extends DatabaseAccessor /// Retrieves a list of [Player]s associated with the given [gameId]. /// Returns null if no players are found. - Future?> getPlayersByGameId({required String gameId}) async { + Future?> getPlayersOfGame({required String gameId}) async { final result = await (select( playerGameTable, )..where((p) => p.gameId.equals(gameId))).get(); diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index fe067ae..5484bf7 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -10,8 +10,31 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); + /// Adds a [player] to a group with the given [groupId]. + /// If the player is already in the group, no action is taken. + /// If the player does not exist in the player table, they are added. + /// Returns `true` if the player was added, otherwise `false`. + Future addPlayerToGroup({ + required Player player, + required String groupId, + }) async { + if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { + return false; + } + + if (await db.playerDao.playerExists(playerId: player.id) == false) { + db.playerDao.addPlayer(player: player); + } + + await into(playerGroupTable).insert( + PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + ); + + return true; + } + /// Retrieves all players belonging to a specific group by [groupId]. - Future> getPlayersOfGroupById({required String groupId}) async { + Future> getPlayersOfGroup({required String groupId}) async { final query = select(playerGroupTable) ..where((pG) => pG.groupId.equals(groupId)); final result = await query.get(); @@ -38,29 +61,6 @@ class PlayerGroupDao extends DatabaseAccessor return rowsAffected > 0; } - /// Adds a [player] to a group with the given [groupId]. - /// If the player is already in the group, no action is taken. - /// If the player does not exist in the player table, they are added. - /// Returns `true` if the player was added, otherwise `false`. - Future addPlayerToGroup({ - required Player player, - required String groupId, - }) async { - if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { - return false; - } - - if (await db.playerDao.playerExists(playerId: player.id) == false) { - db.playerDao.addPlayer(player: player); - } - - await into(playerGroupTable).insert( - PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), - ); - - return true; - } - /// Checks if a player with [playerId] is in the group with [groupId]. /// Returns `true` if the player is in the group, otherwise `false`. Future isPlayerInGroup({ From 961c6bb679b77492dcb60cb7a61e83c132fba855 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 01:05:35 +0100 Subject: [PATCH 34/53] Refactored tests in own files --- lib/data/dao/group_game_dao.dart | 11 +- test/db_tests/game_test.dart | 158 ++++++++++----------------- test/db_tests/group_game_test.dart | 110 +++++++++++++++++++ test/db_tests/group_test.dart | 55 +--------- test/db_tests/player_game_test.dart | 126 +++++++++++++++++++++ test/db_tests/player_group_test.dart | 90 +++++++++++++++ test/db_tests/player_test.dart | 3 +- 7 files changed, 394 insertions(+), 159 deletions(-) create mode 100644 test/db_tests/group_game_test.dart create mode 100644 test/db_tests/player_game_test.dart create mode 100644 test/db_tests/player_group_test.dart diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 8081c6f..f3ddcc7 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -64,8 +64,15 @@ class GroupGameDao extends DatabaseAccessor return (count ?? 0) > 0; } - Future removeGroupFromGame({required String gameId}) async { - final query = delete(groupGameTable)..where((g) => g.gameId.equals(gameId)); + /// Removes the association of a group from a game based on [groupId] and + /// [gameId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeGroupFromGame({ + required String gameId, + required String groupId, + }) async { + final query = delete(groupGameTable) + ..where((g) => g.gameId.equals(gameId) & g.groupId.equals(groupId)); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 05cf9d0..d5b0856 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -14,12 +14,12 @@ void main() { late Player player3; late Player player4; late Player player5; - late Player player6; late Group testgroup; late Group testgroup2; - late Game testgame; + late Game testgame1; late Game testgame2; - late Game testgame3; + late Game testgameWithPlayer; + late Game testgameWithGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -38,16 +38,12 @@ void main() { player3 = Player(name: 'Charlie'); player4 = Player(name: 'Diana'); player5 = Player(name: 'Eve'); - player6 = Player(name: 'Frank'); testgroup = Group( - name: 'Test Group', + name: 'Test Group 2', members: [player1, player2, player3], ); - testgroup2 = Group( - name: 'Test Group', - members: [player1, player2, player3], - ); - testgame = Game( + testgroup2 = Group(name: 'Test Group 2', members: [player4, player5]); + testgame1 = Game( name: 'Test Game', group: testgroup, players: [player4, player5], @@ -57,7 +53,11 @@ void main() { group: testgroup2, players: [player1, player2, player3], ); - testgame3 = Game(name: 'Third Test Game', players: [player4, player5]); + testgameWithPlayer = Game( + name: 'Second Test Game', + players: [player1, player2, player3], + ); + testgameWithGroup = Game(name: 'Second Test Game', group: testgroup2); }); }); tearDown(() async { @@ -66,14 +66,14 @@ void main() { group('Game Tests', () { test('Adding and fetching single game works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); - final result = await database.gameDao.getGameById(gameId: testgame.id); + final result = await database.gameDao.getGameById(gameId: testgame1.id); - expect(result.id, testgame.id); - expect(result.name, testgame.name); - expect(result.winner, testgame.winner); - expect(result.createdAt, testgame.createdAt); + expect(result.id, testgame1.id); + expect(result.name, testgame1.name); + expect(result.winner, testgame1.winner); + expect(result.createdAt, testgame1.createdAt); if (result.group != null) { expect(result.group!.members.length, testgroup.members.length); @@ -86,12 +86,12 @@ void main() { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testgame.players!.length); + expect(result.players!.length, testgame1.players!.length); - for (int i = 0; i < testgame.players!.length; i++) { - expect(result.players![i].id, testgame.players![i].id); - expect(result.players![i].name, testgame.players![i].name); - expect(result.players![i].createdAt, testgame.players![i].createdAt); + for (int i = 0; i < testgame1.players!.length; i++) { + expect(result.players![i].id, testgame1.players![i].id); + expect(result.players![i].name, testgame1.players![i].name); + expect(result.players![i].createdAt, testgame1.players![i].createdAt); } } else { fail('Players is null'); @@ -99,51 +99,54 @@ void main() { }); // TODO: Use upcoming addGames() method + // TODO: Iterate through games test('Adding and fetching multiple games works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGame(game: testgameWithGroup); + await database.gameDao.addGame(game: testgameWithPlayer); final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 2); + expect(allGames.length, 4); - final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame.id); + final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame1.id); // game checks - expect(fetchedGame1.id, testgame.id); - expect(fetchedGame1.name, testgame.name); - expect(fetchedGame1.createdAt, testgame.createdAt); - expect(fetchedGame1.winner, testgame.winner); + expect(fetchedGame1.id, testgame1.id); + expect(fetchedGame1.name, testgame1.name); + expect(fetchedGame1.createdAt, testgame1.createdAt); + expect(fetchedGame1.winner, testgame1.winner); // group checks - expect(fetchedGame1.group!.id, testgame.group!.id); - expect(fetchedGame1.group!.name, testgame.group!.name); - expect(fetchedGame1.group!.createdAt, testgame.group!.createdAt); + expect(fetchedGame1.group!.id, testgame1.group!.id); + expect(fetchedGame1.group!.name, testgame1.group!.name); + expect(fetchedGame1.group!.createdAt, testgame1.group!.createdAt); // group members checks expect( fetchedGame1.group!.members.length, - testgame.group!.members.length, + testgame1.group!.members.length, ); - for (int i = 0; i < testgame.group!.members.length; i++) { + for (int i = 0; i < testgame1.group!.members.length; i++) { expect( fetchedGame1.group!.members[i].id, - testgame.group!.members[i].id, + testgame1.group!.members[i].id, ); expect( fetchedGame1.group!.members[i].name, - testgame.group!.members[i].name, + testgame1.group!.members[i].name, ); expect( fetchedGame1.group!.members[i].createdAt, - testgame.group!.members[i].createdAt, + testgame1.group!.members[i].createdAt, ); } // players checks for (int i = 0; i < fetchedGame1.players!.length; i++) { - expect(fetchedGame1.players![i].id, testgame.players![i].id); - expect(fetchedGame1.players![i].name, testgame.players![i].name); + expect(fetchedGame1.players![i].id, testgame1.players![i].id); + expect(fetchedGame1.players![i].name, testgame1.players![i].name); expect( fetchedGame1.players![i].createdAt, - testgame.players![i].createdAt, + testgame1.players![i].createdAt, ); } @@ -190,32 +193,34 @@ void main() { }); test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testgame); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testgame1); final gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); }); test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testgame.id); + var gameExists = await database.gameDao.gameExists(gameId: testgame1.id); expect(gameExists, false); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); - gameExists = await database.gameDao.gameExists(gameId: testgame.id); + gameExists = await database.gameDao.gameExists(gameId: testgame1.id); expect(gameExists, true); }); test('Deleting a game works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); final gameDeleted = await database.gameDao.deleteGame( - gameId: testgame.id, + gameId: testgame1.id, ); expect(gameDeleted, true); - final gameExists = await database.gameDao.gameExists(gameId: testgame.id); + final gameExists = await database.gameDao.gameExists( + gameId: testgame1.id, + ); expect(gameExists, false); }); @@ -223,7 +228,7 @@ void main() { var gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); @@ -233,7 +238,7 @@ void main() { gameCount = await database.gameDao.getGameCount(); expect(gameCount, 2); - await database.gameDao.deleteGame(gameId: testgame.id); + await database.gameDao.deleteGame(gameId: testgame1.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); @@ -243,58 +248,5 @@ void main() { gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); }); - - test( - 'Adding and removing player to and from a game works correclty', - () async { - database.gameDao.addGame(game: testgame); - database.playerDao.addPlayer(player: player6); - database.playerGameDao.addPlayerToGame( - gameId: testgame.id, - playerId: player6.id, - ); - - var playerInGame = await database.playerGameDao.isPlayerInGame( - gameId: testgame.id, - playerId: player6.id, - ); - - expect(playerInGame, true); - - final playerRemoved = await database.playerGameDao.removePlayerFromGame( - gameId: testgame.id, - playerId: player6.id, - ); - - expect(playerRemoved, true); - - playerInGame = await database.playerGameDao.isPlayerInGame( - gameId: testgame.id, - playerId: player6.id, - ); - expect(playerInGame, false); - }, - ); - - test( - 'Adding and removing a group to and from a game works correclty', - () async { - database.gameDao.addGame(game: testgame3); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame(testgame3.id, testgroup.id); - var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgame3.id, - ); - expect(gameHasGroup, true); - final groupRemoved = await database.groupGameDao.removeGroupFromGame( - gameId: testgame3.id, - ); - expect(groupRemoved, true); - gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgame3.id, - ); - expect(gameHasGroup, false); - }, - ); }); } diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart new file mode 100644 index 0000000..2e208d9 --- /dev/null +++ b/test/db_tests/group_game_test.dart @@ -0,0 +1,110 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Player player5; + late Group testgroup; + late Game gameWithGroup; + late Game gameWithPlayers; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + gameWithPlayers = Game( + name: 'Game with Players', + players: [player4, player5], + ); + gameWithGroup = Game(name: 'Game with Group', group: testgroup); + }); + }); + tearDown(() async { + await database.close(); + }); + group('Group-Game Tests', () { + test('Game has group works correctly', () async { + database.gameDao.addGame(game: gameWithPlayers); + database.groupDao.addGroup(group: testgroup); + + var gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: gameWithPlayers.id, + ); + + expect(gameHasGroup, false); + + database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + + gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: gameWithPlayers.id, + ); + + expect(gameHasGroup, true); + }); + + test('Adding a group to a game works correctly', () async { + database.gameDao.addGame(game: gameWithPlayers); + database.groupDao.addGroup(group: testgroup); + database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + + var groupAdded = await database.groupGameDao.isGroupInGame( + gameId: gameWithPlayers.id, + groupId: testgroup.id, + ); + expect(groupAdded, true); + + groupAdded = await database.groupGameDao.isGroupInGame( + gameId: gameWithPlayers.id, + groupId: '', + ); + expect(groupAdded, false); + }); + + test('Removing group from game works correctly', () async { + await database.gameDao.addGame(game: gameWithGroup); + + final groupToRemove = gameWithGroup.group!; + + final removed = await database.groupGameDao.removeGroupFromGame( + groupId: groupToRemove.id, + gameId: gameWithGroup.id, + ); + expect(removed, true); + + final result = await database.gameDao.getGameById( + gameId: gameWithGroup.id, + ); + expect(result.group, null); + }); + + // TODO: test getGroupOfGame() + test('Retrieving group of a game works correctly', () async {}); + }); +} diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2572f52..b18942e 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -68,7 +68,6 @@ void main() { } }); - // TODO: Use upcoming addGroups() method test('Adding and fetching a single group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); @@ -89,6 +88,8 @@ void main() { expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); }); + // TODO: Use upcoming addGroups() method + // TODO: An Test in Game Tests orientieren test('Adding the same group twice does not create duplicates', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); @@ -139,58 +140,6 @@ void main() { expect(result.name, newGroupName); }); - test('Adding player to group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - - await database.playerGroupDao.addPlayerToGroup( - player: player4, - groupId: testgroup.id, - ); - - final playerAdded = await database.playerGroupDao.isPlayerInGroup( - playerId: player4.id, - groupId: testgroup.id, - ); - - expect(playerAdded, true); - - final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( - playerId: '', - groupId: testgroup.id, - ); - - expect(playerNotAdded, true); - - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length + 1); - - final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); - expect(addedPlayer.name, player4.name); - expect(addedPlayer.createdAt, player4.createdAt); - }); - - test('Removing player from group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - - final playerToRemove = testgroup.members[0]; - - final removed = await database.playerGroupDao.removePlayerFromGroup( - playerId: playerToRemove.id, - groupId: testgroup.id, - ); - expect(removed, true); - - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length - 1); - - final playerExists = result.members.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); - test('Getting the group count works correctly', () async { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart new file mode 100644 index 0000000..7df18a1 --- /dev/null +++ b/test/db_tests/player_game_test.dart @@ -0,0 +1,126 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Player player5; + late Player player6; + late Group testgroup; + late Game testgameWithGroup; + late Game testgameWithPlayers; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); + player6 = Player(name: 'Frank'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + testgameWithGroup = Game(name: 'Test Game', group: testgroup); + testgameWithPlayers = Game( + name: 'Test Game with Players', + players: [player4, player5, player6], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Game Tests', () { + test('Game has player works correctly', () async { + database.gameDao.addGame(game: testgameWithGroup); + database.playerDao.addPlayer(player: player1); + + var gameHasPlayers = await database.playerGameDao.gameHasPlayers( + gameId: testgameWithGroup.id, + ); + + expect(gameHasPlayers, false); + + database.playerGameDao.addPlayerToGame( + gameId: testgameWithGroup.id, + playerId: player1.id, + ); + + gameHasPlayers = await database.playerGameDao.gameHasPlayers( + gameId: testgameWithGroup.id, + ); + + expect(gameHasPlayers, true); + }); + + test('Adding a player to a game works correctly', () async { + database.gameDao.addGame(game: testgameWithGroup); + database.playerDao.addPlayer(player: player5); + database.playerGameDao.addPlayerToGame( + gameId: testgameWithGroup.id, + playerId: player5.id, + ); + + var playerAdded = await database.playerGameDao.isPlayerInGame( + gameId: testgameWithGroup.id, + playerId: player5.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerGameDao.isPlayerInGame( + gameId: testgameWithGroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + test('Removing player from game works correctly', () async { + await database.gameDao.addGame(game: testgameWithPlayers); + + final playerToRemove = testgameWithPlayers.players![0]; + + final removed = await database.playerGameDao.removePlayerFromGame( + playerId: playerToRemove.id, + gameId: testgameWithPlayers.id, + ); + expect(removed, true); + + final result = await database.gameDao.getGameById( + gameId: testgameWithGroup.id, + ); + expect(result.players!.length, testgameWithGroup.players!.length - 1); + + final playerExists = result.players!.any( + (p) => p.id == playerToRemove.id, + ); + expect(playerExists, false); + }); + + //TODO: test getPlayersOfGame() + test('Retrieving players of a game works correctly', () async {}); + }); +} diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart new file mode 100644 index 0000000..74d7658 --- /dev/null +++ b/test/db_tests/player_group_test.dart @@ -0,0 +1,90 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Group Tests', () { + test('Adding a player to a group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + await database.playerDao.addPlayer(player: player4); + await database.playerGroupDao.addPlayerToGroup( + groupId: testgroup.id, + player: player4, + ); + + var playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testgroup.id, + playerId: player4.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testgroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final playerToRemove = testgroup.members[0]; + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testgroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); + + //TODO: test getPlayersOfGroup() + test('Retrieving players of a group works correctly', () async {}); + }); +} diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index d894836..9430433 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -23,7 +23,7 @@ void main() { withClock(fakeClock, () { testPlayer = Player(name: 'Test Player'); - testPlayer2 = Player(name: 'Second Group'); + testPlayer2 = Player(name: 'Second Player'); }); }); tearDown(() async { @@ -52,6 +52,7 @@ void main() { }); // TODO: Use upcoming addPlayers() method + // TODO: An Tests in Game orientieren test('Adding and fetching multiple players works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); From fe9239ee02138fdf0751c069a928b1b41e61700d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:45:48 +0100 Subject: [PATCH 35/53] Added missing test --- test/db_tests/group_game_test.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 2e208d9..6621d9a 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -104,7 +104,25 @@ void main() { expect(result.group, null); }); - // TODO: test getGroupOfGame() - test('Retrieving group of a game works correctly', () async {}); + test('Retrieving group of a game works correctly', () async { + await database.gameDao.addGame(game: gameWithGroup); + final group = await database.groupGameDao.getGroupOfGame( + gameId: gameWithGroup.id, + ); + + if (group == null) { + fail('Group should not be null'); + } + + expect(group.id, testgroup.id); + expect(group.name, testgroup.name); + expect(group.createdAt, testgroup.createdAt); + expect(group.members.length, testgroup.members.length); + for (int i = 0; i < group.members.length; i++) { + expect(group.members[i].id, testgroup.members[i].id); + expect(group.members[i].name, testgroup.members[i].name); + expect(group.members[i].createdAt, testgroup.members[i].createdAt); + } + }); }); } From 229750ffcffda1c68ff2974b48c06fe138c30517 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:46:04 +0100 Subject: [PATCH 36/53] Fixed test --- test/db_tests/player_game_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 7df18a1..d6f282e 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -110,9 +110,9 @@ void main() { expect(removed, true); final result = await database.gameDao.getGameById( - gameId: testgameWithGroup.id, + gameId: testgameWithPlayers.id, ); - expect(result.players!.length, testgameWithGroup.players!.length - 1); + expect(result.players!.length, testgameWithPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, From 32f3f68da9083c2f60a9f664146d4b180de007bc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:46:18 +0100 Subject: [PATCH 37/53] Annotation for missing test & method --- lib/data/dao/player_group_dao.dart | 3 +++ test/db_tests/player_group_test.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 5484bf7..4024629 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -10,6 +10,9 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); + /// No need for a groupHasPlayers method since the members attribute is + /// not nullable + /// Adds a [player] to a group with the given [groupId]. /// If the player is already in the group, no action is taken. /// If the player does not exist in the player table, they are added. diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 74d7658..1181eda 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -41,6 +41,9 @@ void main() { }); group('Player-Group Tests', () { + /// No need to test if group has players since the members attribute is + /// not nullable + test('Adding a player to a group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.playerDao.addPlayer(player: player4); From e15f5d163db0b40c3bf1c5198e71d147e6f99035 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:12:36 +0100 Subject: [PATCH 38/53] Added missing methods --- test/db_tests/group_game_test.dart | 42 ++++++++++++++++------------ test/db_tests/player_game_test.dart | 18 ++++++++++-- test/db_tests/player_group_test.dart | 14 ++++++++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 6621d9a..e231284 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -15,8 +15,8 @@ void main() { late Player player4; late Player player5; late Group testgroup; - late Game gameWithGroup; - late Game gameWithPlayers; + late Game testgameWithGroup; + late Game testgameWithPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -39,11 +39,11 @@ void main() { name: 'Test Group', members: [player1, player2, player3], ); - gameWithPlayers = Game( + testgameWithPlayers = Game( name: 'Game with Players', players: [player4, player5], ); - gameWithGroup = Game(name: 'Game with Group', group: testgroup); + testgameWithGroup = Game(name: 'Game with Group', group: testgroup); }); }); tearDown(() async { @@ -51,63 +51,69 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: gameWithPlayers); + database.gameDao.addGame(game: testgameWithPlayers); database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, ); expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + database.groupGameDao.addGroupToGame( + testgameWithPlayers.id, + testgroup.id, + ); gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, ); expect(gameHasGroup, true); }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: gameWithPlayers); + database.gameDao.addGame(game: testgameWithPlayers); database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + database.groupGameDao.addGroupToGame( + testgameWithPlayers.id, + testgroup.id, + ); var groupAdded = await database.groupGameDao.isGroupInGame( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, groupId: testgroup.id, ); expect(groupAdded, true); groupAdded = await database.groupGameDao.isGroupInGame( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, groupId: '', ); expect(groupAdded, false); }); test('Removing group from game works correctly', () async { - await database.gameDao.addGame(game: gameWithGroup); + await database.gameDao.addGame(game: testgameWithGroup); - final groupToRemove = gameWithGroup.group!; + final groupToRemove = testgameWithGroup.group!; final removed = await database.groupGameDao.removeGroupFromGame( groupId: groupToRemove.id, - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); expect(removed, true); final result = await database.gameDao.getGameById( - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); expect(result.group, null); }); test('Retrieving group of a game works correctly', () async { - await database.gameDao.addGame(game: gameWithGroup); + await database.gameDao.addGame(game: testgameWithGroup); final group = await database.groupGameDao.getGroupOfGame( - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); if (group == null) { diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index d6f282e..1fd0128 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -120,7 +120,21 @@ void main() { expect(playerExists, false); }); - //TODO: test getPlayersOfGame() - test('Retrieving players of a game works correctly', () async {}); + test('Retrieving players of a game works correctly', () async { + await database.gameDao.addGame(game: testgameWithPlayers); + final players = await database.playerGameDao.getPlayersOfGame( + gameId: testgameWithPlayers.id, + ); + + if (players == null) { + fail('Players should not be null'); + } + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testgameWithPlayers.players![i].id); + expect(players[i].name, testgameWithPlayers.players![i].name); + expect(players[i].createdAt, testgameWithPlayers.players![i].createdAt); + } + }); }); } diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 1181eda..9e367e0 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -87,7 +87,17 @@ void main() { expect(playerExists, false); }); - //TODO: test getPlayersOfGroup() - test('Retrieving players of a group works correctly', () async {}); + test('Retrieving players of a group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: testgroup.id, + ); + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testgroup.members[i].id); + expect(players[i].name, testgroup.members[i].name); + expect(players[i].createdAt, testgroup.members[i].createdAt); + } + }); }); } From d948f2f13d6888996783f40b9761b87510c668f0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:42:27 +0100 Subject: [PATCH 39/53] Added iteration for multiple items test --- test/db_tests/game_test.dart | 130 +++++++++++++-------------------- test/db_tests/group_test.dart | 47 ++++++++---- test/db_tests/player_test.dart | 77 ++++++++++--------- 3 files changed, 129 insertions(+), 125 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d5b0856..71b7573 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -98,9 +98,8 @@ void main() { } }); - // TODO: Use upcoming addGames() method - // TODO: Iterate through games - test('Adding and fetching multiple games works correclty', () async { + test('Adding and fetching multiple games works correctly', () async { + // TODO: Use upcoming addGames() method await database.gameDao.addGame(game: testgame1); await database.gameDao.addGame(game: testgame2); await database.gameDao.addGame(game: testgameWithGroup); @@ -109,86 +108,61 @@ void main() { final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); - final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame1.id); - // game checks - expect(fetchedGame1.id, testgame1.id); - expect(fetchedGame1.name, testgame1.name); - expect(fetchedGame1.createdAt, testgame1.createdAt); - expect(fetchedGame1.winner, testgame1.winner); + final testGames = { + testgame1.id: testgame1, + testgame2.id: testgame2, + testgameWithGroup.id: testgameWithGroup, + testgameWithPlayer.id: testgameWithPlayer, + }; - // group checks - expect(fetchedGame1.group!.id, testgame1.group!.id); - expect(fetchedGame1.group!.name, testgame1.group!.name); - expect(fetchedGame1.group!.createdAt, testgame1.group!.createdAt); - // group members checks - expect( - fetchedGame1.group!.members.length, - testgame1.group!.members.length, - ); - for (int i = 0; i < testgame1.group!.members.length; i++) { - expect( - fetchedGame1.group!.members[i].id, - testgame1.group!.members[i].id, - ); - expect( - fetchedGame1.group!.members[i].name, - testgame1.group!.members[i].name, - ); - expect( - fetchedGame1.group!.members[i].createdAt, - testgame1.group!.members[i].createdAt, - ); - } + for (final game in allGames) { + final expectedGame = testGames[game.id]!; - // players checks - for (int i = 0; i < fetchedGame1.players!.length; i++) { - expect(fetchedGame1.players![i].id, testgame1.players![i].id); - expect(fetchedGame1.players![i].name, testgame1.players![i].name); - expect( - fetchedGame1.players![i].createdAt, - testgame1.players![i].createdAt, - ); - } + // Game-Checks + expect(game.id, expectedGame.id); + expect(game.name, expectedGame.name); + expect(game.createdAt, expectedGame.createdAt); + expect(game.winner, expectedGame.winner); - final fetchedGame2 = allGames.firstWhere((g) => g.id == testgame2.id); - // game checks - expect(fetchedGame2.id, testgame2.id); - expect(fetchedGame2.name, testgame2.name); - expect(fetchedGame2.createdAt, testgame2.createdAt); - expect(fetchedGame2.winner, testgame2.winner); + // Group-Checks + if (expectedGame.group != null) { + expect(game.group!.id, expectedGame.group!.id); + expect(game.group!.name, expectedGame.group!.name); + expect(game.group!.createdAt, expectedGame.group!.createdAt); - // group checks - expect(fetchedGame2.group!.id, testgame2.group!.id); - expect(fetchedGame2.group!.name, testgame2.group!.name); - expect(fetchedGame2.group!.createdAt, testgame2.group!.createdAt); - // group members checks - expect( - fetchedGame2.group!.members.length, - testgame2.group!.members.length, - ); - for (int i = 0; i < testgame2.group!.members.length; i++) { - expect( - fetchedGame2.group!.members[i].id, - testgame2.group!.members[i].id, - ); - expect( - fetchedGame2.group!.members[i].name, - testgame2.group!.members[i].name, - ); - expect( - fetchedGame2.group!.members[i].createdAt, - testgame2.group!.members[i].createdAt, - ); - } + // Group Members-Checks + expect( + game.group!.members.length, + expectedGame.group!.members.length, + ); + for (int i = 0; i < expectedGame.group!.members.length; i++) { + expect( + game.group!.members[i].id, + expectedGame.group!.members[i].id, + ); + expect( + game.group!.members[i].name, + expectedGame.group!.members[i].name, + ); + expect( + game.group!.members[i].createdAt, + expectedGame.group!.members[i].createdAt, + ); + } + } - // players checks - for (int i = 0; i < fetchedGame2.players!.length; i++) { - expect(fetchedGame2.players![i].id, testgame2.players![i].id); - expect(fetchedGame2.players![i].name, testgame2.players![i].name); - expect( - fetchedGame2.players![i].createdAt, - testgame2.players![i].createdAt, - ); + // Players-Checks + if (expectedGame.players != null) { + expect(game.players!.length, expectedGame.players!.length); + for (int i = 0; i < expectedGame.players!.length; i++) { + expect(game.players![i].id, expectedGame.players![i].id); + expect(game.players![i].name, expectedGame.players![i].name); + expect( + game.players![i].createdAt, + expectedGame.players![i].createdAt, + ); + } + } } }); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index b18942e..189e4c3 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -14,6 +14,8 @@ void main() { late Player player4; late Group testgroup; late Group testgroup2; + late Group testgroup3; + late Group testgroup4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -40,6 +42,16 @@ void main() { name: 'Second Group', members: [player2, player3, player4], ); + testgroup3 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player4], + ); + testgroup4 = Group( + id: 'gr2', + name: 'Second Group', + members: [player1, player2, player3, player4], + ); }); }); tearDown(() async { @@ -68,28 +80,37 @@ void main() { } }); - test('Adding and fetching a single group works correctly', () async { + test('Adding and fetching multiple groups works correctly', () async { + // TODO: Use upcoming addGroups() method await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); + await database.groupDao.addGroup(group: testgroup3); + await database.groupDao.addGroup(group: testgroup4); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); - final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); - expect(fetchedGroup1.name, testgroup.name); - expect(fetchedGroup1.members.length, testgroup.members.length); - expect(fetchedGroup1.members.elementAt(0).id, player1.id); - expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt); + final testGroups = {testgroup.id: testgroup, testgroup2.id: testgroup2}; - final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); - expect(fetchedGroup2.name, testgroup2.name); - expect(fetchedGroup2.members.length, testgroup2.members.length); - expect(fetchedGroup2.members.elementAt(0).id, player2.id); - expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); + for (final group in allGroups) { + final expectedGroup = testGroups[group.id]!; + + expect(group.id, expectedGroup.id); + expect(group.name, expectedGroup.name); + expect(group.createdAt, expectedGroup.createdAt); + + expect(group.members.length, expectedGroup.members.length); + for (int i = 0; i < expectedGroup.members.length; i++) { + expect(group.members[i].id, expectedGroup.members[i].id); + expect(group.members[i].name, expectedGroup.members[i].name); + expect( + group.members[i].createdAt, + expectedGroup.members[i].createdAt, + ); + } + } }); - // TODO: Use upcoming addGroups() method - // TODO: An Test in Game Tests orientieren test('Adding the same group twice does not create duplicates', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 9430433..aa5d09e 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -7,8 +7,10 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player testPlayer; + late Player testPlayer1; late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -22,27 +24,29 @@ void main() { ); withClock(fakeClock, () { - testPlayer = Player(name: 'Test Player'); + testPlayer1 = Player(name: 'Test Player'); testPlayer2 = Player(name: 'Second Player'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); }); }); tearDown(() async { await database.close(); }); - group('player tests', () { + group('Player Tests', () { test('Adding and fetching single player works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 2); final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, + (g) => g.id == testPlayer1.id, ); - expect(fetchedPlayer1.name, testPlayer.name); - expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + expect(fetchedPlayer1.name, testPlayer1.name); + expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); final fetchedPlayer2 = allPlayers.firstWhere( (g) => g.id == testPlayer2.id, @@ -51,31 +55,36 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - // TODO: Use upcoming addPlayers() method - // TODO: An Tests in Game orientieren test('Adding and fetching multiple players works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + // TODO: Use upcoming addPlayers() method + await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); + await database.playerDao.addPlayer(player: testPlayer3); + await database.playerDao.addPlayer(player: testPlayer4); final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); + expect(allPlayers.length, 4); - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, - ); - expect(fetchedPlayer1.name, testPlayer.name); - expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + // Map for connencting fetched players with expected players + final testPlayer = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + testPlayer3.id: testPlayer3, + testPlayer4.id: testPlayer4, + }; - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); - expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); + for (final player in allPlayers) { + final expectedPlayer = testPlayer[player.id]!; + + expect(player.id, expectedPlayer.id); + expect(player.name, expectedPlayer.name); + expect(player.createdAt, expectedPlayer.createdAt); + } }); test('Adding the same player twice does not create duplicates', () async { - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer1); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 1); @@ -83,43 +92,43 @@ void main() { test('Player existence check works correctly', () async { var playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, false); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, true); }); test('Deleting a player works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); final playerDeleted = await database.playerDao.deletePlayer( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerDeleted, true); final playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, false); }); test('Updating a player name works correcly', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); const newPlayerName = 'new player name'; await database.playerDao.updatePlayername( - playerId: testPlayer.id, + playerId: testPlayer1.id, newName: newPlayerName, ); final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(result.name, newPlayerName); }); @@ -128,7 +137,7 @@ void main() { var playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 0); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 1); @@ -138,7 +147,7 @@ void main() { playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 2); - await database.playerDao.deletePlayer(playerId: testPlayer.id); + await database.playerDao.deletePlayer(playerId: testPlayer1.id); playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 1); From 8c0538520353ba9e42ff893a7f800d32591acf97 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:47:27 +0100 Subject: [PATCH 40/53] Renamed variables to be consistent --- test/db_tests/game_test.dart | 133 ++++++++++++++------------- test/db_tests/group_game_test.dart | 28 +++--- test/db_tests/group_test.dart | 96 +++++++++---------- test/db_tests/player_game_test.dart | 82 ++++++++--------- test/db_tests/player_group_test.dart | 24 ++--- 5 files changed, 183 insertions(+), 180 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 71b7573..a7163a3 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -9,17 +9,17 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; - late Group testgroup; - late Group testgroup2; - late Game testgame1; - late Game testgame2; - late Game testgameWithPlayer; - late Game testgameWithGroup; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Group testGroup1; + late Group testGroup2; + late Game testGame1; + late Game testGame2; + late Game testGameOnlyPlayers; + late Game testGameOnlyGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -33,31 +33,34 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); - testgroup = Group( + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); + testGroup1 = Group( name: 'Test Group 2', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgroup2 = Group(name: 'Test Group 2', members: [player4, player5]); - testgame1 = Game( - name: 'Test Game', - group: testgroup, - players: [player4, player5], + testGroup2 = Group( + name: 'Test Group 2', + members: [testPlayer4, testPlayer5], ); - testgame2 = Game( + testGame1 = Game( + name: 'First Test Game', + group: testGroup1, + players: [testPlayer4, testPlayer5], + ); + testGame2 = Game( name: 'Second Test Game', - group: testgroup2, - players: [player1, player2, player3], + group: testGroup2, + players: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithPlayer = Game( - name: 'Second Test Game', - players: [player1, player2, player3], + testGameOnlyPlayers = Game( + name: 'Test Game with Players', + players: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithGroup = Game(name: 'Second Test Game', group: testgroup2); + testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); }); }); tearDown(() async { @@ -66,32 +69,32 @@ void main() { group('Game Tests', () { test('Adding and fetching single game works correclty', () async { - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); - final result = await database.gameDao.getGameById(gameId: testgame1.id); + final result = await database.gameDao.getGameById(gameId: testGame1.id); - expect(result.id, testgame1.id); - expect(result.name, testgame1.name); - expect(result.winner, testgame1.winner); - expect(result.createdAt, testgame1.createdAt); + expect(result.id, testGame1.id); + expect(result.name, testGame1.name); + expect(result.winner, testGame1.winner); + expect(result.createdAt, testGame1.createdAt); if (result.group != null) { - expect(result.group!.members.length, testgroup.members.length); + expect(result.group!.members.length, testGroup1.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.group!.members[i].id, testgroup.members[i].id); - expect(result.group!.members[i].name, testgroup.members[i].name); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(result.group!.members[i].id, testGroup1.members[i].id); + expect(result.group!.members[i].name, testGroup1.members[i].name); } } else { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testgame1.players!.length); + expect(result.players!.length, testGame1.players!.length); - for (int i = 0; i < testgame1.players!.length; i++) { - expect(result.players![i].id, testgame1.players![i].id); - expect(result.players![i].name, testgame1.players![i].name); - expect(result.players![i].createdAt, testgame1.players![i].createdAt); + for (int i = 0; i < testGame1.players!.length; i++) { + expect(result.players![i].id, testGame1.players![i].id); + expect(result.players![i].name, testGame1.players![i].name); + expect(result.players![i].createdAt, testGame1.players![i].createdAt); } } else { fail('Players is null'); @@ -100,19 +103,19 @@ void main() { test('Adding and fetching multiple games works correctly', () async { // TODO: Use upcoming addGames() method - await database.gameDao.addGame(game: testgame1); - await database.gameDao.addGame(game: testgame2); - await database.gameDao.addGame(game: testgameWithGroup); - await database.gameDao.addGame(game: testgameWithPlayer); + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame2); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.gameDao.addGame(game: testGameOnlyPlayers); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); final testGames = { - testgame1.id: testgame1, - testgame2.id: testgame2, - testgameWithGroup.id: testgameWithGroup, - testgameWithPlayer.id: testgameWithPlayer, + testGame1.id: testGame1, + testGame2.id: testGame2, + testGameOnlyGroup.id: testGameOnlyGroup, + testGameOnlyPlayers.id: testGameOnlyPlayers, }; for (final game in allGames) { @@ -167,33 +170,33 @@ void main() { }); test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testgame1); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame1); final gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); }); test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testgame1.id); + var gameExists = await database.gameDao.gameExists(gameId: testGame1.id); expect(gameExists, false); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); - gameExists = await database.gameDao.gameExists(gameId: testgame1.id); + gameExists = await database.gameDao.gameExists(gameId: testGame1.id); expect(gameExists, true); }); test('Deleting a game works correclty', () async { - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); final gameDeleted = await database.gameDao.deleteGame( - gameId: testgame1.id, + gameId: testGame1.id, ); expect(gameDeleted, true); final gameExists = await database.gameDao.gameExists( - gameId: testgame1.id, + gameId: testGame1.id, ); expect(gameExists, false); }); @@ -202,22 +205,22 @@ void main() { var gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); - await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGame(game: testGame2); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 2); - await database.gameDao.deleteGame(gameId: testgame1.id); + await database.gameDao.deleteGame(gameId: testGame1.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); - await database.gameDao.deleteGame(gameId: testgame2.id); + await database.gameDao.deleteGame(gameId: testGame2.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index e231284..49d3cdb 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -9,11 +9,11 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; late Group testgroup; late Game testgameWithGroup; late Game testgameWithPlayers; @@ -30,20 +30,20 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); testgameWithPlayers = Game( - name: 'Game with Players', - players: [player4, player5], + name: 'Test Game with Players', + players: [testPlayer4, testPlayer5], ); - testgameWithGroup = Game(name: 'Game with Group', group: testgroup); + testgameWithGroup = Game(name: 'Test Game with Group', group: testgroup); }); }); tearDown(() async { diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 189e4c3..2cf9bba 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -8,14 +8,14 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Group testgroup; - late Group testgroup2; - late Group testgroup3; - late Group testgroup4; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Group testGroup1; + late Group testGroup2; + late Group testGroup3; + late Group testGroup4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -29,28 +29,28 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - testgroup = Group( + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testGroup1 = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgroup2 = Group( + testGroup2 = Group( id: 'gr2', name: 'Second Group', - members: [player2, player3, player4], + members: [testPlayer2, testPlayer3, testPlayer4], ); - testgroup3 = Group( + testGroup3 = Group( id: 'gr2', name: 'Second Group', - members: [player2, player4], + members: [testPlayer2, testPlayer4], ); - testgroup4 = Group( + testGroup4 = Group( id: 'gr2', name: 'Second Group', - members: [player1, player2, player3, player4], + members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); }); }); @@ -59,38 +59,38 @@ void main() { }); group('Group Tests', () { test('Adding and fetching a single group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final fetchedGroup = await database.groupDao.getGroupById( - groupId: testgroup.id, + groupId: testGroup1.id, ); - expect(fetchedGroup.id, testgroup.id); - expect(fetchedGroup.name, testgroup.name); - expect(fetchedGroup.createdAt, testgroup.createdAt); + expect(fetchedGroup.id, testGroup1.id); + expect(fetchedGroup.name, testGroup1.name); + expect(fetchedGroup.createdAt, testGroup1.createdAt); - expect(fetchedGroup.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(fetchedGroup.members[i].id, testgroup.members[i].id); - expect(fetchedGroup.members[i].name, testgroup.members[i].name); + expect(fetchedGroup.members.length, testGroup1.members.length); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(fetchedGroup.members[i].id, testGroup1.members[i].id); + expect(fetchedGroup.members[i].name, testGroup1.members[i].name); expect( fetchedGroup.members[i].createdAt, - testgroup.members[i].createdAt, + testGroup1.members[i].createdAt, ); } }); test('Adding and fetching multiple groups works correctly', () async { // TODO: Use upcoming addGroups() method - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); - await database.groupDao.addGroup(group: testgroup3); - await database.groupDao.addGroup(group: testgroup4); + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup2); + await database.groupDao.addGroup(group: testGroup3); + await database.groupDao.addGroup(group: testGroup4); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); - final testGroups = {testgroup.id: testgroup, testgroup2.id: testgroup2}; + final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; for (final group in allGroups) { final expectedGroup = testGroups[group.id]!; @@ -112,8 +112,8 @@ void main() { }); test('Adding the same group twice does not create duplicates', () async { - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup1); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 1); @@ -121,42 +121,42 @@ void main() { test('Group existence check works correctly', () async { var groupExists = await database.groupDao.groupExists( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupExists, false); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); - groupExists = await database.groupDao.groupExists(groupId: testgroup.id); + groupExists = await database.groupDao.groupExists(groupId: testGroup1.id); expect(groupExists, true); }); test('Deleting a group works correclty', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final groupDeleted = await database.groupDao.deleteGroup( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupDeleted, true); final groupExists = await database.groupDao.groupExists( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupExists, false); }); test('Updating a group name works correcly', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); const newGroupName = 'new group name'; await database.groupDao.updateGroupname( - groupId: testgroup.id, + groupId: testGroup1.id, newName: newGroupName, ); final result = await database.groupDao.getGroupById( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(result.name, newGroupName); }); @@ -165,13 +165,13 @@ void main() { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final groupAdded = await database.groupDao.getGroupCount(); expect(groupAdded, 1); final groupRemoved = await database.groupDao.deleteGroup( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupRemoved, true); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 1fd0128..0eca9ff 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -9,15 +9,15 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; - late Player player6; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Player testPlayer6; late Group testgroup; - late Game testgameWithGroup; - late Game testgameWithPlayers; + late Game testGameOnlyGroup; + late Game testGameOnlyPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -31,20 +31,20 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); - player6 = Player(name: 'Frank'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); + testPlayer6 = Player(name: 'Frank'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithGroup = Game(name: 'Test Game', group: testgroup); - testgameWithPlayers = Game( + testGameOnlyGroup = Game(name: 'Test Game with Group', group: testgroup); + testGameOnlyPlayers = Game( name: 'Test Game with Players', - players: [player4, player5, player6], + players: [testPlayer4, testPlayer5, testPlayer6], ); }); }); @@ -54,44 +54,44 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testgameWithGroup); - database.playerDao.addPlayer(player: player1); + database.gameDao.addGame(game: testGameOnlyGroup); + database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, ); expect(gameHasPlayers, false); database.playerGameDao.addPlayerToGame( - gameId: testgameWithGroup.id, - playerId: player1.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer1.id, ); gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, ); expect(gameHasPlayers, true); }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithGroup); - database.playerDao.addPlayer(player: player5); + database.gameDao.addGame(game: testGameOnlyGroup); + database.playerDao.addPlayer(player: testPlayer5); database.playerGameDao.addPlayerToGame( - gameId: testgameWithGroup.id, - playerId: player5.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer5.id, ); var playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testgameWithGroup.id, - playerId: player5.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer5.id, ); expect(playerAdded, true); playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, playerId: '', ); @@ -99,20 +99,20 @@ void main() { }); test('Removing player from game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + await database.gameDao.addGame(game: testGameOnlyPlayers); - final playerToRemove = testgameWithPlayers.players![0]; + final playerToRemove = testGameOnlyPlayers.players![0]; final removed = await database.playerGameDao.removePlayerFromGame( playerId: playerToRemove.id, - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); expect(removed, true); final result = await database.gameDao.getGameById( - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); - expect(result.players!.length, testgameWithPlayers.players!.length - 1); + expect(result.players!.length, testGameOnlyPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, @@ -121,9 +121,9 @@ void main() { }); test('Retrieving players of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + await database.gameDao.addGame(game: testGameOnlyPlayers); final players = await database.playerGameDao.getPlayersOfGame( - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); if (players == null) { @@ -131,9 +131,9 @@ void main() { } for (int i = 0; i < players.length; i++) { - expect(players[i].id, testgameWithPlayers.players![i].id); - expect(players[i].name, testgameWithPlayers.players![i].name); - expect(players[i].createdAt, testgameWithPlayers.players![i].createdAt); + expect(players[i].id, testGameOnlyPlayers.players![i].id); + expect(players[i].name, testGameOnlyPlayers.players![i].name); + expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); } }); }); diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 9e367e0..2783430 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -8,10 +8,10 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; late Group testgroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -26,13 +26,13 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); }); }); @@ -46,15 +46,15 @@ void main() { test('Adding a player to a group works correctly', () async { await database.groupDao.addGroup(group: testgroup); - await database.playerDao.addPlayer(player: player4); + await database.playerDao.addPlayer(player: testPlayer4); await database.playerGroupDao.addPlayerToGroup( groupId: testgroup.id, - player: player4, + player: testPlayer4, ); var playerAdded = await database.playerGroupDao.isPlayerInGroup( groupId: testgroup.id, - playerId: player4.id, + playerId: testPlayer4.id, ); expect(playerAdded, true); From 51722eb7fd25dbfe321df3f853f705e46df89d0f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:01:45 +0100 Subject: [PATCH 41/53] Added batch insert methods to tests --- test/db_tests/game_test.dart | 8 +++----- test/db_tests/group_test.dart | 8 +++----- test/db_tests/player_test.dart | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index a7163a3..86ca34b 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -102,11 +102,9 @@ void main() { }); test('Adding and fetching multiple games works correctly', () async { - // TODO: Use upcoming addGames() method - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); - await database.gameDao.addGame(game: testGameOnlyGroup); - await database.gameDao.addGame(game: testGameOnlyPlayers); + await database.gameDao.addGames( + games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], + ); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2cf9bba..b2e63eb 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -81,11 +81,9 @@ void main() { }); test('Adding and fetching multiple groups works correctly', () async { - // TODO: Use upcoming addGroups() method - await database.groupDao.addGroup(group: testGroup1); - await database.groupDao.addGroup(group: testGroup2); - await database.groupDao.addGroup(group: testGroup3); - await database.groupDao.addGroup(group: testGroup4); + await database.groupDao.addGroups( + groups: [testGroup1, testGroup2, testGroup3, testGroup4], + ); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index aa5d09e..0d72ed5 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -56,11 +56,9 @@ void main() { }); test('Adding and fetching multiple players works correclty', () async { - // TODO: Use upcoming addPlayers() method - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer2); - await database.playerDao.addPlayer(player: testPlayer3); - await database.playerDao.addPlayer(player: testPlayer4); + await database.playerDao.addPlayers( + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], + ); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 4); From c56663d15e1a4caef27193254846c507f52be363 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:06:11 +0100 Subject: [PATCH 42/53] Added missing await --- test/db_tests/group_game_test.dart | 12 ++++++------ test/db_tests/player_game_test.dart | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 49d3cdb..1733243 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -51,8 +51,8 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( gameId: testgameWithPlayers.id, @@ -60,7 +60,7 @@ void main() { expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame( + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); @@ -73,9 +73,9 @@ void main() { }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame( + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 0eca9ff..e8fd707 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -54,8 +54,8 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( gameId: testGameOnlyGroup.id, @@ -63,7 +63,7 @@ void main() { expect(gameHasPlayers, false); - database.playerGameDao.addPlayerToGame( + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer1.id, ); @@ -76,9 +76,9 @@ void main() { }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer5); - database.playerGameDao.addPlayerToGame( + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer5); + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer5.id, ); From dbb52cfc48546f69245297a0bf3b883d5bd8ba29 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:20:42 +0100 Subject: [PATCH 43/53] Added missing awaits --- test/db_tests/group_game_test.dart | 12 ++++++------ test/db_tests/player_game_test.dart | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 49d3cdb..1733243 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -51,8 +51,8 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( gameId: testgameWithPlayers.id, @@ -60,7 +60,7 @@ void main() { expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame( + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); @@ -73,9 +73,9 @@ void main() { }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame( + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 0eca9ff..f50afc1 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -54,8 +54,8 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( gameId: testGameOnlyGroup.id, @@ -76,9 +76,9 @@ void main() { }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer5); - database.playerGameDao.addPlayerToGame( + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer5); + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer5.id, ); From ab250e2df43d1880d6d0e0c190a2ca00b7be76d2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:24:57 +0100 Subject: [PATCH 44/53] Typo --- test/db_tests/game_test.dart | 4 ++-- test/db_tests/group_test.dart | 2 +- test/db_tests/player_test.dart | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index a7163a3..311e30f 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -68,7 +68,7 @@ void main() { }); group('Game Tests', () { - test('Adding and fetching single game works correclty', () async { + test('Adding and fetching single game works correctly', () async { await database.gameDao.addGame(game: testGame1); final result = await database.gameDao.getGameById(gameId: testGame1.id); @@ -187,7 +187,7 @@ void main() { expect(gameExists, true); }); - test('Deleting a game works correclty', () async { + test('Deleting a game works correctly', () async { await database.gameDao.addGame(game: testGame1); final gameDeleted = await database.gameDao.deleteGame( diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2cf9bba..9104b74 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -131,7 +131,7 @@ void main() { expect(groupExists, true); }); - test('Deleting a group works correclty', () async { + test('Deleting a group works correctly', () async { await database.groupDao.addGroup(group: testGroup1); final groupDeleted = await database.groupDao.deleteGroup( diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index aa5d09e..2ec57e5 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -35,7 +35,7 @@ void main() { }); group('Player Tests', () { - test('Adding and fetching single player works correclty', () async { + test('Adding and fetching single player works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); @@ -55,7 +55,7 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('Adding and fetching multiple players works correclty', () async { + test('Adding and fetching multiple players works correctly', () async { // TODO: Use upcoming addPlayers() method await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); @@ -104,7 +104,7 @@ void main() { expect(playerExists, true); }); - test('Deleting a player works correclty', () async { + test('Deleting a player works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); final playerDeleted = await database.playerDao.deletePlayer( playerId: testPlayer1.id, From 1b3334f3e0c7439d74931ce6554c393cbecd4079 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:24 +0100 Subject: [PATCH 45/53] Fixed addGroups method --- lib/data/dao/group_dao.dart | 53 +++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 3489f5c..fbb4d6f 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -87,10 +87,17 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { Future addGroups({required List groups}) async { if (groups.isEmpty) return; await db.transaction(() async { + // Deduplicate groups by id - keep first occurrence + final Map uniqueGroups = {}; + for (final g in groups) { + uniqueGroups.putIfAbsent(g.id, () => g); + } + + // Insert unique groups in batch await db.batch( (b) => b.insertAll( groupTable, - groups + uniqueGroups.values .map( (group) => GroupTableCompanion.insert( id: group.id, @@ -103,17 +110,24 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ); - for (final group in groups) { - await db.playerDao.addPlayers(players: group.members); + // Collect unique players from all groups + final uniquePlayers = {}; + for (final g in uniqueGroups.values) { + for (final m in g.members) { + uniquePlayers[m.id] = m; + } + } + if (uniquePlayers.isNotEmpty) { await db.batch( (b) => b.insertAll( - db.playerGroupTable, - group.members + db.playerTable, + uniquePlayers.values .map( - (member) => PlayerGroupTableCompanion.insert( - playerId: member.id, - groupId: group.id, + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, ), ) .toList(), @@ -121,6 +135,29 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ); } + + // Prepare all player-group associations in one list (unique pairs) + final Set seenPairs = {}; + final List pgRows = []; + for (final g in uniqueGroups.values) { + for (final m in g.members) { + final key = '${m.id}|${g.id}'; + if (!seenPairs.contains(key)) { + seenPairs.add(key); + pgRows.add( + PlayerGroupTableCompanion.insert(playerId: m.id, groupId: g.id), + ); + } + } + } + + if (pgRows.isNotEmpty) { + await db.batch((b) { + for (final pg in pgRows) { + b.insert(db.playerGroupTable, pg, mode: InsertMode.insertOrReplace); + } + }); + } }); } From 70307016b36581f9e7495a8c149a74776ad5f6a2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:32 +0100 Subject: [PATCH 46/53] Fixed addGames method --- lib/data/dao/game_dao.dart | 78 +++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 12893db..47b5848 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -107,6 +107,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { mode: InsertMode.insertOrReplace, ), ); + // Add all groups of the games in batch await db.batch( (b) => b.insertAll( @@ -125,8 +126,42 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ), ); - // Add all players of the games in batch - await db.batch((b) async { + // Add all players of the games in batch (unique) + final uniquePlayers = {}; + for (final game in games) { + if (game.players != null) { + for (final p in game.players!) { + uniquePlayers[p.id] = p; + } + } + // Also include members of groups + if (game.group != null) { + for (final m in game.group!.members) { + uniquePlayers[m.id] = m; + } + } + } + + if (uniquePlayers.isNotEmpty) { + await db.batch( + (b) => b.insertAll( + db.playerTable, + uniquePlayers.values + .map( + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + + // Add all player-game associations in batch + await db.batch((b) { for (final game in games) { if (game.players != null) { for (final p in game.players ?? []) { @@ -143,8 +178,26 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } }); + // Add all player-group associations in batch + await db.batch((b) { + for (final game in games) { + if (game.group != null) { + for (final m in game.group!.members) { + b.insert( + db.playerGroupTable, + PlayerGroupTableCompanion.insert( + playerId: m.id, + groupId: game.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + // Add all group-game associations in batch - await db.batch((b) async { + await db.batch((b) { for (final game in games) { if (game.group != null) { b.insert( @@ -158,25 +211,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } } }); - - // Add all player-game associations in batch - await db.batch((b) async { - for (final game in games) { - if (game.players != null) { - for (final p in game.players ?? []) { - b.insert( - db.playerTable, - PlayerTableCompanion.insert( - id: p.id, - name: p.name, - createdAt: p.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); }); } From 2ebd4274f05b22bde0dcb9f927de2e55d49f3f1a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:44 +0100 Subject: [PATCH 47/53] Moved method validateJsonSchema() --- .../views/main_menu/settings_view.dart | 25 ----------------- lib/services/data_transfer_service.dart | 28 +++++++++++++++++-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 70f7663..6ebb7fb 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,39 +1,14 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; -import 'package:json_schema/json_schema.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @override State createState() => _SettingsViewState(); - - static Future validateJsonSchema(String jsonString) async { - final String schemaString; - - schemaString = await rootBundle.loadString('assets/schema.json'); - - try { - final schema = JsonSchema.create(json.decode(schemaString)); - final jsonData = json.decode(jsonString); - final result = schema.validate(jsonData); - - if (result.isValid) { - return true; - } - return false; - } catch (e, stack) { - print('[validateJsonSchema] $e'); - print(stack); - return false; - } - } } class _SettingsViewState extends State { diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 13eb658..eaa9633 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -1,15 +1,15 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; class DataTransferService { @@ -85,7 +85,7 @@ class DataTransferService { return ImportResult.fileReadError; } - if (await SettingsView.validateJsonSchema(jsonString)) { + if (await _validateJsonSchema(jsonString)) { final Map jsonData = json.decode(jsonString) as Map; @@ -136,4 +136,26 @@ class DataTransferService { if (file.path != null) return await File(file.path!).readAsString(); return null; } + + /// Validates the given JSON string against the predefined schema. + static Future _validateJsonSchema(String jsonString) async { + final String schemaString; + + schemaString = await rootBundle.loadString('assets/schema.json'); + + try { + final schema = JsonSchema.create(json.decode(schemaString)); + final jsonData = json.decode(jsonString); + final result = schema.validate(jsonData); + + if (result.isValid) { + return true; + } + return false; + } catch (e, stack) { + print('[validateJsonSchema] $e'); + print(stack); + return false; + } + } } From 893eb91143a67ff175a1ee8fed77d7a3b4c7ba2b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 01:12:39 +0100 Subject: [PATCH 48/53] Schema corrected --- assets/schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 69f889b..1883122 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -19,7 +19,7 @@ }, "players": { "type": [ - "object", + "array", "null" ], "properties": { @@ -88,7 +88,7 @@ ] }, "winner": { - "type": "string" + "type": ["string","null"] }, "required": [ "id", From 62eea086144522acde8a8396aa584f3dbd24b9b2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:12:41 +0100 Subject: [PATCH 49/53] Renamed variable --- lib/data/dao/game_dao.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index f29d553..283c02f 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -19,12 +19,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return Future.wait( result.map((row) async { final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); - final player = await db.playerGameDao.getPlayersOfGame(gameId: row.id); + final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); return Game( id: row.id, name: row.name, group: group, - players: player, + players: players, createdAt: row.createdAt, winner: row.winnerId, ); From 24f18f5c655088ecfa127495d8e34e8592aa1b9d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:13:15 +0100 Subject: [PATCH 50/53] Removed false comparison --- lib/data/dao/player_group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 4024629..8cf96c2 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -25,7 +25,7 @@ class PlayerGroupDao extends DatabaseAccessor return false; } - if (await db.playerDao.playerExists(playerId: player.id) == false) { + if (!await db.playerDao.playerExists(playerId: player.id)) { db.playerDao.addPlayer(player: player); } From bef812502cbe4ca7ed4a38974fb443bfc913232d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:20:51 +0100 Subject: [PATCH 51/53] Renamed variable and added null checks --- test/db_tests/game_test.dart | 53 ++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 311e30f..7590a97 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -119,52 +119,47 @@ void main() { }; for (final game in allGames) { - final expectedGame = testGames[game.id]!; + final testGame = testGames[game.id]!; // Game-Checks - expect(game.id, expectedGame.id); - expect(game.name, expectedGame.name); - expect(game.createdAt, expectedGame.createdAt); - expect(game.winner, expectedGame.winner); + expect(game.id, testGame.id); + expect(game.name, testGame.name); + expect(game.createdAt, testGame.createdAt); + expect(game.winner, testGame.winner); // Group-Checks - if (expectedGame.group != null) { - expect(game.group!.id, expectedGame.group!.id); - expect(game.group!.name, expectedGame.group!.name); - expect(game.group!.createdAt, expectedGame.group!.createdAt); + if (testGame.group != null) { + expect(game.group!.id, testGame.group!.id); + expect(game.group!.name, testGame.group!.name); + expect(game.group!.createdAt, testGame.group!.createdAt); // Group Members-Checks - expect( - game.group!.members.length, - expectedGame.group!.members.length, - ); - for (int i = 0; i < expectedGame.group!.members.length; i++) { - expect( - game.group!.members[i].id, - expectedGame.group!.members[i].id, - ); + expect(game.group!.members.length, testGame.group!.members.length); + for (int i = 0; i < testGame.group!.members.length; i++) { + expect(game.group!.members[i].id, testGame.group!.members[i].id); expect( game.group!.members[i].name, - expectedGame.group!.members[i].name, + testGame.group!.members[i].name, ); expect( game.group!.members[i].createdAt, - expectedGame.group!.members[i].createdAt, + testGame.group!.members[i].createdAt, ); } + } else { + expect(game.group, null); } // Players-Checks - if (expectedGame.players != null) { - expect(game.players!.length, expectedGame.players!.length); - for (int i = 0; i < expectedGame.players!.length; i++) { - expect(game.players![i].id, expectedGame.players![i].id); - expect(game.players![i].name, expectedGame.players![i].name); - expect( - game.players![i].createdAt, - expectedGame.players![i].createdAt, - ); + if (testGame.players != null) { + expect(game.players!.length, testGame.players!.length); + for (int i = 0; i < testGame.players!.length; i++) { + expect(game.players![i].id, testGame.players![i].id); + expect(game.players![i].name, testGame.players![i].name); + expect(game.players![i].createdAt, testGame.players![i].createdAt); } + } else { + expect(game.players, null); } } }); From 9346f61d141c1658e33101527e04ff6e88689f56 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:21:28 +0100 Subject: [PATCH 52/53] Renamed variable --- test/db_tests/group_test.dart | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 9104b74..6c8e6c9 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -93,20 +93,17 @@ void main() { final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; for (final group in allGroups) { - final expectedGroup = testGroups[group.id]!; + final testGroup = testGroups[group.id]!; - expect(group.id, expectedGroup.id); - expect(group.name, expectedGroup.name); - expect(group.createdAt, expectedGroup.createdAt); + expect(group.id, testGroup.id); + expect(group.name, testGroup.name); + expect(group.createdAt, testGroup.createdAt); - expect(group.members.length, expectedGroup.members.length); - for (int i = 0; i < expectedGroup.members.length; i++) { - expect(group.members[i].id, expectedGroup.members[i].id); - expect(group.members[i].name, expectedGroup.members[i].name); - expect( - group.members[i].createdAt, - expectedGroup.members[i].createdAt, - ); + expect(group.members.length, testGroup.members.length); + for (int i = 0; i < testGroup.members.length; i++) { + expect(group.members[i].id, testGroup.members[i].id); + expect(group.members[i].name, testGroup.members[i].name); + expect(group.members[i].createdAt, testGroup.members[i].createdAt); } } }); From 30645f06f82c2c7cf20f9f79d1bb1f22aef3d59f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:21:55 +0100 Subject: [PATCH 53/53] Renamed variable --- test/db_tests/player_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 2ec57e5..30d9a14 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -66,7 +66,7 @@ void main() { expect(allPlayers.length, 4); // Map for connencting fetched players with expected players - final testPlayer = { + final testPlayers = { testPlayer1.id: testPlayer1, testPlayer2.id: testPlayer2, testPlayer3.id: testPlayer3, @@ -74,11 +74,11 @@ void main() { }; for (final player in allPlayers) { - final expectedPlayer = testPlayer[player.id]!; + final testPlayer = testPlayers[player.id]!; - expect(player.id, expectedPlayer.id); - expect(player.name, expectedPlayer.name); - expect(player.createdAt, expectedPlayer.createdAt); + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); } });