Datenbankstruktur für Spiele #16

Merged
flixcoo merged 22 commits from feature/13-datenbankstruktur-fuer-spiele into development 2025-11-15 15:56:40 +00:00
27 changed files with 3114 additions and 539 deletions

View File

@@ -1,6 +1,6 @@
---
name: Bug report
about: Create a report for something does not work as it should
about: Erstelle eine Meldung für etwas, das nicht Funktioniert, wie es soll.
title: ''
labels: 'Task/Bug'
assignees: ''
@@ -9,28 +9,27 @@ assignees: ''
# Bug Report
## Description
[A clear and concise description of the bug]
## Beschreibung
[Eine klare und prägnante Beschreibung des Bugs]
## Steps to Reproduce
1. Step 1
2. Step 2
## Schritte zur Reproduktion
1. Schritt 1
2. Schritt 2
3. ...
## Expected Behavior
[What should have happened]
## Erwartetes Verhalten
[Was hätte passieren sollen]
## Actual Behavior
[What actually happened]
## Tatsächliches Verhalten
[Was tatsächlich passiert ist]
## Screenshots/Logs
[If applicable, add screenshots, error logs, or stack traces]
## Screenshots/Protokolle
[Falls zutreffend, füge Screenshots, Error Logs oder Stack Traces hinzu]
## Environment
- OS: [e.g., iOS 18.5, Android 14]
## Umgebung
- Plattform: Android, iOS, Web
- OS: [z. B. iOS 18.5, Android 14]
- Flutter Version: [z.B. 3.35.6]
## Possible Fix (Optional)
[Any suggestions on how to resolve the issue]
## Related Issues (Optional)
[Reference similar issues or PRs]
## Verwandte Issues
[Verweisen Sie auf ähnliche Issues oder PRs]

View File

@@ -9,14 +9,14 @@ assignees: ''
# Enhancement
## Current Behavior
[Describe the existing functionality]
## Aktuelles Verhalten
[Beschreibe die bestehende Funktionalität]
## Limitations/Problems
[What are the current shortcomings?]
## Einschränkungen/Probleme
[Was sind die aktuellen Mängel?]
## Suggested Improvement
[How can this be enhanced? Be specific.]
## Vorgeschlagene Verbesserung
[Wie kann das Problem bzw. die Einschränkung verbessert werden?]
## Benefits
[How will this improve the product?]
## Zugehörige Issues
[Links zu verwandten oder blockierenden Issues]

View File

@@ -1,22 +1,19 @@
---
name: Feature
about: New feature for the app
about: Neues Feature für die App
title: ''
labels: 'Task\Feature'
assignees: ''
---
# 🚀 Feature
# Feature
## Description
[Detailed explanation of the proposed feature]
## Beschreibung
[Ausführliche Erläuterung der vorgeschlagenen Funktion]
## Why is this feature needed?
[Explain the problem or use case this feature would solve]
## Vorgeschlagene Lösung
[Beschreibe, wie die Feature funktionieren soll]
## Proposed Solution
[Describe how the feature should work]
## Related Issues (Optional)
[Links to related discussions or requests]
## Zugehörige Issues
[Links zu verwandten oder blockierenden Issues]

View File

@@ -1,16 +1,16 @@
# [PR Title]
# [PR Titel]
**Related Issue(s):**
**Zugehörige Issue(s):**
Closes `<issue-no>`
## Description
*A clear and concise overview of the changes made. Explain the "why" behind the PR, not just the "what".*
## Beschreibung
*Eine klare und prägnante Übersicht über die vorgenommenen Änderungen. Erläutere nicht nur das was gemacht wurde, sondern auch warum.*
## Changes Made
- [ ] Added new feature X
- [ ] Fixed bug in component Y
- [ ] Refactored module Z for better performance
- [ ] Updated dependencies
## Änderungen
- [ ] Neue Funktion X hinzugefügt
- [ ] Bug in Komponente Y behoben
- [ ] Modul Z für bessere Leistung refactored
- [ ] Dependencies aktualisiert
## Additional Notes
*Any extra context, limitations, or decisions that reviewers should know about?*
## Zusätzliche Anmerkungen
*Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, die Reviewer wissen sollten?*

View File

@@ -2,6 +2,8 @@ import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/game_table.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';
part 'game_dao.g.dart';
@@ -16,11 +18,58 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
return result.map((row) => Game(id: row.id, name: row.name)).toList();
}
/// Retrieves a [Game] by its [id].
Future<Game> getGameById(String id) async {
final query = select(gameTable)..where((g) => g.id.equals(id));
/// Retrieves a [Game] by its [gameId].
Future<Game> getGameById({required String gameId}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
final result = await query.getSingle();
return Game(id: result.id, name: result.name);
List<Player>? players;
sneeex marked this conversation as resolved
Review

Wieso gehst du überall davon aus, dass players null sein kann? Man kann doch kein Game ohne Player erstellen oder in welchem case ist das der Fall?

Wieso gehst du überall davon aus, dass players null sein kann? Man kann doch kein Game ohne Player erstellen oder in welchem case ist das der Fall?
Review

Weil ein Game ja auch nur eine Group bekommern kann

Weil ein `Game` ja auch nur eine `Group` bekommern kann
if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) {
players = await db.playerGameDao.getPlayersByGameId(gameId: gameId);
}
Group? group;
if (await db.groupGameDao.hasGameGroup(gameId: gameId)) {
group = await db.groupGameDao.getGroupByGameId(gameId: gameId);
}
return Game(
id: result.id,
name: result.name,
players: players,
group: group,
winner: result.winnerId,
);
}
/// Adds a new [Game] to the database.
/// Also adds associated players and group if they exist.
Future<void> 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,
name: game.name,
winnerId: game.winner,
),
mode: InsertMode.insertOrReplace,
);
});
}
/// Deletes the game with the given [gameId] from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteGame({required String gameId}) async {
final query = delete(gameTable)..where((g) => g.id.equals(gameId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Retrieves the number of games in the database.
@@ -31,4 +80,12 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
.getSingle();
return count ?? 0;
}
/// Checks if a game with the given [gameId] exists in the database.
/// Returns `true` if the game exists, otherwise `false`.
Future<bool> gameExists({required String gameId}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
final result = await query.getSingleOrNull();
return result != null;
}
}

View File

@@ -4,5 +4,6 @@ part of 'game_dao.dart';
// ignore_for_file: type=lint
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
}

View File

@@ -19,39 +19,60 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
.toList();
}
/// Retrieves a [Group] by its [id], including its members.
Future<Group> getGroupById(String id) async {
final query = select(groupTable)..where((g) => g.id.equals(id));
/// Retrieves a [Group] by its [groupId], including its members.
Future<Group> getGroupById({required String groupId}) async {
final query = select(groupTable)..where((g) => g.id.equals(groupId));
final result = await query.getSingle();
List<Player> members = [];
members = await db.playerGroupDao.getPlayersOfGroupById(id);
List<Player> members = await db.playerGroupDao.getPlayersOfGroupById(
groupId: groupId,
);
return Group(id: result.id, name: result.name, members: members);
}
/// Adds a new group with the given [id] and [name] to the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<void> addGroup(String id, String name) async {
await into(
groupTable,
).insert(GroupTableCompanion.insert(id: id, name: name));
/// This method also adds the group's members to the [PlayerGroupTable].
Future<void> addGroup({required Group group}) async {
await db.transaction(() async {
await into(
groupTable,
).insert(GroupTableCompanion.insert(id: group.id, name: group.name));
await db.batch(
(b) => b.insertAll(
db.playerGroupTable,
group.members
.map(
(member) => PlayerGroupTableCompanion.insert(
playerId: member.id,
groupId: group.id,
),
)
.toList(),
),
);
await Future.wait(
group.members.map((player) => db.playerDao.addPlayer(player: player)),
);
});
}
/// Deletes the group with the given [id] from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteGroup(String id) async {
final query = (delete(groupTable)..where((g) => g.id.equals(id)));
Future<bool> deleteGroup({required String groupId}) async {
final query = (delete(groupTable)..where((g) => g.id.equals(groupId)));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Updates the name of the group with the given [id] to [newName].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateGroupname(String id, String newName) async {
Future<bool> updateGroupname({
required String groupId,
required String newName,
}) async {
final rowsAffected =
await (update(groupTable)..where((g) => g.id.equals(id))).write(
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
GroupTableCompanion(name: Value(newName)),
);
return rowsAffected > 0;
@@ -65,4 +86,12 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
.getSingle();
return count ?? 0;
}
/// Checks if a group with the given [groupId] exists in the database.
/// Returns `true` if the group exists, `false` otherwise.
Future<bool> groupExists({required String groupId}) async {
final query = select(groupTable)..where((g) => g.id.equals(groupId));
final result = await query.getSingleOrNull();
return result != null;
}
}

View File

@@ -0,0 +1,42 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/group_game_table.dart';
import 'package:game_tracker/data/dto/group.dart';
part 'group_game_dao.g.dart';
@DriftAccessor(tables: [GroupGameTable])
class GroupGameDao extends DatabaseAccessor<AppDatabase>
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<bool> 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;
}
flixcoo marked this conversation as resolved
Review

doku vergessen

doku vergessen
/// Retrieves the [Group] associated with the given [gameId].
Future<Group> getGroupByGameId({required String gameId}) async {
final result = await (select(
groupGameTable,
)..where((g) => g.gameId.equals(gameId))).getSingle();
final group = await db.groupDao.getGroupById(groupId: result.groupId);
return group;
}
/// Associates a group with a game by inserting a record into the
/// [GroupGameTable].
Future<void> addGroupToGame(String gameId, String groupId) async {
await into(
groupGameTable,
).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId));
}
}

View File

@@ -0,0 +1,11 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'group_game_dao.dart';
// ignore_for_file: type=lint
mixin _$GroupGameDaoMixin on DatabaseAccessor<AppDatabase> {
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable;
}

View File

@@ -17,39 +17,57 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
}
/// Retrieves a [Player] by their [id].
Future<Player> getPlayerById(String id) async {
final query = select(playerTable)..where((p) => p.id.equals(id));
Future<Player> getPlayerById({required String playerId}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
final result = await query.getSingle();
return Player(id: result.id, name: result.name);
}
/// Adds a new [player] to the database.
Future<void> addPlayer(Player player) async {
await into(
playerTable,
).insert(PlayerTableCompanion.insert(id: player.id, name: player.name));
/// If a player with the same ID already exists, updates their name to
/// the new one.
Future<void> addPlayer({required Player player}) async {
if (!await playerExists(playerId: player.id)) {
await into(
playerTable,
).insert(PlayerTableCompanion.insert(id: player.id, name: player.name));
} else {
await updatePlayername(playerId: player.id, newName: player.name);
}
}
/// 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<bool> deletePlayer(String id) async {
final query = delete(playerTable)..where((p) => p.id.equals(id));
Future<bool> deletePlayer({required String playerId}) async {
final query = delete(playerTable)..where((p) => p.id.equals(playerId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Checks if a player with the given [id] exists in the database.
/// Returns `true` if the player exists, `false` otherwise.
Future<bool> playerExists(String id) async {
final query = select(playerTable)..where((p) => p.id.equals(id));
Future<bool> playerExists({required String playerId}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
final result = await query.getSingleOrNull();
return result != null;
}
/// Updates the name of the player with the given [id] to [newName].
Future<void> updatePlayername(String id, String newName) async {
await (update(playerTable)..where((p) => p.id.equals(id))).write(
/// Updates the name of the player with the given [playerId] to [newName].
Future<void> updatePlayername({
required String playerId,
required String newName,
}) async {
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
PlayerTableCompanion(name: Value(newName)),
);
}
/// Retrieves the total count of players in the database.
Future<int> getPlayerCount() async {
final count =
await (selectOnly(playerTable)..addColumns([playerTable.id.count()]))
.map((row) => row.read(playerTable.id.count()))
.getSingle();
return count ?? 0;
}
}

View File

@@ -0,0 +1,51 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/player_game_table.dart';
import 'package:game_tracker/data/dto/player.dart';
part 'player_game_dao.g.dart';
@DriftAccessor(tables: [PlayerGameTable])
class PlayerGameDao extends DatabaseAccessor<AppDatabase>
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<bool> gameHasPlayers({required String gameId}) async {
final count =
flixcoo marked this conversation as resolved
Review

Finde den namen hasGamePlayers schlecht, besser gameHasPlayers

Finde den namen hasGamePlayers schlecht, besser `gameHasPlayers`
await (selectOnly(playerGameTable)
..where(playerGameTable.gameId.equals(gameId))
..addColumns([playerGameTable.playerId.count()]))
.map((row) => row.read(playerGameTable.playerId.count()))
.getSingle();
return (count ?? 0) > 0;
}
/// Retrieves a list of [Player]s associated with the given [gameId].
/// Returns an empty list if no players are found.
Future<List<Player>> getPlayersByGameId({required String gameId}) async {
final result = await (select(
playerGameTable,
)..where((p) => p.gameId.equals(gameId))).get();
if (result.isEmpty) return <Player>[];
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
final players = await Future.wait(futures);
return players.whereType<Player>().toList();
}
/// Associates a player with a game by inserting a record into the
/// [PlayerGameTable].
Future<void> addPlayerToGame({
required String gameId,
required String playerId,
}) async {
await into(playerGameTable).insert(
PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId),
);
}
}

View File

@@ -0,0 +1,10 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'player_game_dao.dart';
// ignore_for_file: type=lint
mixin _$PlayerGameDaoMixin on DatabaseAccessor<AppDatabase> {
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable;
}

View File

@@ -11,34 +11,65 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
PlayerGroupDao(super.db);
/// Retrieves all players belonging to a specific group by [groupId].
Future<List<Player>> getPlayersOfGroupById(String groupId) async {
Future<List<Player>> getPlayersOfGroupById({required String groupId}) async {
final query = select(playerGroupTable)
..where((pG) => pG.groupId.equals(groupId));
final result = await query.get();
List<Player> groupMembers = [];
List<Player> groupMembers = List.empty(growable: true);
for (var entry in result) {
final player = await db.playerDao.getPlayerById(entry.userId);
final player = await db.playerDao.getPlayerById(playerId: entry.playerId);
groupMembers.add(player);
}
return groupMembers;
}
/// Removes a player from a group based on [userId] and [groupId].
/// Removes a player from a group based on [playerId] and [groupId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removePlayerFromGroup(String userId, String groupId) async {
Future<bool> removePlayerFromGroup({
required String playerId,
required String groupId,
}) async {
final query = delete(playerGroupTable)
..where((p) => p.userId.equals(userId) & p.groupId.equals(groupId));
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Adds a player to a group with the given [userId] and [groupId].
Future<void> addPlayerToGroup(String userId, String groupId) async {
/// 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<bool> 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(userId: userId, groupId: groupId),
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<bool> isPlayerInGroup({
required String playerId,
required String groupId,
}) async {
final query = select(playerGroupTable)
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
final result = await query.getSingleOrNull();
return result != null;
}
}

View File

@@ -2,10 +2,14 @@ import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:game_tracker/data/dao/game_dao.dart';
import 'package:game_tracker/data/dao/group_dao.dart';
import 'package:game_tracker/data/dao/group_game_dao.dart';
import 'package:game_tracker/data/dao/player_dao.dart';
import 'package:game_tracker/data/dao/player_game_dao.dart';
import 'package:game_tracker/data/dao/player_group_dao.dart';
import 'package:game_tracker/data/db/tables/game_table.dart';
import 'package:game_tracker/data/db/tables/group_game_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/db/tables/player_game_table.dart';
import 'package:game_tracker/data/db/tables/player_group_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
import 'package:path_provider/path_provider.dart';
@@ -13,8 +17,22 @@ import 'package:path_provider/path_provider.dart';
part 'database.g.dart';
@DriftDatabase(
tables: [PlayerTable, GroupTable, PlayerGroupTable, GameTable],
daos: [GroupDao, PlayerDao, PlayerGroupDao, GameDao],
tables: [
PlayerTable,
GroupTable,
GameTable,
PlayerGroupTable,
PlayerGameTable,
GroupGameTable,
],
daos: [
PlayerDao,
GroupDao,
GameDao,
PlayerGroupDao,
PlayerGameDao,
GroupGameDao,
],
)
class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,11 @@
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)();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -0,0 +1,13 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/game_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
class GroupGameTable extends Table {
TextColumn get groupId =>
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
flixcoo marked this conversation as resolved
Review

Müsste PlayerTable nicht GameTable sein? ist ja die Verbindungstabelle von group und game

Müsste PlayerTable nicht GameTable sein? ist ja die Verbindungstabelle von group und game
TextColumn get gameId =>
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
@override
Set<Column<Object>> get primaryKey => {groupId, gameId};
}

View File

@@ -0,0 +1,13 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/game_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
class PlayerGameTable extends Table {
TextColumn get playerId =>
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get gameId =>
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
@override
Set<Column<Object>> get primaryKey => {playerId, gameId};
}

View File

@@ -3,9 +3,11 @@ import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
class PlayerGroupTable extends Table {
TextColumn get userId => text().references(PlayerTable, #id)();
TextColumn get groupId => text().references(GroupTable, #id)();
TextColumn get playerId =>
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get groupId =>
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
@override
Set<Column<Object>> get primaryKey => {userId, groupId};
Set<Column<Object>> get primaryKey => {playerId, groupId};
}

View File

@@ -1,6 +1,23 @@
import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart';
class Game {
final String id;
final String name;
final List<Player>? players;
final Group? group;
final String winner;
Game({required this.id, required this.name});
Game({
this.players,
this.group,
this.winner = '',
required this.id,
required this.name,
});
@override
String toString() {
return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
}
}

View File

@@ -6,4 +6,9 @@ class Group {
final List<Player> members;
Group({required this.id, required this.name, required this.members});
@override
String toString() {
return 'Group{id: $id, name: $name,members: $members}';
}
}

View File

@@ -3,4 +3,9 @@ class Player {
final String name;
Player({required this.id, required this.name});
@override
String toString() {
return 'Player{id: $id,name: $name}';
}
}

View File

@@ -8,14 +8,14 @@ void main() {
runApp(
Provider<AppDatabase>(
create: (context) => AppDatabase(),
child: const MyApp(),
child: const GameTracker(),
dispose: (context, db) => db.close(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
class GameTracker extends StatelessWidget {
const GameTracker({super.key});
@override
Widget build(BuildContext context) {

View File

@@ -0,0 +1,109 @@
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 Group testgroup;
late Game testgame;
setUp(() {
database = AppDatabase(
DatabaseConnection(
NativeDatabase.memory(),
// Recommended for widget tests to avoid test errors.
closeStreamsSynchronously: true,
),
);
player1 = Player(id: 'p1', name: 'Alice');
player2 = Player(id: 'p2', name: 'Bob');
player3 = Player(id: 'p3', name: 'Charlie');
player4 = Player(id: 'p4', name: 'Diana');
testgroup = Group(
id: 'gr1',
name: 'Test Group',
members: [player1, player2, player3],
);
testgame = Game(
id: 'ga1',
name: 'Test Game',
group: testgroup,
players: [player4],
);
});
tearDown(() async {
await database.close();
});
group('game tests', () {
test('game is added correctly', () async {
await database.gameDao.addGame(game: testgame);
final result = await database.gameDao.getGameById(gameId: testgame.id);
expect(result.id, testgame.id);
expect(result.name, testgame.name);
expect(result.winner, testgame.winner);
if (result.group != null) {
expect(result.group!.members.length, testgroup.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);
}
} else {
fail('Group is null');
}
if (result.players != null) {
expect(result.players!.length, testgame.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);
}
} else {
fail('Players is null');
}
});
test('game is deleted correctly', () async {
await database.gameDao.addGame(game: testgame);
final gameDeleted = await database.gameDao.deleteGame(
gameId: testgame.id,
);
expect(gameDeleted, true);
final gameExists = await database.gameDao.gameExists(gameId: testgame.id);
expect(gameExists, false);
});
test('get game count works correctly', () async {
final initialCount = await database.gameDao.getGameCount();
expect(initialCount, 0);
await database.gameDao.addGame(game: testgame);
final gameAdded = await database.gameDao.getGameCount();
expect(gameAdded, 1);
final gameRemoved = await database.gameDao.deleteGame(
gameId: testgame.id,
);
expect(gameRemoved, true);
final finalCount = await database.gameDao.getGameCount();
expect(finalCount, 0);
});
});
}

View File

@@ -0,0 +1,148 @@
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;
setUp(() {
database = AppDatabase(
DatabaseConnection(
NativeDatabase.memory(),
// Recommended for widget tests to avoid test errors.
closeStreamsSynchronously: true,
),
);
player1 = Player(id: 'p1', name: 'Alice');
player2 = Player(id: 'p2', name: 'Bob');
player3 = Player(id: 'p3', name: 'Charlie');
player4 = Player(id: 'p4', name: 'Diana');
testgroup = Group(
id: 'gr1',
name: 'Test Group',
members: [player1, player2, player3],
);
});
tearDown(() async {
await database.close();
});
test('group and group members gets added correctly', () async {
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.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);
}
});
test('group gets deleted correctly', () async {
await database.groupDao.addGroup(group: testgroup);
final groupDeleted = await database.groupDao.deleteGroup(
groupId: testgroup.id,
);
expect(groupDeleted, true);
final groupExists = await database.groupDao.groupExists(
groupId: testgroup.id,
);
expect(groupExists, false);
});
test('group name gets updated correcly ', () async {
await database.groupDao.addGroup(group: testgroup);
const newGroupName = 'new group name';
await database.groupDao.updateGroupname(
groupId: testgroup.id,
newName: newGroupName,
);
final result = await database.groupDao.getGroupById(groupId: testgroup.id);
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 playerAdded2 = await database.playerGroupDao.isPlayerInGroup(
playerId: 'a',
groupId: testgroup.id,
);
expect(playerAdded2, false);
expect(playerAdded, 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);
});
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('get group count works correctly', () async {
final initialCount = await database.groupDao.getGroupCount();
expect(initialCount, 0);
await database.groupDao.addGroup(group: testgroup);
final groupAdded = await database.groupDao.getGroupCount();
expect(groupAdded, 1);
final groupRemoved = await database.groupDao.deleteGroup(
groupId: testgroup.id,
);
expect(groupRemoved, true);
final finalCount = await database.groupDao.getGroupCount();
expect(finalCount, 0);
});
}

View File

@@ -0,0 +1,84 @@
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/player.dart';
void main() {
late AppDatabase database;
late Player testPlayer;
setUp(() {
database = AppDatabase(
DatabaseConnection(
NativeDatabase.memory(),
// Recommended for widget tests to avoid test errors.
closeStreamsSynchronously: true,
),
);
testPlayer = Player(id: 'test_id', name: 'Test Player');
});
tearDown(() async {
await database.close();
});
group('player tests', () {
test('players get inserted correcly ', () async {
await database.playerDao.addPlayer(player: testPlayer);
final result = await database.playerDao.getPlayerById(
playerId: testPlayer.id,
);
expect(result.id, testPlayer.id);
expect(result.name, testPlayer.name);
});
test('players get deleted correcly ', () async {
await database.playerDao.addPlayer(player: testPlayer);
final playerDeleted = await database.playerDao.deletePlayer(
playerId: testPlayer.id,
);
expect(playerDeleted, true);
final playerExists = await database.playerDao.playerExists(
playerId: testPlayer.id,
);
expect(playerExists, false);
});
test('player name gets updated correcly ', () async {
await database.playerDao.addPlayer(player: testPlayer);
const newPlayerName = 'new player name';
await database.playerDao.updatePlayername(
playerId: testPlayer.id,
newName: newPlayerName,
);
final result = await database.playerDao.getPlayerById(
playerId: testPlayer.id,
);
expect(result.name, newPlayerName);
});
test('get player count works correctly', () async {
final initialCount = await database.playerDao.getPlayerCount();
expect(initialCount, 0);
await database.playerDao.addPlayer(player: testPlayer);
final playerAdded = await database.playerDao.getPlayerCount();
expect(playerAdded, 1);
final playerRemoved = await database.playerDao.deletePlayer(
playerId: testPlayer.id,
);
expect(playerRemoved, true);
final finalCount = await database.playerDao.getPlayerCount();
expect(finalCount, 0);
});
});
}

View File

@@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:game_tracker/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}