Neue Datenbank Struktur #156

Open
gelbeinhalb wants to merge 88 commits from feature/88-neue-datenbank-struktur into development
15 changed files with 94 additions and 40 deletions
Showing only changes of commit b0cb385756 - Show all commits

View File

@@ -29,24 +29,27 @@ enum ImportResult {
/// - [ExportResult.unknownException]: An exception occurred during export. /// - [ExportResult.unknownException]: An exception occurred during export.
enum ExportResult { success, canceled, unknownException } enum ExportResult { success, canceled, unknownException }
/// Different rulesets available for matches /// Different rulesets available for games
/// - [Ruleset.singleWinner]: The match is won by a single player /// - [Ruleset.highestScore]: The player with the highest score wins.
/// - [Ruleset.singleLoser]: The match is lost by a single player /// - [Ruleset.lowestScore]: The player with the lowest score wins.
/// - [Ruleset.mostPoints]: The player with the most points wins. /// - [Ruleset.singleWinner]: The match is won by a single player.
/// - [Ruleset.leastPoints]: The player with the fewest points wins. /// - [Ruleset.singleLoser]: The match has a single loser.
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } /// - [Ruleset.multipleWinners]: Multiple players can be winners.
enum Ruleset { highestScore, lowestScore, singleWinner, singleLoser, multipleWinners }
/// Translates a [Ruleset] enum value to its corresponding localized string. /// Translates a [Ruleset] enum value to its corresponding localized string.
String translateRulesetToString(Ruleset ruleset, BuildContext context) { String translateRulesetToString(Ruleset ruleset, BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
switch (ruleset) { switch (ruleset) {
case Ruleset.highestScore:
return loc.highest_score;
case Ruleset.lowestScore:
return loc.lowest_score;
case Ruleset.singleWinner: case Ruleset.singleWinner:
return loc.single_winner; return loc.single_winner;
case Ruleset.singleLoser: case Ruleset.singleLoser:
return loc.single_loser; return loc.single_loser;
case Ruleset.mostPoints: case Ruleset.multipleWinners:
return loc.most_points; return loc.multiple_winners;
case Ruleset.leastPoints:
return loc.least_points;
} }
} }

View File

@@ -2,6 +2,7 @@ import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/game_table.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/game.dart';
import 'package:game_tracker/core/enums.dart';
part 'game_dao.g.dart'; part 'game_dao.g.dart';
@@ -18,7 +19,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
(row) => Game( (row) => Game(
id: row.id, id: row.id,
name: row.name, name: row.name,
ruleset: row.ruleset, ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
description: row.description, description: row.description,
color: row.color, color: row.color,
icon: row.icon, icon: row.icon,
@@ -35,7 +36,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
return Game( return Game(
id: result.id, id: result.id,
name: result.name, name: result.name,
ruleset: result.ruleset, ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
flixcoo marked this conversation as resolved
Review

Funktioniert das tatsächlich? Ist das getestet, dass dann auch ein ruleset (color) gefunden wird wenn du den enum speicherst?

Funktioniert das tatsächlich? Ist das getestet, dass dann auch ein ruleset (color) gefunden wird wenn du den enum speicherst?
Review

Ja glaube das hat funktioniert

Ja glaube das hat funktioniert
description: result.description, description: result.description,
color: result.color, color: result.color,
icon: result.icon, icon: result.icon,
@@ -52,7 +53,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
GameTableCompanion.insert( GameTableCompanion.insert(
id: game.id, id: game.id,
name: game.name, name: game.name,
ruleset: game.ruleset ?? '', ruleset: game.ruleset.name,
description: game.description, description: game.description,
color: game.color, color: game.color,
icon: Value(game.icon), icon: Value(game.icon),
@@ -78,7 +79,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
(game) => GameTableCompanion.insert( (game) => GameTableCompanion.insert(
id: game.id, id: game.id,
name: game.name, name: game.name,
ruleset: game.ruleset ?? '', ruleset: game.ruleset.name,
description: game.description, description: game.description,
color: game.color, color: game.color,
icon: Value(game.icon), icon: Value(game.icon),
@@ -122,10 +123,10 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
/// Updates the ruleset of the game with the given [gameId]. /// Updates the ruleset of the game with the given [gameId].
Future<void> updateGameRuleset({ Future<void> updateGameRuleset({
required String gameId, required String gameId,
required String newRuleset, required Ruleset newRuleset,
}) async { }) async {
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
GameTableCompanion(ruleset: Value(newRuleset)), GameTableCompanion(ruleset: Value(newRuleset.name)),
); );
} }

View File

@@ -127,7 +127,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
(game) => GameTableCompanion.insert( (game) => GameTableCompanion.insert(
id: game.id, id: game.id,
name: game.name, name: game.name,
ruleset: game.ruleset ?? '', ruleset: game.ruleset.name,
description: game.description, description: game.description,
color: game.color, color: game.color,
icon: Value(game.icon), icon: Value(game.icon),

View File

@@ -1,11 +1,12 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:game_tracker/core/enums.dart';
class Game { class Game {
final String id; final String id;
final DateTime createdAt; final DateTime createdAt;
final String name; final String name;
gelbeinhalb marked this conversation as resolved
Review

Ruleset sollte nicht optional, weil das immer gebraucht wird
Ruleset als enum

Ruleset sollte nicht optional, weil das immer gebraucht wird Ruleset als enum
final String? ruleset; final Ruleset ruleset;
gelbeinhalb marked this conversation as resolved Outdated

Description als leeren string setzen, wenn nicht übergeben

Description als leeren string setzen, wenn nicht übergeben

warum findest du leeren string besser als nullable?

warum findest du leeren string besser als nullable?

Finde null sollte man nur dann verwenden, wenns halt nicht anders geht, wie z.B. bei objekten

Finde null sollte man nur dann verwenden, wenns halt nicht anders geht, wie z.B. bei objekten
final String description; final String description;
gelbeinhalb marked this conversation as resolved Outdated

Hier lieber enum, gerne auch mit einem Color.none

Hier lieber enum, gerne auch mit einem `Color.none`

warum aber nicht hex codes?

warum aber nicht hex codes?

oder willst du die farben vorgeben?

oder willst du die farben vorgeben?

Ja man könnte auch hexcodes machen. Würde die Farben so oder so vorgeben unabhängig davon wie man sie speichert

Ja man könnte auch hexcodes machen. Würde die Farben so oder so vorgeben unabhängig davon wie man sie speichert

würde enums maybe machen, weil man sonst im export einfach den hex code ändern könnte. Also idk ob das juckt aber 🤷‍♂️

würde enums maybe machen, weil man sonst im export einfach den hex code ändern könnte. Also idk ob das juckt aber 🤷‍♂️
final String color; final String color;
gelbeinhalb marked this conversation as resolved Outdated

Hier auch mit enum arbeiten?

Hier auch mit enum arbeiten?
final String? icon; final String? icon;
@@ -14,7 +15,7 @@ class Game {
String? id, String? id,
DateTime? createdAt, DateTime? createdAt,
required this.name, required this.name,
this.ruleset, required this.ruleset,
required this.description, required this.description,
Review

Description sollte nicht obligatorisch sein, sondern optional. Wenn nicht gesetzt soll es ein leerer String werden.

Description sollte nicht obligatorisch sein, sondern optional. Wenn nicht gesetzt soll es ein leerer String werden.
Review

Dachte man soll den als leeren String selber setzen müssen

Dachte man soll den als leeren String selber setzen müssen
Review

würde das lieber so machen, weil man ja sonst immer unnötig die description setzten muss

würde das lieber so machen, weil man ja sonst immer unnötig die description setzten muss
Review

finde auch optional

finde auch optional
required this.color, required this.color,
this.icon, this.icon,
@@ -31,7 +32,7 @@ class Game {
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
name = json['name'], name = json['name'],
ruleset = json['ruleset'], ruleset = Ruleset.values.firstWhere((e) => e.name == json['ruleset']),
description = json['description'], description = json['description'],
color = json['color'], color = json['color'],
icon = json['icon']; icon = json['icon'];
@@ -41,7 +42,7 @@ class Game {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),
'name': name, 'name': name,
'ruleset': ruleset, 'ruleset': ruleset.name,
'description': description, 'description': description,
'color': color, 'color': color,
'icon': icon, 'icon': icon,

View File

@@ -82,6 +82,9 @@
"settings": "Einstellungen", "settings": "Einstellungen",
"single_loser": "Ein:e Verlierer:in", "single_loser": "Ein:e Verlierer:in",
"single_winner": "Ein:e Gewinner:in", "single_winner": "Ein:e Gewinner:in",
"highest_score": "Höchste Punkte",
"lowest_score": "Niedrigste Punkte",
"multiple_winners": "Mehrere Gewinner:innen",
"statistics": "Statistiken", "statistics": "Statistiken",
"stats": "Statistiken", "stats": "Statistiken",
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",

View File

@@ -380,6 +380,9 @@
"settings": "Settings", "settings": "Settings",
"single_loser": "Single Loser", "single_loser": "Single Loser",
"single_winner": "Single Winner", "single_winner": "Single Winner",
"highest_score": "Highest Score",
"lowest_score": "Lowest Score",
"multiple_winners": "Multiple Winners",
"statistics": "Statistics", "statistics": "Statistics",
"stats": "Stats", "stats": "Stats",
"successfully_added_player": "Successfully added player {playerName}", "successfully_added_player": "Successfully added player {playerName}",

View File

@@ -590,6 +590,24 @@ abstract class AppLocalizations {
/// **'Single Winner'** /// **'Single Winner'**
String get single_winner; String get single_winner;
/// No description provided for @highest_score.
///
/// In en, this message translates to:
/// **'Highest Score'**
String get highest_score;
/// No description provided for @lowest_score.
///
/// In en, this message translates to:
/// **'Lowest Score'**
String get lowest_score;
/// No description provided for @multiple_winners.
///
/// In en, this message translates to:
/// **'Multiple Winners'**
String get multiple_winners;
/// Statistics tab label /// Statistics tab label
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -266,6 +266,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get single_winner => 'Ein:e Gewinner:in'; String get single_winner => 'Ein:e Gewinner:in';
@override
String get highest_score => 'Höchste Punkte';
@override
String get lowest_score => 'Niedrigste Punkte';
@override
String get multiple_winners => 'Mehrere Gewinner:innen';
@override @override
String get statistics => 'Statistiken'; String get statistics => 'Statistiken';

View File

@@ -266,6 +266,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get single_winner => 'Single Winner'; String get single_winner => 'Single Winner';
@override
String get highest_score => 'Highest Score';
@override
String get lowest_score => 'Lowest Score';
@override
String get multiple_winners => 'Multiple Winners';
@override @override
String get statistics => 'Statistics'; String get statistics => 'Statistics';

View File

@@ -100,7 +100,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
} }
List<(String, String, Ruleset)> games = [ List<(String, String, Ruleset)> games = [
('Example Game 1', 'This is a description', Ruleset.leastPoints), ('Example Game 1', 'This is a description', Ruleset.lowestScore),
('Example Game 2', '', Ruleset.singleWinner), ('Example Game 2', '', Ruleset.singleWinner),
]; ];
@@ -201,7 +201,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
gameToUse = Game( gameToUse = Game(
name: selectedGame.$1, name: selectedGame.$1,
description: selectedGame.$2, description: selectedGame.$2,
ruleset: selectedGame.$3.name, ruleset: selectedGame.$3,
color: '0xFF000000', color: '0xFF000000',
); );
} else { } else {
@@ -210,7 +210,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
gameToUse = Game( gameToUse = Game(
name: selectedGame.$1, name: selectedGame.$1,
description: selectedGame.$2, description: selectedGame.$2,
ruleset: selectedGame.$3.name, ruleset: selectedGame.$3,
color: '0xFF000000', color: '0xFF000000',
); );
} }

View File

@@ -4,6 +4,7 @@ import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -24,7 +25,7 @@ void main() {
withClock(fakeClock, () { withClock(fakeClock, () {
testGame1 = Game( testGame1 = Game(
name: 'Chess', name: 'Chess',
ruleset: 'winner.single', ruleset: Ruleset.singleWinner,
description: 'A classic strategy game', description: 'A classic strategy game',
color: '0xFF0000FF', color: '0xFF0000FF',
icon: 'chess_icon', icon: 'chess_icon',
@@ -32,14 +33,15 @@ void main() {
testGame2 = Game( testGame2 = Game(
id: 'game2', id: 'game2',
name: 'Poker', name: 'Poker',
ruleset: 'Texas Hold\'em rules', ruleset: Ruleset.multipleWinners,
description: 'winner.multiple', description: 'Card game with multiple winners',
color: '0xFFFF0000', color: '0xFFFF0000',
icon: 'poker_icon', icon: 'poker_icon',
); );
testGame3 = Game( testGame3 = Game(
id: 'game3', id: 'game3',
name: 'Monopoly', name: 'Monopoly',
ruleset: Ruleset.highestScore,
description: 'A board game about real estate', description: 'A board game about real estate',
color: '0xFF000000', color: '0xFF000000',
); );
@@ -131,7 +133,7 @@ void main() {
// Verifies that a game with null optional fields can be added and retrieved. // Verifies that a game with null optional fields can be added and retrieved.
test('addGame handles game with null optional fields', () async { test('addGame handles game with null optional fields', () async {
final gameWithNulls = Game(name: 'Simple Game', description: 'A simple game', color: '0xFF000000'); final gameWithNulls = Game(name: 'Simple Game', ruleset: Ruleset.lowestScore, description: 'A simple game', color: '0xFF000000');
final result = await database.gameDao.addGame(game: gameWithNulls); final result = await database.gameDao.addGame(game: gameWithNulls);
expect(result, true); expect(result, true);
@@ -269,13 +271,13 @@ void main() {
await database.gameDao.updateGameRuleset( await database.gameDao.updateGameRuleset(
gameId: testGame1.id, gameId: testGame1.id,
newRuleset: 'New ruleset for chess', newRuleset: Ruleset.highestScore,
); );
final updatedGame = await database.gameDao.getGameById( final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id, gameId: testGame1.id,
); );
expect(updatedGame.ruleset, 'New ruleset for chess'); expect(updatedGame.ruleset, Ruleset.highestScore);
expect(updatedGame.name, testGame1.name); expect(updatedGame.name, testGame1.name);
}); });
@@ -283,7 +285,7 @@ void main() {
test('updateGameRuleset does nothing for non-existent game', () async { test('updateGameRuleset does nothing for non-existent game', () async {
await database.gameDao.updateGameRuleset( await database.gameDao.updateGameRuleset(
gameId: 'non-existent-id', gameId: 'non-existent-id',
newRuleset: 'New Ruleset', newRuleset: Ruleset.lowestScore,
); );
final allGames = await database.gameDao.getAllGames(); final allGames = await database.gameDao.getAllGames();
@@ -455,6 +457,7 @@ void main() {
test('Game with special characters in name is stored correctly', () async { test('Game with special characters in name is stored correctly', () async {
final specialGame = Game( final specialGame = Game(
name: 'Game\'s & "Special" <Name>', name: 'Game\'s & "Special" <Name>',
ruleset: Ruleset.multipleWinners,
description: 'Description with émojis 🎮🎲', description: 'Description with émojis 🎮🎲',
color: '0xFF000000', color: '0xFF000000',
); );
@@ -471,7 +474,7 @@ void main() {
test('Game with empty string fields is stored correctly', () async { test('Game with empty string fields is stored correctly', () async {
final emptyGame = Game( final emptyGame = Game(
name: '', name: '',
ruleset: '', ruleset: Ruleset.singleWinner,
description: '', description: '',
icon: '', icon: '',
color: '0xFF000000', color: '0xFF000000',
@@ -482,7 +485,7 @@ void main() {
gameId: emptyGame.id, gameId: emptyGame.id,
); );
expect(fetchedGame.name, ''); expect(fetchedGame.name, '');
expect(fetchedGame.ruleset, ''); expect(fetchedGame.ruleset, Ruleset.singleWinner);
expect(fetchedGame.description, ''); expect(fetchedGame.description, '');
expect(fetchedGame.icon, ''); expect(fetchedGame.icon, '');
}); });
@@ -493,7 +496,7 @@ void main() {
final longGame = Game( final longGame = Game(
name: longString, name: longString,
description: longString, description: longString,
ruleset: longString, ruleset: Ruleset.multipleWinners,
color: '0xFF000000', color: '0xFF000000',
); );
await database.gameDao.addGame(game: longGame); await database.gameDao.addGame(game: longGame);
@@ -503,7 +506,7 @@ void main() {
); );
expect(fetchedGame.name.length, 10000); expect(fetchedGame.name.length, 10000);
expect(fetchedGame.description.length, 10000); expect(fetchedGame.description.length, 10000);
expect(fetchedGame.ruleset?.length, 10000); expect(fetchedGame.ruleset, Ruleset.multipleWinners);
}); });
// Verifies that multiple sequential updates to the same game work correctly. // Verifies that multiple sequential updates to the same game work correctly.

View File

@@ -7,6 +7,7 @@ import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -48,7 +49,7 @@ void main() {
name: 'Test Group 2', name: 'Test Group 2',
members: [testPlayer4, testPlayer5], members: [testPlayer4, testPlayer5],
); );
testGame = Game(name: 'Test Game', description: 'A test game', color: '0xFF000000'); testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: '0xFF000000');
testMatch1 = Match( testMatch1 = Match(
name: 'First Test Match', name: 'First Test Match',
game: testGame, game: testGame,

View File

@@ -8,6 +8,7 @@ import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/data/dto/team.dart'; import 'package:game_tracker/data/dto/team.dart';
import 'package:game_tracker/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -46,7 +47,7 @@ void main() {
name: 'Test Group', name: 'Test Group',
members: [testPlayer1, testPlayer2, testPlayer3], members: [testPlayer1, testPlayer2, testPlayer3],
); );
testGame = Game(name: 'Test Game', description: 'A test game', color: '0xFF000000'); testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: '0xFF000000');
testMatchOnlyGroup = Match( testMatchOnlyGroup = Match(
name: 'Test Match with Group', name: 'Test Match with Group',
game: testGame, game: testGame,

View File

@@ -6,6 +6,7 @@ import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -31,7 +32,7 @@ void main() {
testPlayer1 = Player(name: 'Alice'); testPlayer1 = Player(name: 'Alice');
testPlayer2 = Player(name: 'Bob'); testPlayer2 = Player(name: 'Bob');
testPlayer3 = Player(name: 'Charlie'); testPlayer3 = Player(name: 'Charlie');
testGame = Game(name: 'Test Game', description: 'A test game', color: '0xFF000000'); testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: '0xFF000000');
testMatch1 = Match( testMatch1 = Match(
name: 'Test Match 1', name: 'Test Match 1',
game: testGame, game: testGame,

View File

@@ -7,6 +7,7 @@ import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/data/dto/team.dart'; import 'package:game_tracker/data/dto/team.dart';
import 'package:game_tracker/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -48,8 +49,8 @@ void main() {
name: 'Team Gamma', name: 'Team Gamma',
members: [testPlayer1, testPlayer3], members: [testPlayer1, testPlayer3],
); );
testGame1 = Game(name: 'Game 1', description: 'Test game 1', color: '0xFF000000'); testGame1 = Game(name: 'Game 1', ruleset: Ruleset.singleWinner, description: 'Test game 1', color: '0xFF000000');
testGame2 = Game(name: 'Game 2', description: 'Test game 2', color: '0xFF000000'); testGame2 = Game(name: 'Game 2', ruleset: Ruleset.highestScore, description: 'Test game 2', color: '0xFF000000');
}); });
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(