19 Commits

Author SHA1 Message Date
gelbeinhalb
8bd53a69c3 add soft delete parameter 2026-04-19 12:39:14 +02:00
36fda30f27 Updated licenses [skip ci] 2026-04-14 16:13:31 +00:00
1ab869ec26 Updated version number [skip ci] 2026-04-14 16:12:55 +00:00
52b78e44e4 Merge pull request 'Score implementation ergänzen' (#196) from feature/191-score-implementation-ergaenzen into development
All checks were successful
Push Pipeline / test (push) Successful in 42s
Push Pipeline / update_version (push) Successful in 5s
Push Pipeline / generate_licenses (push) Successful in 35s
Push Pipeline / format (push) Successful in 54s
Push Pipeline / build (push) Successful in 5m32s
Reviewed-on: #196
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
2026-04-14 16:12:05 +00:00
e827f4c527 Fixed references
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 42s
Pull Request Pipeline / lint (pull_request) Successful in 47s
2026-04-13 22:53:39 +02:00
73c85b1ff2 Renamed score dao
Some checks failed
Pull Request Pipeline / lint (pull_request) Failing after 44s
Pull Request Pipeline / test (pull_request) Failing after 1m33s
2026-04-13 22:49:30 +02:00
c43b7b478c Updated comments
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 44s
Pull Request Pipeline / lint (pull_request) Successful in 46s
2026-04-13 22:47:29 +02:00
c9188c222a Added edge cases for games, groups, teams and matches 2026-04-13 22:16:34 +02:00
fcca74cea5 Refactoring 2026-04-13 22:16:12 +02:00
80672343b9 Refactoring
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 41s
Pull Request Pipeline / lint (pull_request) Successful in 48s
2026-04-12 02:02:27 +02:00
d903a9fd7e Refactoring 2026-04-12 02:02:16 +02:00
bed8a05057 Refactoring 2026-04-12 02:01:26 +02:00
8a312152a5 Refactoring 2026-04-12 02:01:12 +02:00
eeb68496d5 Removed tester input
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 41s
Pull Request Pipeline / lint (pull_request) Successful in 45s
2026-04-12 01:42:47 +02:00
65704b4a03 Updated schema to add scores attribute
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 46s
Pull Request Pipeline / lint (pull_request) Successful in 49s
2026-04-12 01:42:02 +02:00
f40113ef2c Added restriction to schema 2026-04-12 01:37:31 +02:00
723699d363 Added test for json schema
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 44s
Pull Request Pipeline / lint (pull_request) Successful in 46s
2026-04-12 01:32:21 +02:00
0823a4ed41 Corrected type
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 42s
Pull Request Pipeline / lint (pull_request) Successful in 46s
2026-04-12 01:21:17 +02:00
22753d29c1 Refactored method, added tests for DTS
Some checks failed
Pull Request Pipeline / test (pull_request) Failing after 44s
Pull Request Pipeline / lint (pull_request) Successful in 45s
2026-04-12 01:20:13 +02:00
28 changed files with 1756 additions and 658 deletions

View File

@@ -20,6 +20,7 @@
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"createdAt",
@@ -55,6 +56,7 @@
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"createdAt",
@@ -90,6 +92,7 @@
}
}
},
"additionalProperties": false,
"required": [
"id",
"name",
@@ -120,6 +123,7 @@
}
}
},
"additionalProperties": false,
"required": [
"id",
"name",
@@ -157,10 +161,31 @@
"type": "string"
}
},
"scores": {
"type": "object",
"items": {
"type": "array",
"items": {
"type": "string",
"properties": {
"roundNumber": {
"type": "number"
},
"score": {
"type": "number"
},
"change": {
"type": "number"
}
}
}
}
},
"notes": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"name",
@@ -172,6 +197,7 @@
}
}
},
"additionalProperties": false,
"required": [
"players",
"games",

View File

@@ -30,9 +30,11 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
final scores = await db.scoreDao.getAllMatchScores(matchId: row.id);
final scores = await db.scoreEntryDao.getAllMatchScores(
matchId: row.id,
);
final winner = await db.scoreDao.getWinner(matchId: row.id);
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
return Match(
id: row.id,
name: row.name,
@@ -64,9 +66,9 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
final scores = await db.scoreDao.getAllMatchScores(matchId: matchId);
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
final winner = await db.scoreDao.getWinner(matchId: matchId);
final winner = await db.scoreEntryDao.getWinner(matchId: matchId);
return Match(
id: result.id,
@@ -109,15 +111,15 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
for (final pid in match.scores.keys) {
final playerScores = match.scores[pid]!;
await db.scoreDao.addScoresAsList(
scores: playerScores,
await db.scoreEntryDao.addScoresAsList(
entrys: playerScores,
playerId: pid,
matchId: match.id,
);
}
if (match.winner != null) {
await db.scoreDao.setWinner(
await db.scoreEntryDao.setWinner(
matchId: match.id,
playerId: match.winner!.id,
);
@@ -298,7 +300,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final group = await db.groupDao.getGroupById(groupId: groupId);
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
final winner = await db.scoreDao.getWinner(matchId: row.id);
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
return Match(
id: row.id,
name: row.name,

View File

@@ -2,45 +2,44 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/db/tables/score_table.dart';
import 'package:tallee/data/db/tables/score_entry_table.dart';
import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score.dart';
import 'package:tallee/data/models/score_entry.dart';
part 'score_dao.g.dart';
part 'score_entry_dao.g.dart';
@DriftAccessor(tables: [ScoreTable])
class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
ScoreDao(super.db);
@DriftAccessor(tables: [ScoreEntryTable])
class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
with _$ScoreEntryDaoMixin {
ScoreEntryDao(super.db);
/// Adds a score entry to the database.
Future<void> addScore({
required String playerId,
required String matchId,
required int score,
int change = 0,
int roundNumber = 0,
required ScoreEntry entry,
}) async {
await into(scoreTable).insert(
ScoreTableCompanion.insert(
await into(scoreEntryTable).insert(
ScoreEntryTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: roundNumber,
score: score,
change: change,
roundNumber: entry.roundNumber,
score: entry.score,
change: entry.change,
),
mode: InsertMode.insertOrReplace,
);
}
Future<void> addScoresAsList({
required List<Score> scores,
required List<ScoreEntry> entrys,
required String playerId,
required String matchId,
}) async {
if (scores.isEmpty) return;
final entries = scores
if (entrys.isEmpty) return;
final entries = entrys
.map(
(score) => ScoreTableCompanion.insert(
(score) => ScoreEntryTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: score.roundNumber,
@@ -51,17 +50,21 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
.toList();
await batch((batch) {
batch.insertAll(scoreTable, entries, mode: InsertMode.insertOrReplace);
batch.insertAll(
scoreEntryTable,
entries,
mode: InsertMode.insertOrReplace,
);
});
}
/// Retrieves the score for a specific round.
Future<Score?> getScore({
Future<ScoreEntry?> getScore({
required String playerId,
required String matchId,
int roundNumber = 0,
}) async {
final query = select(scoreTable)
final query = select(scoreEntryTable)
..where(
(s) =>
s.playerId.equals(playerId) &
@@ -72,7 +75,7 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
final result = await query.getSingleOrNull();
if (result == null) return null;
return Score(
return ScoreEntry(
roundNumber: result.roundNumber,
score: result.score,
change: result.change,
@@ -80,15 +83,16 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
}
/// Retrieves all scores for a specific match.
Future<Map<String, List<Score>>> getAllMatchScores({
Future<Map<String, List<ScoreEntry>>> getAllMatchScores({
required String matchId,
}) async {
final query = select(scoreTable)..where((s) => s.matchId.equals(matchId));
final query = select(scoreEntryTable)
..where((s) => s.matchId.equals(matchId));
final result = await query.get();
final Map<String, List<Score>> scoresByPlayer = {};
final Map<String, List<ScoreEntry>> scoresByPlayer = {};
for (final row in result) {
final score = Score(
final score = ScoreEntry(
roundNumber: row.roundNumber,
score: row.score,
change: row.change,
@@ -100,17 +104,17 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
}
/// Retrieves all scores for a specific player in a match.
Future<List<Score>> getAllPlayerScoresInMatch({
Future<List<ScoreEntry>> getAllPlayerScoresInMatch({
required String playerId,
required String matchId,
}) async {
final query = select(scoreTable)
final query = select(scoreEntryTable)
..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId))
..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]);
final result = await query.get();
return result
.map(
(row) => Score(
(row) => ScoreEntry(
roundNumber: row.roundNumber,
score: row.score,
change: row.change,
@@ -126,21 +130,19 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
Future<bool> updateScore({
required String playerId,
required String matchId,
required int newScore,
int newChange = 0,
int roundNumber = 0,
required ScoreEntry newEntry,
}) async {
final rowsAffected =
await (update(scoreTable)..where(
await (update(scoreEntryTable)..where(
(s) =>
s.playerId.equals(playerId) &
s.matchId.equals(matchId) &
s.roundNumber.equals(roundNumber),
s.roundNumber.equals(newEntry.roundNumber),
))
.write(
ScoreTableCompanion(
score: Value(newScore),
change: Value(newChange),
ScoreEntryTableCompanion(
score: Value(newEntry.score),
change: Value(newEntry.change),
),
);
return rowsAffected > 0;
@@ -152,7 +154,7 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
required String matchId,
int roundNumber = 0,
}) async {
final query = delete(scoreTable)
final query = delete(scoreEntryTable)
..where(
(s) =>
s.playerId.equals(playerId) &
@@ -164,7 +166,8 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
}
Future<bool> deleteAllScoresForMatch({required String matchId}) async {
final query = delete(scoreTable)..where((s) => s.matchId.equals(matchId));
final query = delete(scoreEntryTable)
..where((s) => s.matchId.equals(matchId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
@@ -173,7 +176,7 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
required String matchId,
required String playerId,
}) async {
final query = delete(scoreTable)
final query = delete(scoreEntryTable)
..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId));
final rowsAffected = await query.go();
return rowsAffected > 0;
@@ -182,11 +185,11 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
/// Gets the highest (latest) round number for a match.
/// Returns `null` if there are no scores for the match.
Future<int?> getLatestRoundNumber({required String matchId}) async {
final query = selectOnly(scoreTable)
..where(scoreTable.matchId.equals(matchId))
..addColumns([scoreTable.roundNumber.max()]);
final query = selectOnly(scoreEntryTable)
..where(scoreEntryTable.matchId.equals(matchId))
..addColumns([scoreEntryTable.roundNumber.max()]);
final result = await query.getSingle();
return result.read(scoreTable.roundNumber.max());
return result.read(scoreEntryTable.roundNumber.max());
}
/// Aggregates the total score for a player in a match by summing all their
@@ -218,8 +221,8 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
deleteAllScoresForMatch(matchId: matchId);
// Set the winner's score to 1
final rowsAffected = await into(scoreTable).insert(
ScoreTableCompanion.insert(
final rowsAffected = await into(scoreEntryTable).insert(
ScoreEntryTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: 0,
@@ -234,7 +237,7 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
// Retrieves the winner of a match based on the highest score.
Future<Player?> getWinner({required String matchId}) async {
final query = select(scoreTable)
final query = select(scoreEntryTable)
..where((s) => s.matchId.equals(matchId))
..orderBy([(s) => OrderingTerm.desc(s.score)])
..limit(1);
@@ -278,8 +281,8 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
deleteAllScoresForMatch(matchId: matchId);
// Set the loosers score to 0
final rowsAffected = await into(scoreTable).insert(
ScoreTableCompanion.insert(
final rowsAffected = await into(scoreEntryTable).insert(
ScoreEntryTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: 0,
@@ -294,7 +297,7 @@ class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
/// Retrieves the looser of a match based on the score 0.
Future<Player?> getLooser({required String matchId}) async {
final query = select(scoreTable)
final query = select(scoreEntryTable)
..where((s) => s.matchId.equals(matchId) & s.score.equals(0));
final result = await query.getSingleOrNull();

View File

@@ -1,20 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'score_dao.dart';
part of 'score_entry_dao.dart';
// ignore_for_file: type=lint
mixin _$ScoreDaoMixin on DatabaseAccessor<AppDatabase> {
mixin _$ScoreEntryDaoMixin on DatabaseAccessor<AppDatabase> {
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$ScoreTableTable get scoreTable => attachedDatabase.scoreTable;
ScoreDaoManager get managers => ScoreDaoManager(this);
$ScoreEntryTableTable get scoreEntryTable => attachedDatabase.scoreEntryTable;
ScoreEntryDaoManager get managers => ScoreEntryDaoManager(this);
}
class ScoreDaoManager {
final _$ScoreDaoMixin _db;
ScoreDaoManager(this._db);
class ScoreEntryDaoManager {
final _$ScoreEntryDaoMixin _db;
ScoreEntryDaoManager(this._db);
$$PlayerTableTableTableManager get playerTable =>
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
$$GameTableTableTableManager get gameTable =>
@@ -23,6 +23,9 @@ class ScoreDaoManager {
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
$$MatchTableTableTableManager get matchTable =>
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
$$ScoreTableTableTableManager get scoreTable =>
$$ScoreTableTableTableManager(_db.attachedDatabase, _db.scoreTable);
$$ScoreEntryTableTableTableManager get scoreEntryTable =>
$$ScoreEntryTableTableTableManager(
_db.attachedDatabase,
_db.scoreEntryTable,
);
}

View File

@@ -7,7 +7,7 @@ import 'package:tallee/data/dao/match_dao.dart';
import 'package:tallee/data/dao/player_dao.dart';
import 'package:tallee/data/dao/player_group_dao.dart';
import 'package:tallee/data/dao/player_match_dao.dart';
import 'package:tallee/data/dao/score_dao.dart';
import 'package:tallee/data/dao/score_entry_dao.dart';
import 'package:tallee/data/dao/team_dao.dart';
import 'package:tallee/data/db/tables/game_table.dart';
import 'package:tallee/data/db/tables/group_table.dart';
@@ -15,7 +15,7 @@ import 'package:tallee/data/db/tables/match_table.dart';
import 'package:tallee/data/db/tables/player_group_table.dart';
import 'package:tallee/data/db/tables/player_match_table.dart';
import 'package:tallee/data/db/tables/player_table.dart';
import 'package:tallee/data/db/tables/score_table.dart';
import 'package:tallee/data/db/tables/score_entry_table.dart';
import 'package:tallee/data/db/tables/team_table.dart';
part 'database.g.dart';
@@ -29,7 +29,7 @@ part 'database.g.dart';
PlayerMatchTable,
GameTable,
TeamTable,
ScoreTable,
ScoreEntryTable,
],
daos: [
PlayerDao,
@@ -38,8 +38,8 @@ part 'database.g.dart';
PlayerGroupDao,
PlayerMatchDao,
GameDao,
ScoreDao,
TeamDao
ScoreEntryDao,
TeamDao,
],
)
class AppDatabase extends _$AppDatabase {
@@ -60,7 +60,9 @@ class AppDatabase extends _$AppDatabase {
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'gametracker_db',
native: const DriftNativeOptions(databaseDirectory: getApplicationSupportDirectory),
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
);
}
}

View File

@@ -2307,12 +2307,12 @@ class PlayerMatchTableCompanion extends UpdateCompanion<PlayerMatchTableData> {
}
}
class $ScoreTableTable extends ScoreTable
with TableInfo<$ScoreTableTable, ScoreTableData> {
class $ScoreEntryTableTable extends ScoreEntryTable
with TableInfo<$ScoreEntryTableTable, ScoreEntryTableData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$ScoreTableTable(this.attachedDatabase, [this._alias]);
$ScoreEntryTableTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _playerIdMeta = const VerificationMeta(
'playerId',
);
@@ -2382,10 +2382,10 @@ class $ScoreTableTable extends ScoreTable
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'score_table';
static const String $name = 'score_entry_table';
@override
VerificationContext validateIntegrity(
Insertable<ScoreTableData> instance, {
Insertable<ScoreEntryTableData> instance, {
bool isInserting = false,
}) {
final context = VerificationContext();
@@ -2439,9 +2439,9 @@ class $ScoreTableTable extends ScoreTable
@override
Set<GeneratedColumn> get $primaryKey => {playerId, matchId, roundNumber};
@override
ScoreTableData map(Map<String, dynamic> data, {String? tablePrefix}) {
ScoreEntryTableData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return ScoreTableData(
return ScoreEntryTableData(
playerId: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}player_id'],
@@ -2466,18 +2466,19 @@ class $ScoreTableTable extends ScoreTable
}
@override
$ScoreTableTable createAlias(String alias) {
return $ScoreTableTable(attachedDatabase, alias);
$ScoreEntryTableTable createAlias(String alias) {
return $ScoreEntryTableTable(attachedDatabase, alias);
}
}
class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
class ScoreEntryTableData extends DataClass
implements Insertable<ScoreEntryTableData> {
final String playerId;
final String matchId;
final int roundNumber;
final int score;
final int change;
const ScoreTableData({
const ScoreEntryTableData({
required this.playerId,
required this.matchId,
required this.roundNumber,
@@ -2495,8 +2496,8 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
return map;
}
ScoreTableCompanion toCompanion(bool nullToAbsent) {
return ScoreTableCompanion(
ScoreEntryTableCompanion toCompanion(bool nullToAbsent) {
return ScoreEntryTableCompanion(
playerId: Value(playerId),
matchId: Value(matchId),
roundNumber: Value(roundNumber),
@@ -2505,12 +2506,12 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
);
}
factory ScoreTableData.fromJson(
factory ScoreEntryTableData.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return ScoreTableData(
return ScoreEntryTableData(
playerId: serializer.fromJson<String>(json['playerId']),
matchId: serializer.fromJson<String>(json['matchId']),
roundNumber: serializer.fromJson<int>(json['roundNumber']),
@@ -2530,21 +2531,21 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
};
}
ScoreTableData copyWith({
ScoreEntryTableData copyWith({
String? playerId,
String? matchId,
int? roundNumber,
int? score,
int? change,
}) => ScoreTableData(
}) => ScoreEntryTableData(
playerId: playerId ?? this.playerId,
matchId: matchId ?? this.matchId,
roundNumber: roundNumber ?? this.roundNumber,
score: score ?? this.score,
change: change ?? this.change,
);
ScoreTableData copyWithCompanion(ScoreTableCompanion data) {
return ScoreTableData(
ScoreEntryTableData copyWithCompanion(ScoreEntryTableCompanion data) {
return ScoreEntryTableData(
playerId: data.playerId.present ? data.playerId.value : this.playerId,
matchId: data.matchId.present ? data.matchId.value : this.matchId,
roundNumber: data.roundNumber.present
@@ -2557,7 +2558,7 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
@override
String toString() {
return (StringBuffer('ScoreTableData(')
return (StringBuffer('ScoreEntryTableData(')
..write('playerId: $playerId, ')
..write('matchId: $matchId, ')
..write('roundNumber: $roundNumber, ')
@@ -2573,7 +2574,7 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is ScoreTableData &&
(other is ScoreEntryTableData &&
other.playerId == this.playerId &&
other.matchId == this.matchId &&
other.roundNumber == this.roundNumber &&
@@ -2581,14 +2582,14 @@ class ScoreTableData extends DataClass implements Insertable<ScoreTableData> {
other.change == this.change);
}
class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
class ScoreEntryTableCompanion extends UpdateCompanion<ScoreEntryTableData> {
final Value<String> playerId;
final Value<String> matchId;
final Value<int> roundNumber;
final Value<int> score;
final Value<int> change;
final Value<int> rowid;
const ScoreTableCompanion({
const ScoreEntryTableCompanion({
this.playerId = const Value.absent(),
this.matchId = const Value.absent(),
this.roundNumber = const Value.absent(),
@@ -2596,7 +2597,7 @@ class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
this.change = const Value.absent(),
this.rowid = const Value.absent(),
});
ScoreTableCompanion.insert({
ScoreEntryTableCompanion.insert({
required String playerId,
required String matchId,
required int roundNumber,
@@ -2608,7 +2609,7 @@ class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
roundNumber = Value(roundNumber),
score = Value(score),
change = Value(change);
static Insertable<ScoreTableData> custom({
static Insertable<ScoreEntryTableData> custom({
Expression<String>? playerId,
Expression<String>? matchId,
Expression<int>? roundNumber,
@@ -2626,7 +2627,7 @@ class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
});
}
ScoreTableCompanion copyWith({
ScoreEntryTableCompanion copyWith({
Value<String>? playerId,
Value<String>? matchId,
Value<int>? roundNumber,
@@ -2634,7 +2635,7 @@ class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
Value<int>? change,
Value<int>? rowid,
}) {
return ScoreTableCompanion(
return ScoreEntryTableCompanion(
playerId: playerId ?? this.playerId,
matchId: matchId ?? this.matchId,
roundNumber: roundNumber ?? this.roundNumber,
@@ -2670,7 +2671,7 @@ class ScoreTableCompanion extends UpdateCompanion<ScoreTableData> {
@override
String toString() {
return (StringBuffer('ScoreTableCompanion(')
return (StringBuffer('ScoreEntryTableCompanion(')
..write('playerId: $playerId, ')
..write('matchId: $matchId, ')
..write('roundNumber: $roundNumber, ')
@@ -2696,7 +2697,9 @@ abstract class _$AppDatabase extends GeneratedDatabase {
late final $PlayerMatchTableTable playerMatchTable = $PlayerMatchTableTable(
this,
);
late final $ScoreTableTable scoreTable = $ScoreTableTable(this);
late final $ScoreEntryTableTable scoreEntryTable = $ScoreEntryTableTable(
this,
);
late final PlayerDao playerDao = PlayerDao(this as AppDatabase);
late final GroupDao groupDao = GroupDao(this as AppDatabase);
late final MatchDao matchDao = MatchDao(this as AppDatabase);
@@ -2707,7 +2710,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
this as AppDatabase,
);
late final GameDao gameDao = GameDao(this as AppDatabase);
late final ScoreDao scoreDao = ScoreDao(this as AppDatabase);
late final ScoreEntryDao scoreEntryDao = ScoreEntryDao(this as AppDatabase);
late final TeamDao teamDao = TeamDao(this as AppDatabase);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
@@ -2721,7 +2724,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
playerGroupTable,
teamTable,
playerMatchTable,
scoreTable,
scoreEntryTable,
];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([
@@ -2772,14 +2775,14 @@ abstract class _$AppDatabase extends GeneratedDatabase {
'player_table',
limitUpdateKind: UpdateKind.delete,
),
result: [TableUpdate('score_table', kind: UpdateKind.delete)],
result: [TableUpdate('score_entry_table', kind: UpdateKind.delete)],
),
WritePropagation(
on: TableUpdateQuery.onTableName(
'match_table',
limitUpdateKind: UpdateKind.delete,
),
result: [TableUpdate('score_table', kind: UpdateKind.delete)],
result: [TableUpdate('score_entry_table', kind: UpdateKind.delete)],
),
]);
}
@@ -2851,19 +2854,24 @@ final class $$PlayerTableTableReferences
);
}
static MultiTypedResultKey<$ScoreTableTable, List<ScoreTableData>>
_scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.scoreTable,
aliasName: $_aliasNameGenerator(db.playerTable.id, db.scoreTable.playerId),
static MultiTypedResultKey<$ScoreEntryTableTable, List<ScoreEntryTableData>>
_scoreEntryTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.scoreEntryTable,
aliasName: $_aliasNameGenerator(
db.playerTable.id,
db.scoreEntryTable.playerId,
),
);
$$ScoreTableTableProcessedTableManager get scoreTableRefs {
final manager = $$ScoreTableTableTableManager(
$$ScoreEntryTableTableProcessedTableManager get scoreEntryTableRefs {
final manager = $$ScoreEntryTableTableTableManager(
$_db,
$_db.scoreTable,
$_db.scoreEntryTable,
).filter((f) => f.playerId.id.sqlEquals($_itemColumn<String>('id')!));
final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db));
final cache = $_typedResult.readTableOrNull(
_scoreEntryTableRefsTable($_db),
);
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache),
);
@@ -2949,22 +2957,22 @@ class $$PlayerTableTableFilterComposer
return f(composer);
}
Expression<bool> scoreTableRefs(
Expression<bool> Function($$ScoreTableTableFilterComposer f) f,
Expression<bool> scoreEntryTableRefs(
Expression<bool> Function($$ScoreEntryTableTableFilterComposer f) f,
) {
final $$ScoreTableTableFilterComposer composer = $composerBuilder(
final $$ScoreEntryTableTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.scoreTable,
referencedTable: $db.scoreEntryTable,
getReferencedColumn: (t) => t.playerId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$ScoreTableTableFilterComposer(
}) => $$ScoreEntryTableTableFilterComposer(
$db: $db,
$table: $db.scoreTable,
$table: $db.scoreEntryTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -3078,22 +3086,22 @@ class $$PlayerTableTableAnnotationComposer
return f(composer);
}
Expression<T> scoreTableRefs<T extends Object>(
Expression<T> Function($$ScoreTableTableAnnotationComposer a) f,
Expression<T> scoreEntryTableRefs<T extends Object>(
Expression<T> Function($$ScoreEntryTableTableAnnotationComposer a) f,
) {
final $$ScoreTableTableAnnotationComposer composer = $composerBuilder(
final $$ScoreEntryTableTableAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.scoreTable,
referencedTable: $db.scoreEntryTable,
getReferencedColumn: (t) => t.playerId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$ScoreTableTableAnnotationComposer(
}) => $$ScoreEntryTableTableAnnotationComposer(
$db: $db,
$table: $db.scoreTable,
$table: $db.scoreEntryTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -3120,7 +3128,7 @@ class $$PlayerTableTableTableManager
PrefetchHooks Function({
bool playerGroupTableRefs,
bool playerMatchTableRefs,
bool scoreTableRefs,
bool scoreEntryTableRefs,
})
> {
$$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table)
@@ -3174,14 +3182,14 @@ class $$PlayerTableTableTableManager
({
playerGroupTableRefs = false,
playerMatchTableRefs = false,
scoreTableRefs = false,
scoreEntryTableRefs = false,
}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [
if (playerGroupTableRefs) db.playerGroupTable,
if (playerMatchTableRefs) db.playerMatchTable,
if (scoreTableRefs) db.scoreTable,
if (scoreEntryTableRefs) db.scoreEntryTable,
],
addJoins: null,
getPrefetchedDataCallback: (items) async {
@@ -3228,21 +3236,21 @@ class $$PlayerTableTableTableManager
),
typedResults: items,
),
if (scoreTableRefs)
if (scoreEntryTableRefs)
await $_getPrefetchedData<
PlayerTableData,
$PlayerTableTable,
ScoreTableData
ScoreEntryTableData
>(
currentTable: table,
referencedTable: $$PlayerTableTableReferences
._scoreTableRefsTable(db),
._scoreEntryTableRefsTable(db),
managerFromTypedResult: (p0) =>
$$PlayerTableTableReferences(
db,
table,
p0,
).scoreTableRefs,
).scoreEntryTableRefs,
referencedItemsForCurrentItem:
(item, referencedItems) => referencedItems.where(
(e) => e.playerId == item.id,
@@ -3272,7 +3280,7 @@ typedef $$PlayerTableTableProcessedTableManager =
PrefetchHooks Function({
bool playerGroupTableRefs,
bool playerMatchTableRefs,
bool scoreTableRefs,
bool scoreEntryTableRefs,
})
>;
typedef $$GroupTableTableCreateCompanionBuilder =
@@ -4089,19 +4097,24 @@ final class $$MatchTableTableReferences
);
}
static MultiTypedResultKey<$ScoreTableTable, List<ScoreTableData>>
_scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.scoreTable,
aliasName: $_aliasNameGenerator(db.matchTable.id, db.scoreTable.matchId),
static MultiTypedResultKey<$ScoreEntryTableTable, List<ScoreEntryTableData>>
_scoreEntryTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.scoreEntryTable,
aliasName: $_aliasNameGenerator(
db.matchTable.id,
db.scoreEntryTable.matchId,
),
);
$$ScoreTableTableProcessedTableManager get scoreTableRefs {
final manager = $$ScoreTableTableTableManager(
$$ScoreEntryTableTableProcessedTableManager get scoreEntryTableRefs {
final manager = $$ScoreEntryTableTableTableManager(
$_db,
$_db.scoreTable,
$_db.scoreEntryTable,
).filter((f) => f.matchId.id.sqlEquals($_itemColumn<String>('id')!));
final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db));
final cache = $_typedResult.readTableOrNull(
_scoreEntryTableRefsTable($_db),
);
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache),
);
@@ -4213,22 +4226,22 @@ class $$MatchTableTableFilterComposer
return f(composer);
}
Expression<bool> scoreTableRefs(
Expression<bool> Function($$ScoreTableTableFilterComposer f) f,
Expression<bool> scoreEntryTableRefs(
Expression<bool> Function($$ScoreEntryTableTableFilterComposer f) f,
) {
final $$ScoreTableTableFilterComposer composer = $composerBuilder(
final $$ScoreEntryTableTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.scoreTable,
referencedTable: $db.scoreEntryTable,
getReferencedColumn: (t) => t.matchId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$ScoreTableTableFilterComposer(
}) => $$ScoreEntryTableTableFilterComposer(
$db: $db,
$table: $db.scoreTable,
$table: $db.scoreEntryTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -4415,22 +4428,22 @@ class $$MatchTableTableAnnotationComposer
return f(composer);
}
Expression<T> scoreTableRefs<T extends Object>(
Expression<T> Function($$ScoreTableTableAnnotationComposer a) f,
Expression<T> scoreEntryTableRefs<T extends Object>(
Expression<T> Function($$ScoreEntryTableTableAnnotationComposer a) f,
) {
final $$ScoreTableTableAnnotationComposer composer = $composerBuilder(
final $$ScoreEntryTableTableAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.scoreTable,
referencedTable: $db.scoreEntryTable,
getReferencedColumn: (t) => t.matchId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$ScoreTableTableAnnotationComposer(
}) => $$ScoreEntryTableTableAnnotationComposer(
$db: $db,
$table: $db.scoreTable,
$table: $db.scoreEntryTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
@@ -4458,7 +4471,7 @@ class $$MatchTableTableTableManager
bool gameId,
bool groupId,
bool playerMatchTableRefs,
bool scoreTableRefs,
bool scoreEntryTableRefs,
})
> {
$$MatchTableTableTableManager(_$AppDatabase db, $MatchTableTable table)
@@ -4525,13 +4538,13 @@ class $$MatchTableTableTableManager
gameId = false,
groupId = false,
playerMatchTableRefs = false,
scoreTableRefs = false,
scoreEntryTableRefs = false,
}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [
if (playerMatchTableRefs) db.playerMatchTable,
if (scoreTableRefs) db.scoreTable,
if (scoreEntryTableRefs) db.scoreEntryTable,
],
addJoins:
<
@@ -4603,21 +4616,21 @@ class $$MatchTableTableTableManager
),
typedResults: items,
),
if (scoreTableRefs)
if (scoreEntryTableRefs)
await $_getPrefetchedData<
MatchTableData,
$MatchTableTable,
ScoreTableData
ScoreEntryTableData
>(
currentTable: table,
referencedTable: $$MatchTableTableReferences
._scoreTableRefsTable(db),
._scoreEntryTableRefsTable(db),
managerFromTypedResult: (p0) =>
$$MatchTableTableReferences(
db,
table,
p0,
).scoreTableRefs,
).scoreEntryTableRefs,
referencedItemsForCurrentItem:
(item, referencedItems) => referencedItems.where(
(e) => e.matchId == item.id,
@@ -4648,7 +4661,7 @@ typedef $$MatchTableTableProcessedTableManager =
bool gameId,
bool groupId,
bool playerMatchTableRefs,
bool scoreTableRefs,
bool scoreEntryTableRefs,
})
>;
typedef $$PlayerGroupTableTableCreateCompanionBuilder =
@@ -5761,8 +5774,8 @@ typedef $$PlayerMatchTableTableProcessedTableManager =
PlayerMatchTableData,
PrefetchHooks Function({bool playerId, bool matchId, bool teamId})
>;
typedef $$ScoreTableTableCreateCompanionBuilder =
ScoreTableCompanion Function({
typedef $$ScoreEntryTableTableCreateCompanionBuilder =
ScoreEntryTableCompanion Function({
required String playerId,
required String matchId,
required int roundNumber,
@@ -5770,8 +5783,8 @@ typedef $$ScoreTableTableCreateCompanionBuilder =
required int change,
Value<int> rowid,
});
typedef $$ScoreTableTableUpdateCompanionBuilder =
ScoreTableCompanion Function({
typedef $$ScoreEntryTableTableUpdateCompanionBuilder =
ScoreEntryTableCompanion Function({
Value<String> playerId,
Value<String> matchId,
Value<int> roundNumber,
@@ -5780,13 +5793,22 @@ typedef $$ScoreTableTableUpdateCompanionBuilder =
Value<int> rowid,
});
final class $$ScoreTableTableReferences
extends BaseReferences<_$AppDatabase, $ScoreTableTable, ScoreTableData> {
$$ScoreTableTableReferences(super.$_db, super.$_table, super.$_typedResult);
final class $$ScoreEntryTableTableReferences
extends
BaseReferences<
_$AppDatabase,
$ScoreEntryTableTable,
ScoreEntryTableData
> {
$$ScoreEntryTableTableReferences(
super.$_db,
super.$_table,
super.$_typedResult,
);
static $PlayerTableTable _playerIdTable(_$AppDatabase db) =>
db.playerTable.createAlias(
$_aliasNameGenerator(db.scoreTable.playerId, db.playerTable.id),
$_aliasNameGenerator(db.scoreEntryTable.playerId, db.playerTable.id),
);
$$PlayerTableTableProcessedTableManager get playerId {
@@ -5805,7 +5827,7 @@ final class $$ScoreTableTableReferences
static $MatchTableTable _matchIdTable(_$AppDatabase db) =>
db.matchTable.createAlias(
$_aliasNameGenerator(db.scoreTable.matchId, db.matchTable.id),
$_aliasNameGenerator(db.scoreEntryTable.matchId, db.matchTable.id),
);
$$MatchTableTableProcessedTableManager get matchId {
@@ -5823,9 +5845,9 @@ final class $$ScoreTableTableReferences
}
}
class $$ScoreTableTableFilterComposer
extends Composer<_$AppDatabase, $ScoreTableTable> {
$$ScoreTableTableFilterComposer({
class $$ScoreEntryTableTableFilterComposer
extends Composer<_$AppDatabase, $ScoreEntryTableTable> {
$$ScoreEntryTableTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -5894,9 +5916,9 @@ class $$ScoreTableTableFilterComposer
}
}
class $$ScoreTableTableOrderingComposer
extends Composer<_$AppDatabase, $ScoreTableTable> {
$$ScoreTableTableOrderingComposer({
class $$ScoreEntryTableTableOrderingComposer
extends Composer<_$AppDatabase, $ScoreEntryTableTable> {
$$ScoreEntryTableTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -5965,9 +5987,9 @@ class $$ScoreTableTableOrderingComposer
}
}
class $$ScoreTableTableAnnotationComposer
extends Composer<_$AppDatabase, $ScoreTableTable> {
$$ScoreTableTableAnnotationComposer({
class $$ScoreEntryTableTableAnnotationComposer
extends Composer<_$AppDatabase, $ScoreEntryTableTable> {
$$ScoreEntryTableTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
@@ -6032,32 +6054,34 @@ class $$ScoreTableTableAnnotationComposer
}
}
class $$ScoreTableTableTableManager
class $$ScoreEntryTableTableTableManager
extends
RootTableManager<
_$AppDatabase,
$ScoreTableTable,
ScoreTableData,
$$ScoreTableTableFilterComposer,
$$ScoreTableTableOrderingComposer,
$$ScoreTableTableAnnotationComposer,
$$ScoreTableTableCreateCompanionBuilder,
$$ScoreTableTableUpdateCompanionBuilder,
(ScoreTableData, $$ScoreTableTableReferences),
ScoreTableData,
$ScoreEntryTableTable,
ScoreEntryTableData,
$$ScoreEntryTableTableFilterComposer,
$$ScoreEntryTableTableOrderingComposer,
$$ScoreEntryTableTableAnnotationComposer,
$$ScoreEntryTableTableCreateCompanionBuilder,
$$ScoreEntryTableTableUpdateCompanionBuilder,
(ScoreEntryTableData, $$ScoreEntryTableTableReferences),
ScoreEntryTableData,
PrefetchHooks Function({bool playerId, bool matchId})
> {
$$ScoreTableTableTableManager(_$AppDatabase db, $ScoreTableTable table)
: super(
$$ScoreEntryTableTableTableManager(
_$AppDatabase db,
$ScoreEntryTableTable table,
) : super(
TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$ScoreTableTableFilterComposer($db: db, $table: table),
$$ScoreEntryTableTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$ScoreTableTableOrderingComposer($db: db, $table: table),
$$ScoreEntryTableTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$ScoreTableTableAnnotationComposer($db: db, $table: table),
$$ScoreEntryTableTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
Value<String> playerId = const Value.absent(),
@@ -6066,7 +6090,7 @@ class $$ScoreTableTableTableManager
Value<int> score = const Value.absent(),
Value<int> change = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => ScoreTableCompanion(
}) => ScoreEntryTableCompanion(
playerId: playerId,
matchId: matchId,
roundNumber: roundNumber,
@@ -6082,7 +6106,7 @@ class $$ScoreTableTableTableManager
required int score,
required int change,
Value<int> rowid = const Value.absent(),
}) => ScoreTableCompanion.insert(
}) => ScoreEntryTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: roundNumber,
@@ -6094,7 +6118,7 @@ class $$ScoreTableTableTableManager
.map(
(e) => (
e.readTable(table),
$$ScoreTableTableReferences(db, table, e),
$$ScoreEntryTableTableReferences(db, table, e),
),
)
.toList(),
@@ -6123,11 +6147,13 @@ class $$ScoreTableTableTableManager
state.withJoin(
currentTable: table,
currentColumn: table.playerId,
referencedTable: $$ScoreTableTableReferences
._playerIdTable(db),
referencedColumn: $$ScoreTableTableReferences
._playerIdTable(db)
.id,
referencedTable:
$$ScoreEntryTableTableReferences
._playerIdTable(db),
referencedColumn:
$$ScoreEntryTableTableReferences
._playerIdTable(db)
.id,
)
as T;
}
@@ -6136,11 +6162,13 @@ class $$ScoreTableTableTableManager
state.withJoin(
currentTable: table,
currentColumn: table.matchId,
referencedTable: $$ScoreTableTableReferences
._matchIdTable(db),
referencedColumn: $$ScoreTableTableReferences
._matchIdTable(db)
.id,
referencedTable:
$$ScoreEntryTableTableReferences
._matchIdTable(db),
referencedColumn:
$$ScoreEntryTableTableReferences
._matchIdTable(db)
.id,
)
as T;
}
@@ -6156,18 +6184,18 @@ class $$ScoreTableTableTableManager
);
}
typedef $$ScoreTableTableProcessedTableManager =
typedef $$ScoreEntryTableTableProcessedTableManager =
ProcessedTableManager<
_$AppDatabase,
$ScoreTableTable,
ScoreTableData,
$$ScoreTableTableFilterComposer,
$$ScoreTableTableOrderingComposer,
$$ScoreTableTableAnnotationComposer,
$$ScoreTableTableCreateCompanionBuilder,
$$ScoreTableTableUpdateCompanionBuilder,
(ScoreTableData, $$ScoreTableTableReferences),
ScoreTableData,
$ScoreEntryTableTable,
ScoreEntryTableData,
$$ScoreEntryTableTableFilterComposer,
$$ScoreEntryTableTableOrderingComposer,
$$ScoreEntryTableTableAnnotationComposer,
$$ScoreEntryTableTableCreateCompanionBuilder,
$$ScoreEntryTableTableUpdateCompanionBuilder,
(ScoreEntryTableData, $$ScoreEntryTableTableReferences),
ScoreEntryTableData,
PrefetchHooks Function({bool playerId, bool matchId})
>;
@@ -6188,6 +6216,6 @@ class $AppDatabaseManager {
$$TeamTableTableTableManager(_db, _db.teamTable);
$$PlayerMatchTableTableTableManager get playerMatchTable =>
$$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable);
$$ScoreTableTableTableManager get scoreTable =>
$$ScoreTableTableTableManager(_db, _db.scoreTable);
$$ScoreEntryTableTableTableManager get scoreEntryTable =>
$$ScoreEntryTableTableTableManager(_db, _db.scoreEntryTable);
}

View File

@@ -8,6 +8,7 @@ class GameTable extends Table {
TextColumn get color => text()();
TextColumn get icon => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -5,6 +5,7 @@ class GroupTable extends Table {
TextColumn get name => text()();
TextColumn get description => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -15,6 +15,7 @@ class MatchTable extends Table {
TextColumn get notes => text().nullable()();
DateTimeColumn get createdAt => dateTime()();
DateTimeColumn get endedAt => dateTime().nullable()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -7,6 +7,7 @@ class PlayerGroupTable extends Table {
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get groupId =>
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {playerId, groupId};

View File

@@ -9,6 +9,7 @@ class PlayerMatchTable extends Table {
TextColumn get matchId =>
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
TextColumn get teamId => text().references(TeamTable, #id).nullable()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {playerId, matchId};

View File

@@ -5,6 +5,7 @@ class PlayerTable extends Table {
TextColumn get name => text()();
TextColumn get description => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -2,7 +2,7 @@ import 'package:drift/drift.dart';
import 'package:tallee/data/db/tables/match_table.dart';
import 'package:tallee/data/db/tables/player_table.dart';
class ScoreTable extends Table {
class ScoreEntryTable extends Table {
TextColumn get playerId =>
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get matchId =>
@@ -10,6 +10,7 @@ class ScoreTable extends Table {
IntColumn get roundNumber => integer()();
IntColumn get score => integer()();
IntColumn get change => integer()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {playerId, matchId, roundNumber};

View File

@@ -4,6 +4,7 @@ class TeamTable extends Table {
TextColumn get id => text()();
TextColumn get name => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -24,16 +24,17 @@ class Group {
return 'Group{id: $id, name: $name, description: $description, members: $members}';
}
/// Creates a Group instance from a JSON object (memberIds format).
/// Player objects are reconstructed from memberIds by the DataTransferService.
/// Creates a Group instance from a JSON object where the related [Player]
/// objects are represented by their IDs.
Group.fromJson(Map<String, dynamic> json)
: id = json['id'],
createdAt = DateTime.parse(json['createdAt']),
name = json['name'],
description = json['description'],
members = []; // Populated during import via DataTransferService
members = [];
/// Converts the Group instance to a JSON object using normalized format (memberIds only).
/// Converts the Group instance to a JSON object. Related [Player] objects are
/// represented by their IDs.
Map<String, dynamic> toJson() => {
'id': id,
'createdAt': createdAt.toIso8601String(),

View File

@@ -3,7 +3,7 @@ import 'package:tallee/core/enums.dart';
import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart';
import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score.dart';
import 'package:tallee/data/models/score_entry.dart';
import 'package:uuid/uuid.dart';
class Match {
@@ -15,7 +15,7 @@ class Match {
final Group? group;
final List<Player> players;
final String notes;
Map<String, List<Score>> scores;
Map<String, List<ScoreEntry>> scores;
Player? winner;
Match({
@@ -27,7 +27,7 @@ class Match {
this.group,
this.players = const [],
this.notes = '',
Map<String, List<Score>>? scores,
Map<String, List<ScoreEntry>>? scores,
this.winner,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(),
@@ -38,8 +38,9 @@ class Match {
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, winner: $winner}';
}
/// Creates a Match instance from a JSON object (ID references format).
/// Related objects are reconstructed from IDs by the DataTransferService.
/// Creates a Match instance from a JSON object where related objects are
/// represented by their IDs. Therefore, the game, group, and players are not
/// fully constructed here.
Match.fromJson(Map<String, dynamic> json)
: id = json['id'],
createdAt = DateTime.parse(json['createdAt']),
@@ -53,13 +54,15 @@ class Match {
description: '',
color: GameColor.blue,
icon: '',
), // Populated during import via DataTransferService
group = null, // Populated during import via DataTransferService
players = [], // Populated during import via DataTransferService
),
group = null,
players = [],
scores = json['scores'],
notes = json['notes'] ?? '';
/// Converts the Match instance to a JSON object using normalized format (ID references only).
/// Converts the Match instance to a JSON object. Related objects are
/// represented by their IDs, so the game, group, and players are not fully
/// serialized here.
Map<String, dynamic> toJson() => {
'id': id,
'createdAt': createdAt.toIso8601String(),
@@ -68,7 +71,10 @@ class Match {
'gameId': game.id,
'groupId': group?.id,
'playerIds': players.map((player) => player.id).toList(),
'scores': scores,
'scores': scores.map(
(playerId, scoreList) =>
MapEntry(playerId, scoreList.map((score) => score.toJson()).toList()),
),
'notes': notes,
};
}

View File

@@ -1,18 +0,0 @@
class Score {
final int roundNumber;
int score = 0;
int change = 0;
Score({required this.roundNumber, required this.score, required this.change});
Score.fromJson(Map<String, dynamic> json)
: roundNumber = json['roundNumber'],
score = json['score'],
change = json['change'];
Map<String, dynamic> toJson() => {
'roundNumber': roundNumber,
'score': score,
'change': change,
};
}

View File

@@ -0,0 +1,22 @@
class ScoreEntry {
int roundNumber = 0;
final int score;
final int change;
ScoreEntry({
required this.roundNumber,
required this.score,
required this.change,
});
ScoreEntry.fromJson(Map<String, dynamic> json)
: roundNumber = json['roundNumber'],
score = json['score'],
change = json['change'];
Map<String, dynamic> toJson() => {
'roundNumber': roundNumber,
'score': score,
'change': change,
};
}

View File

@@ -29,7 +29,8 @@ class Team {
createdAt = DateTime.parse(json['createdAt']),
members = []; // Populated during import via DataTransferService
/// Converts the Team instance to a JSON object using normalized format (memberIds only).
/// Converts the Team instance to a JSON object. Related objects are
/// represented by their IDs.
Map<String, dynamic> toJson() => {
'id': id,
'name': name,

View File

@@ -227,7 +227,7 @@ class _HomeViewState extends State<HomeView> {
/// Updates the winner information for a specific match in the recent matches list.
Future<void> updatedWinnerInRecentMatches(String matchId) async {
final db = Provider.of<AppDatabase>(context, listen: false);
final winner = await db.scoreDao.getWinner(matchId: matchId);
final winner = await db.scoreEntryDao.getWinner(matchId: matchId);
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
if (matchIndex != -1) {
setState(() {

View File

@@ -139,9 +139,9 @@ class _MatchResultViewState extends State<MatchResultView> {
/// based on the current selection.
Future<void> _handleWinnerSaving() async {
if (_selectedPlayer == null) {
await db.scoreDao.removeWinner(matchId: widget.match.id);
await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
} else {
await db.scoreDao.setWinner(
await db.scoreEntryDao.setWinner(
matchId: widget.match.id,
playerId: _selectedPlayer!.id,
);

View File

@@ -70,6 +70,8 @@ const allDependencies = <Package>[
_http_parser,
_intl,
_io,
_jni,
_jni_flutter,
_js,
_json_annotation,
_json_schema,
@@ -360,13 +362,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// async 2.13.0
/// async 2.13.1
const _async = Package(
name: 'async',
description: "Utility functions and classes related to the 'dart:async' library.",
repository: 'https://github.com/dart-lang/core/tree/main/pkgs/async',
authors: [],
version: '2.13.0',
version: '2.13.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -442,13 +444,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// build 4.0.4
/// build 4.0.5
const _build = Package(
name: 'build',
description: 'A package for authoring build_runner compatible code generators.',
repository: 'https://github.com/dart-lang/build/tree/master/build',
authors: [],
version: '4.0.4',
version: '4.0.5',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -565,13 +567,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// build_runner 2.12.2
/// build_runner 2.13.1
const _build_runner = Package(
name: 'build_runner',
description: 'A build system for Dart code generation and modular compilation.',
repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
authors: [],
version: '2.12.2',
version: '2.13.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -649,14 +651,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// built_value 8.12.4
/// built_value 8.12.5
const _built_value = Package(
name: 'built_value',
description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency.
''',
repository: 'https://github.com/google/built_value.dart/tree/master/built_value',
authors: [],
version: '8.12.4',
version: '8.12.5',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -1436,18 +1438,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// cupertino_icons 1.0.8
/// cupertino_icons 1.0.9
const _cupertino_icons = Package(
name: 'cupertino_icons',
description: 'Default icons asset for Cupertino widgets based on Apple styled icons',
repository: 'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
authors: [],
version: '1.0.8',
version: '1.0.9',
spdxIdentifiers: ['MIT'],
isMarkdown: false,
isSdk: false,
dependencies: [],
devDependencies: [PackageRef('flutter'), PackageRef('flutter_test')],
devDependencies: [PackageRef('collection'), PackageRef('flutter'), PackageRef('flutter_test'), PackageRef('path')],
license: '''The MIT License (MIT)
Copyright (c) 2016 Vladimir Kharlampidi
@@ -2627,13 +2629,13 @@ const _flutter_localizations = Package(
devDependencies: [PackageRef('flutter_test')],
);
/// flutter_plugin_android_lifecycle 2.0.33
/// flutter_plugin_android_lifecycle 2.0.34
const _flutter_plugin_android_lifecycle = Package(
name: 'flutter_plugin_android_lifecycle',
description: 'Flutter plugin for accessing an Android Lifecycle within other plugins.',
repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle',
authors: [],
version: '2.0.33',
version: '2.0.34',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -3173,6 +3175,88 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// jni 1.0.0
const _jni = Package(
name: 'jni',
description: 'A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen.',
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/jni',
authors: [],
version: '1.0.0',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
dependencies: [PackageRef('args'), PackageRef('collection'), PackageRef('ffi'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('plugin_platform_interface')],
devDependencies: [PackageRef('dart_style'), PackageRef('logging'), PackageRef('test')],
license: '''Copyright 2022, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// jni_flutter 1.0.1
const _jni_flutter = Package(
name: 'jni_flutter',
description: 'A library to access Flutter Android specific APIs from Dart.',
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/jni_flutter',
authors: [],
version: '1.0.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
dependencies: [PackageRef('flutter'), PackageRef('jni')],
devDependencies: [PackageRef('flutter_test')],
license: '''Copyright 2026, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// js 0.7.2
const _js = Package(
name: 'js',
@@ -3511,13 +3595,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// markdown 7.3.0
/// markdown 7.3.1
const _markdown = Package(
name: 'markdown',
description: 'A portable Markdown library written in Dart that can parse Markdown into HTML.',
repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/markdown',
authors: [],
version: '7.3.0',
version: '7.3.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -3890,13 +3974,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// native_toolchain_c 0.17.5
/// native_toolchain_c 0.17.6
const _native_toolchain_c = Package(
name: 'native_toolchain_c',
description: 'A library to invoke the native C compiler installed on the host machine.',
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c',
authors: [],
version: '0.17.5',
version: '0.17.6',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -4110,14 +4194,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// package_info_plus 9.0.0
/// package_info_plus 9.0.1
const _package_info_plus = Package(
name: 'package_info_plus',
description: 'Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.',
homepage: 'https://github.com/fluttercommunity/plus_plugins',
repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus',
authors: [],
version: '9.0.0',
version: '9.0.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -4194,13 +4278,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// pana 0.23.10
/// pana 0.23.11
const _pana = Package(
name: 'pana',
description: 'PAckage aNAlyzer - produce a report summarizing the health and quality of a Dart package.',
repository: 'https://github.com/dart-lang/pana',
authors: [],
version: '0.23.10',
version: '0.23.11',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -4315,17 +4399,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// path_provider_android 2.2.22
/// path_provider_android 2.3.1
const _path_provider_android = Package(
name: 'path_provider_android',
description: 'Android implementation of the path_provider plugin.',
repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android',
authors: [],
version: '2.2.22',
version: '2.3.1',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')],
dependencies: [PackageRef('flutter'), PackageRef('jni'), PackageRef('jni_flutter'), PackageRef('path_provider_platform_interface')],
devDependencies: [PackageRef('flutter_test'), PackageRef('test')],
license: '''Copyright 2013 The Flutter Authors
@@ -36119,13 +36203,13 @@ Copyright (C) 2009-2017, International Business Machines Corporation,
Google, and others. All Rights Reserved.''',
);
/// source_gen 4.2.0
/// source_gen 4.2.2
const _source_gen = Package(
name: 'source_gen',
description: 'Source code generation builders and utilities for the Dart build system',
repository: 'https://github.com/dart-lang/source_gen/tree/master/source_gen',
authors: [],
version: '4.2.0',
version: '4.2.2',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -36835,13 +36919,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
);
/// url_launcher_android 6.3.28
/// url_launcher_android 6.3.29
const _url_launcher_android = Package(
name: 'url_launcher_android',
description: 'Android implementation of the url_launcher plugin.',
repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android',
authors: [],
version: '6.3.28',
version: '6.3.29',
spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false,
isSdk: false,
@@ -37796,12 +37880,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
);
/// tallee 0.0.19+253
/// tallee 0.0.20+254
const _tallee = Package(
name: 'tallee',
description: 'Tracking App for Card Games',
authors: [],
version: '0.0.19+253',
version: '0.0.20+254',
spdxIdentifiers: ['LGPL-3.0'],
isMarkdown: false,
isSdk: false,

View File

@@ -18,6 +18,7 @@ class DataTransferService {
/// Deletes all data from the database.
static Future<void> deleteAllData(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false);
await db.matchDao.deleteAllMatches();
await db.teamDao.deleteAllTeams();
await db.groupDao.deleteAllGroups();
@@ -70,6 +71,20 @@ class DataTransferService {
'gameId': m.game.id,
'groupId': m.group?.id,
'playerIds': m.players.map((p) => p.id).toList(),
'scores': m.scores.map(
(playerId, scores) => MapEntry(
playerId,
scores
.map(
(s) => {
'roundNumber': s.roundNumber,
'score': s.score,
'change': s.change,
},
)
.toList(),
),
),
'notes': m.notes,
},
)
@@ -82,8 +97,8 @@ class DataTransferService {
/// Exports the given JSON string to a file with the specified name.
/// Returns an [ExportResult] indicating the outcome.
///
/// [jsonString] The JSON string to be exported.
/// [fileName] The desired name for the exported file (without extension).
/// - [jsonString]: The JSON string to be exported.
/// - [fileName]: The desired name for the exported file (without extension).
static Future<ExportResult> exportData(
String jsonString,
String fileName,
@@ -124,129 +139,12 @@ class DataTransferService {
final jsonString = await _readFileContent(path.files.single);
if (jsonString == null) return ImportResult.fileReadError;
final isValid = await _validateJsonSchema(jsonString);
final isValid = await validateJsonSchema(jsonString);
if (!isValid) return ImportResult.invalidSchema;
final Map<String, dynamic> decoded =
json.decode(jsonString) as Map<String, dynamic>;
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final List<dynamic> playersJson =
(decoded['players'] as List<dynamic>?) ?? [];
final List<dynamic> gamesJson =
(decoded['games'] as List<dynamic>?) ?? [];
final List<dynamic> groupsJson =
(decoded['groups'] as List<dynamic>?) ?? [];
final List<dynamic> teamsJson =
(decoded['teams'] as List<dynamic>?) ?? [];
final List<dynamic> matchesJson =
(decoded['matches'] as List<dynamic>?) ?? [];
// Import Players
final List<Player> importedPlayers = playersJson
.map((p) => Player.fromJson(p as Map<String, dynamic>))
.toList();
final Map<String, Player> playerById = {
for (final p in importedPlayers) p.id: p,
};
// Import Games
final List<Game> importedGames = gamesJson
.map((g) => Game.fromJson(g as Map<String, dynamic>))
.toList();
final Map<String, Game> gameById = {
for (final g in importedGames) g.id: g,
};
// Import Groups
final List<Group> importedGroups = groupsJson.map((g) {
final map = g as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Group(
id: map['id'] as String,
name: map['name'] as String,
description: map['description'] as String,
members: members,
createdAt: DateTime.parse(map['createdAt'] as String),
);
}).toList();
final Map<String, Group> groupById = {
for (final g in importedGroups) g.id: g,
};
// Import Teams
final List<Team> importedTeams = teamsJson.map((t) {
final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Team(
id: map['id'] as String,
name: map['name'] as String,
members: members,
createdAt: DateTime.parse(map['createdAt'] as String),
);
}).toList();
// Import Matches
final List<Match> importedMatches = matchesJson.map((m) {
final map = m as Map<String, dynamic>;
final String gameId = map['gameId'] as String;
final String? groupId = map['groupId'] as String?;
final List<String> playerIds =
(map['playerIds'] as List<dynamic>? ?? []).cast<String>();
final DateTime? endedAt = map['endedAt'] != null
? DateTime.parse(map['endedAt'] as String)
: null;
final game = gameById[gameId];
final group = (groupId == null) ? null : groupById[groupId];
final players = playerIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Match(
id: map['id'] as String,
name: map['name'] as String,
game:
game ??
Game(
name: 'Unknown',
ruleset: Ruleset.singleWinner,
description: '',
color: GameColor.blue,
icon: '',
),
group: group,
players: players,
createdAt: DateTime.parse(map['createdAt'] as String),
endedAt: endedAt,
notes: map['notes'] as String? ?? '',
);
}).toList();
// Import all data into the database
await db.playerDao.addPlayersAsList(players: importedPlayers);
await db.gameDao.addGamesAsList(games: importedGames);
await db.groupDao.addGroupsAsList(groups: importedGroups);
await db.teamDao.addTeamsAsList(teams: importedTeams);
await db.matchDao.addMatchAsList(matches: importedMatches);
await importDataToDatabase(db, decoded);
return ImportResult.success;
} on FormatException catch (e, stack) {
@@ -262,6 +160,167 @@ class DataTransferService {
}
}
/// Imports parsed JSON data into the database.
@visibleForTesting
static Future<void> importDataToDatabase(
AppDatabase db,
Map<String, dynamic> decodedJson,
) async {
// Fetch all entities first to create lookup maps for relationships
final importedPlayers = parsePlayersFromJson(decodedJson);
final playerById = {for (final p in importedPlayers) p.id: p};
final importedGames = parseGamesFromJson(decodedJson);
final gameById = {for (final g in importedGames) g.id: g};
final importedGroups = parseGroupsFromJson(decodedJson, playerById);
final groupById = {for (final g in importedGroups) g.id: g};
final importedTeams = parseTeamsFromJson(decodedJson, playerById);
final importedMatches = parseMatchesFromJson(
decodedJson,
gameById,
groupById,
playerById,
);
await db.playerDao.addPlayersAsList(players: importedPlayers);
await db.gameDao.addGamesAsList(games: importedGames);
await db.groupDao.addGroupsAsList(groups: importedGroups);
await db.teamDao.addTeamsAsList(teams: importedTeams);
await db.matchDao.addMatchAsList(matches: importedMatches);
}
/* Parsing Methods */
@visibleForTesting
static List<Player> parsePlayersFromJson(Map<String, dynamic> decodedJson) {
final playersJson = (decodedJson['players'] as List<dynamic>?) ?? [];
return playersJson
.map((p) => Player.fromJson(p as Map<String, dynamic>))
.toList();
}
@visibleForTesting
static List<Game> parseGamesFromJson(Map<String, dynamic> decodedJson) {
final gamesJson = (decodedJson['games'] as List<dynamic>?) ?? [];
return gamesJson
.map((g) => Game.fromJson(g as Map<String, dynamic>))
.toList();
}
@visibleForTesting
static List<Group> parseGroupsFromJson(
Map<String, dynamic> decodedJson,
Map<String, Player> playerById,
) {
final groupsJson = (decodedJson['groups'] as List<dynamic>?) ?? [];
return groupsJson.map((g) {
final map = g as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Group(
id: map['id'] as String,
name: map['name'] as String,
description: map['description'] as String,
members: members,
createdAt: DateTime.parse(map['createdAt'] as String),
);
}).toList();
}
/// Parses teams from JSON data.
@visibleForTesting
static List<Team> parseTeamsFromJson(
Map<String, dynamic> decodedJson,
Map<String, Player> playerById,
) {
final teamsJson = (decodedJson['teams'] as List<dynamic>?) ?? [];
return teamsJson.map((t) {
final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Team(
id: map['id'] as String,
name: map['name'] as String,
members: members,
createdAt: DateTime.parse(map['createdAt'] as String),
);
}).toList();
}
/// Parses matches from JSON data.
@visibleForTesting
static List<Match> parseMatchesFromJson(
Map<String, dynamic> decodedJson,
Map<String, Game> gamesMap,
Map<String, Group> groupsMap,
Map<String, Player> playersMap,
) {
final matchesJson = (decodedJson['matches'] as List<dynamic>?) ?? [];
return matchesJson.map((m) {
final map = m as Map<String, dynamic>;
// Extract attributes from json
final id = map['id'] as String;
final name = map['name'] as String;
final gameId = map['gameId'] as String;
final groupId = map['groupId'] as String?;
final createdAt = DateTime.parse(map['createdAt'] as String);
final endedAt = map['endedAt'] != null
? DateTime.parse(map['endedAt'] as String)
: null;
final notes = map['notes'] as String? ?? '';
// Link attributes to objects
final game = gamesMap[gameId] ?? getFallbackGame();
final group = groupId != null ? groupsMap[groupId] : null;
final playerIds = (map['playerIds'] as List<dynamic>? ?? [])
.cast<String>();
final players = playerIds
.map((id) => playersMap[id])
.whereType<Player>()
.toList();
return Match(
id: id,
name: name,
game: game,
group: group,
players: players,
createdAt: createdAt,
endedAt: endedAt,
notes: notes,
);
}).toList();
}
/// Creates a fallback game when the referenced game is not found.
@visibleForTesting
static Game getFallbackGame() {
return Game(
name: 'Unknown',
ruleset: Ruleset.singleWinner,
description: '',
color: GameColor.blue,
icon: '',
);
}
/// Helper method to read file content from either bytes or path
static Future<String?> _readFileContent(PlatformFile file) async {
if (file.bytes != null) return utf8.decode(file.bytes!);
@@ -269,8 +328,10 @@ class DataTransferService {
return null;
}
/// Validates the given JSON string against the predefined schema.
static Future<bool> _validateJsonSchema(String jsonString) async {
/// Validates the given JSON string against the schema
/// in `assets/schema.json`.
@visibleForTesting
static Future<bool> validateJsonSchema(String jsonString) async {
final String schemaString;
schemaString = await rootBundle.loadString('assets/schema.json');

View File

@@ -1,7 +1,7 @@
name: tallee
description: "Tracking App for Card Games"
publish_to: 'none'
version: 0.0.19+253
version: 0.0.20+254
environment:
sdk: ^3.8.1

View File

@@ -296,7 +296,7 @@ void main() {
test('Setting a winner works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
await database.scoreDao.setWinner(
await database.scoreEntryDao.setWinner(
matchId: testMatch1.id,
playerId: testPlayer5.id,
);

View File

@@ -267,21 +267,6 @@ void main() {
expect(players, isNull);
});
test(
'updatePlayerScore returns false for non-existent player-match',
() async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
final updated = await database.scoreDao.updateScore(
matchId: testMatchOnlyGroup.id,
playerId: 'non-existent-player-id',
newScore: 50,
);
expect(updated, false);
},
);
test('Adding player with teamId works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);

View File

@@ -7,7 +7,7 @@ import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/match.dart';
import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score.dart';
import 'package:tallee/data/models/score_entry.dart';
void main() {
late AppDatabase database;
@@ -69,15 +69,14 @@ void main() {
group('Score Tests', () {
group('Adding and Fetching scores', () {
test('Single Score', () async {
await database.scoreDao.addScore(
ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry,
);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
@@ -91,18 +90,18 @@ void main() {
test('Multiple Scores', () async {
final entryList = [
Score(roundNumber: 1, score: 5, change: 5),
Score(roundNumber: 2, score: 12, change: 7),
Score(roundNumber: 3, score: 18, change: 6),
ScoreEntry(roundNumber: 1, score: 5, change: 5),
ScoreEntry(roundNumber: 2, score: 12, change: 7),
ScoreEntry(roundNumber: 3, score: 18, change: 6),
];
await database.scoreDao.addScoresAsList(
scores: entryList,
await database.scoreEntryDao.addScoresAsList(
entrys: entryList,
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final scores = await database.scoreDao.getAllPlayerScoresInMatch(
final scores = await database.scoreEntryDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
@@ -120,15 +119,14 @@ void main() {
group('Undesirable values', () {
test('Score & Round can have negative values', () async {
await database.scoreDao.addScore(
ScoreEntry entry = ScoreEntry(roundNumber: -2, score: -10, change: -10);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: -2,
score: -10,
change: -10,
entry: entry,
);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: -2,
@@ -141,15 +139,14 @@ void main() {
});
test('Score & Round can have zero values', () async {
await database.scoreDao.addScore(
ScoreEntry entry = ScoreEntry(roundNumber: 0, score: 0, change: 0);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 0,
score: 0,
change: 0,
entry: entry,
);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 0,
@@ -161,7 +158,7 @@ void main() {
});
test('Getting score for a non-existent entities returns null', () async {
var score = await database.scoreDao.getScore(
var score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: -1,
@@ -169,14 +166,14 @@ void main() {
expect(score, isNull);
score = await database.scoreDao.getScore(
score = await database.scoreEntryDao.getScore(
playerId: 'non-existin-player',
matchId: testMatch1.id,
);
expect(score, isNull);
score = await database.scoreDao.getScore(
score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: 'non-existing-match',
);
@@ -185,23 +182,20 @@ void main() {
});
test('Getting score for a non-match player returns null', () async {
await database.scoreDao.addScore(
ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer3.id,
matchId: testMatch2.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry,
);
var score = await database.scoreDao.getScore(
var score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch2.id,
roundNumber: 1,
@@ -213,29 +207,26 @@ void main() {
group('Scores in matches', () {
test('getAllMatchScores()', () async {
await database.scoreDao.addScore(
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 20, change: 20);
ScoreEntry entry3 = ScoreEntry(roundNumber: 2, score: 25, change: 15);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry1,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer2.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 20,
change: 20,
entry: entry2,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 25,
change: 15,
entry: entry3,
);
final scores = await database.scoreDao.getAllMatchScores(
final scores = await database.scoreEntryDao.getAllMatchScores(
matchId: testMatch1.id,
);
@@ -245,7 +236,7 @@ void main() {
});
test('getAllMatchScores() with no scores saved', () async {
final scores = await database.scoreDao.getAllMatchScores(
final scores = await database.scoreEntryDao.getAllMatchScores(
matchId: testMatch1.id,
);
@@ -253,32 +244,25 @@ void main() {
});
test('getAllPlayerScoresInMatch()', () async {
await database.scoreDao.addScore(
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 25, change: 15);
ScoreEntry entry3 = ScoreEntry(roundNumber: 1, score: 30, change: 30);
await database.scoreEntryDao.addScoresAsList(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entrys: [entry1, entry2],
);
await database.scoreDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 25,
change: 15,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer2.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 30,
change: 30,
entry: entry3,
);
final playerScores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final playerScores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(playerScores.length, 2);
expect(playerScores[0].roundNumber, 1);
@@ -290,43 +274,44 @@ void main() {
});
test('getAllPlayerScoresInMatch() with no scores saved', () async {
final playerScores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final playerScores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(playerScores.isEmpty, true);
});
test('Scores are isolated across different matches', () async {
await database.scoreDao.addScore(
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 50, change: 50);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry1,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch2.id,
roundNumber: 1,
score: 50,
change: 50,
entry: entry2,
);
final match1Scores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final match1Scores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(match1Scores.length, 1);
expect(match1Scores[0].score, 10);
expect(match1Scores[0].change, 10);
final match2Scores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch2.id,
);
final match2Scores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch2.id,
);
expect(match2Scores.length, 1);
expect(match2Scores[0].score, 50);
@@ -336,33 +321,29 @@ void main() {
group('Updating scores', () {
test('updateScore()', () async {
await database.scoreDao.addScore(
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 15, change: 5);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: entry1,
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 15,
change: 5,
entry: entry2,
);
final updated = await database.scoreDao.updateScore(
final updated = await database.scoreEntryDao.updateScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
newScore: 50,
newChange: 40,
newEntry: ScoreEntry(roundNumber: 2, score: 50, change: 40),
);
expect(updated, true);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
@@ -374,12 +355,10 @@ void main() {
});
test('Updating a non-existent score returns false', () async {
final updated = await database.scoreDao.updateScore(
final updated = await database.scoreEntryDao.updateScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
newScore: 20,
newChange: 20,
newEntry: ScoreEntry(roundNumber: 1, score: 20, change: 20),
);
expect(updated, false);
@@ -388,15 +367,13 @@ void main() {
group('Deleting scores', () {
test('deleteScore() ', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
final deleted = await database.scoreDao.deleteScore(
final deleted = await database.scoreEntryDao.deleteScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
@@ -404,7 +381,7 @@ void main() {
expect(deleted, true);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
@@ -414,7 +391,7 @@ void main() {
});
test('Deleting a non-existent score returns false', () async {
final deleted = await database.scoreDao.deleteScore(
final deleted = await database.scoreEntryDao.deleteScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
@@ -424,149 +401,130 @@ void main() {
});
test('deleteAllScoresForMatch() works correctly', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer2.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 20,
change: 20,
entry: ScoreEntry(roundNumber: 1, score: 20, change: 20),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch2.id,
roundNumber: 1,
score: 15,
change: 15,
entry: ScoreEntry(roundNumber: 1, score: 15, change: 15),
);
final deleted = await database.scoreDao.deleteAllScoresForMatch(
final deleted = await database.scoreEntryDao.deleteAllScoresForMatch(
matchId: testMatch1.id,
);
expect(deleted, true);
final match1Scores = await database.scoreDao.getAllMatchScores(
final match1Scores = await database.scoreEntryDao.getAllMatchScores(
matchId: testMatch1.id,
);
expect(match1Scores.length, 0);
final match2Scores = await database.scoreDao.getAllMatchScores(
final match2Scores = await database.scoreEntryDao.getAllMatchScores(
matchId: testMatch2.id,
);
expect(match2Scores.length, 1);
});
test('deleteAllScoresForPlayerInMatch() works correctly', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 15,
change: 5,
entry: ScoreEntry(roundNumber: 2, score: 15, change: 5),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer2.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 6,
change: 6,
entry: ScoreEntry(roundNumber: 1, score: 6, change: 6),
);
final deleted = await database.scoreDao.deleteAllScoresForPlayerInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final deleted = await database.scoreEntryDao
.deleteAllScoresForPlayerInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(deleted, true);
final player1Scores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final player1Scores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(player1Scores.length, 0);
final player2Scores = await database.scoreDao.getAllPlayerScoresInMatch(
playerId: testPlayer2.id,
matchId: testMatch1.id,
);
final player2Scores = await database.scoreEntryDao
.getAllPlayerScoresInMatch(
playerId: testPlayer2.id,
matchId: testMatch1.id,
);
expect(player2Scores.length, 1);
});
});
group('Score Aggregations & Edge Cases', () {
test('getLatestRoundNumber()', () async {
var latestRound = await database.scoreDao.getLatestRoundNumber(
var latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, isNull);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
latestRound = await database.scoreDao.getLatestRoundNumber(
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 1);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 5,
score: 50,
change: 40,
entry: ScoreEntry(roundNumber: 5, score: 50, change: 40),
);
latestRound = await database.scoreDao.getLatestRoundNumber(
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 5);
});
test('getLatestRoundNumber() with non-consecutive rounds', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 5,
score: 50,
change: 40,
entry: ScoreEntry(roundNumber: 5, score: 50, change: 40),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 3,
score: 30,
change: 20,
entry: ScoreEntry(roundNumber: 3, score: 30, change: 20),
);
final latestRound = await database.scoreDao.getLatestRoundNumber(
final latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
@@ -574,35 +532,29 @@ void main() {
});
test('getTotalScoreForPlayer()', () async {
var totalScore = await database.scoreDao.getTotalScoreForPlayer(
var totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(totalScore, 0);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 25,
change: 15,
entry: ScoreEntry(roundNumber: 2, score: 25, change: 15),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 3,
score: 40,
change: 15,
entry: ScoreEntry(roundNumber: 3, score: 40, change: 15),
);
totalScore = await database.scoreDao.getTotalScoreForPlayer(
totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
@@ -610,29 +562,23 @@ void main() {
});
test('getTotalScoreForPlayer() ignores round score', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 2,
score: 25,
change: 25,
entry: ScoreEntry(roundNumber: 2, score: 25, change: 25),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 25,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 25, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 3,
score: 25,
change: 25,
entry: ScoreEntry(roundNumber: 3, score: 25, change: 25),
);
final totalScore = await database.scoreDao.getTotalScoreForPlayer(
final totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
@@ -642,22 +588,18 @@ void main() {
});
test('Adding the same score twice replaces the existing one', () async {
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 10,
change: 10,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreDao.addScore(
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
score: 20,
change: 20,
entry: ScoreEntry(roundNumber: 1, score: 20, change: 20),
);
final score = await database.scoreDao.getScore(
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
@@ -671,100 +613,116 @@ void main() {
group('Handling Winner', () {
test('hasWinner() works correctly', () async {
var hasWinner = await database.scoreDao.hasWinner(
var hasWinner = await database.scoreEntryDao.hasWinner(
matchId: testMatch1.id,
);
expect(hasWinner, false);
await database.scoreDao.setWinner(
await database.scoreEntryDao.setWinner(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
hasWinner = await database.scoreDao.hasWinner(matchId: testMatch1.id);
hasWinner = await database.scoreEntryDao.hasWinner(
matchId: testMatch1.id,
);
expect(hasWinner, true);
});
test('getWinnersForMatch() returns correct winner', () async {
var winner = await database.scoreDao.getWinner(matchId: testMatch1.id);
var winner = await database.scoreEntryDao.getWinner(
matchId: testMatch1.id,
);
expect(winner, isNull);
await database.scoreDao.setWinner(
await database.scoreEntryDao.setWinner(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
winner = await database.scoreDao.getWinner(matchId: testMatch1.id);
winner = await database.scoreEntryDao.getWinner(matchId: testMatch1.id);
expect(winner, isNotNull);
expect(winner!.id, testPlayer1.id);
});
test('removeWinner() works correctly', () async {
var removed = await database.scoreDao.removeWinner(
var removed = await database.scoreEntryDao.removeWinner(
matchId: testMatch1.id,
);
expect(removed, false);
await database.scoreDao.setWinner(
await database.scoreEntryDao.setWinner(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
removed = await database.scoreDao.removeWinner(matchId: testMatch1.id);
removed = await database.scoreEntryDao.removeWinner(
matchId: testMatch1.id,
);
expect(removed, true);
var winner = await database.scoreDao.getWinner(matchId: testMatch1.id);
var winner = await database.scoreEntryDao.getWinner(
matchId: testMatch1.id,
);
expect(winner, isNull);
});
});
group('Handling Looser', () {
test('hasLooser() works correctly', () async {
var hasLooser = await database.scoreDao.hasLooser(
var hasLooser = await database.scoreEntryDao.hasLooser(
matchId: testMatch1.id,
);
expect(hasLooser, false);
await database.scoreDao.setLooser(
await database.scoreEntryDao.setLooser(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
hasLooser = await database.scoreDao.hasLooser(matchId: testMatch1.id);
hasLooser = await database.scoreEntryDao.hasLooser(
matchId: testMatch1.id,
);
expect(hasLooser, true);
});
test('getLooser() returns correct winner', () async {
var looser = await database.scoreDao.getLooser(matchId: testMatch1.id);
var looser = await database.scoreEntryDao.getLooser(
matchId: testMatch1.id,
);
expect(looser, isNull);
await database.scoreDao.setLooser(
await database.scoreEntryDao.setLooser(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
looser = await database.scoreDao.getLooser(matchId: testMatch1.id);
looser = await database.scoreEntryDao.getLooser(matchId: testMatch1.id);
expect(looser, isNotNull);
expect(looser!.id, testPlayer1.id);
});
test('removeLooser() works correctly', () async {
var removed = await database.scoreDao.removeLooser(
var removed = await database.scoreEntryDao.removeLooser(
matchId: testMatch1.id,
);
expect(removed, false);
await database.scoreDao.setLooser(
await database.scoreEntryDao.setLooser(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
removed = await database.scoreDao.removeLooser(matchId: testMatch1.id);
removed = await database.scoreEntryDao.removeLooser(
matchId: testMatch1.id,
);
expect(removed, true);
var looser = await database.scoreDao.getLooser(matchId: testMatch1.id);
var looser = await database.scoreEntryDao.getLooser(
matchId: testMatch1.id,
);
expect(looser, isNull);
});
});

View File

@@ -0,0 +1,926 @@
import 'dart:convert';
import 'package:clock/clock.dart';
import 'package:drift/drift.dart' hide isNull, isNotNull;
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:tallee/core/enums.dart';
import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart';
import 'package:tallee/data/models/match.dart';
import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score_entry.dart';
import 'package:tallee/data/models/team.dart';
import 'package:tallee/services/data_transfer_service.dart';
void main() {
late AppDatabase database;
late Player testPlayer1;
late Player testPlayer2;
late Player testPlayer3;
late Game testGame;
late Group testGroup;
late Team testTeam;
late Match testMatch;
final fixedDate = DateTime(2025, 11, 19, 0, 11, 23);
final fakeClock = Clock(() => fixedDate);
setUp(() {
database = AppDatabase(
DatabaseConnection(
NativeDatabase.memory(),
closeStreamsSynchronously: true,
),
);
withClock(fakeClock, () {
testPlayer1 = Player(name: 'Alice', description: 'First test player');
testPlayer2 = Player(name: 'Bob', description: 'Second test player');
testPlayer3 = Player(name: 'Charlie', description: 'Third player');
testGame = Game(
name: 'Chess',
ruleset: Ruleset.singleWinner,
description: 'Strategic board game',
color: GameColor.blue,
icon: 'chess_icon',
);
testGroup = Group(
name: 'Test Group',
description: 'Group for testing',
members: [testPlayer1, testPlayer2],
);
testTeam = Team(name: 'Test Team', members: [testPlayer1, testPlayer2]);
testMatch = Match(
name: 'Test Match',
game: testGame,
group: testGroup,
players: [testPlayer1, testPlayer2],
notes: 'Test notes',
scores: {
testPlayer1.id: [
ScoreEntry(roundNumber: 1, score: 10, change: 10),
ScoreEntry(roundNumber: 2, score: 20, change: 10),
],
testPlayer2.id: [
ScoreEntry(roundNumber: 1, score: 15, change: 15),
ScoreEntry(roundNumber: 2, score: 25, change: 10),
],
},
);
});
});
tearDown(() async {
await database.close();
});
// Helper for getting BuildContext
Future<BuildContext> getContext(WidgetTester tester) async {
// Minimal widget with Provider
await tester.pumpWidget(
Provider<AppDatabase>.value(
value: database,
child: MaterialApp(
home: Builder(
builder: (context) {
return Container();
},
),
),
),
);
final BuildContext context = tester.element(find.byType(Container));
return context;
}
group('DataTransferService Tests', () {
testWidgets('deleteAllData()', (tester) async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame);
await database.groupDao.addGroup(group: testGroup);
await database.teamDao.addTeam(team: testTeam);
await database.matchDao.addMatch(match: testMatch);
var playerCount = await database.playerDao.getPlayerCount();
var gameCount = await database.gameDao.getGameCount();
var groupCount = await database.groupDao.getGroupCount();
var teamCount = await database.teamDao.getTeamCount();
var matchCount = await database.matchDao.getMatchCount();
expect(playerCount, greaterThan(0));
expect(gameCount, greaterThan(0));
expect(groupCount, greaterThan(0));
expect(teamCount, greaterThan(0));
expect(matchCount, greaterThan(0));
final ctx = await getContext(tester);
await DataTransferService.deleteAllData(ctx);
playerCount = await database.playerDao.getPlayerCount();
gameCount = await database.gameDao.getGameCount();
groupCount = await database.groupDao.getGroupCount();
teamCount = await database.teamDao.getTeamCount();
matchCount = await database.matchDao.getMatchCount();
expect(playerCount, 0);
expect(gameCount, 0);
expect(groupCount, 0);
expect(teamCount, 0);
expect(matchCount, 0);
});
group('getAppDataAsJson()', () {
group('Whole export', () {
testWidgets('Exporting app data works correctly', (tester) async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer2);
await database.gameDao.addGame(game: testGame);
await database.groupDao.addGroup(group: testGroup);
await database.teamDao.addTeam(team: testTeam);
await database.matchDao.addMatch(match: testMatch);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
expect(jsonString, isNotEmpty);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
expect(decoded.containsKey('players'), true);
expect(decoded.containsKey('games'), true);
expect(decoded.containsKey('groups'), true);
expect(decoded.containsKey('teams'), true);
expect(decoded.containsKey('matches'), true);
final players = decoded['players'] as List<dynamic>;
final games = decoded['games'] as List<dynamic>;
final groups = decoded['groups'] as List<dynamic>;
final teams = decoded['teams'] as List<dynamic>;
final matches = decoded['matches'] as List<dynamic>;
expect(players.length, 2);
expect(games.length, 1);
expect(groups.length, 1);
expect(teams.length, 1);
expect(matches.length, 1);
});
testWidgets('Exporting empty data works correctly', (tester) async {
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final players = decoded['players'] as List<dynamic>;
final games = decoded['games'] as List<dynamic>;
final groups = decoded['groups'] as List<dynamic>;
final teams = decoded['teams'] as List<dynamic>;
final matches = decoded['matches'] as List<dynamic>;
expect(players, isEmpty);
expect(games, isEmpty);
expect(groups, isEmpty);
expect(teams, isEmpty);
expect(matches, isEmpty);
});
});
group('Checking specific data', () {
testWidgets('Player data is correct', (tester) async {
await database.playerDao.addPlayer(player: testPlayer1);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final players = decoded['players'] as List<dynamic>;
final playerData = players[0] as Map<String, dynamic>;
expect(playerData['id'], testPlayer1.id);
expect(playerData['name'], testPlayer1.name);
expect(playerData['description'], testPlayer1.description);
expect(
playerData['createdAt'],
testPlayer1.createdAt.toIso8601String(),
);
});
testWidgets('Game data is correct', (tester) async {
await database.gameDao.addGame(game: testGame);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final games = decoded['games'] as List<dynamic>;
final gameData = games[0] as Map<String, dynamic>;
expect(gameData['id'], testGame.id);
expect(gameData['name'], testGame.name);
expect(gameData['ruleset'], testGame.ruleset.name);
expect(gameData['description'], testGame.description);
expect(gameData['color'], testGame.color.name);
expect(gameData['icon'], testGame.icon);
});
testWidgets('Group data is correct', (tester) async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer2);
await database.groupDao.addGroup(group: testGroup);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final groups = decoded['groups'] as List<dynamic>;
final groupData = groups[0] as Map<String, dynamic>;
expect(groupData['id'], testGroup.id);
expect(groupData['name'], testGroup.name);
expect(groupData['description'], testGroup.description);
expect(groupData['memberIds'], isA<List>());
final memberIds = groupData['memberIds'] as List<dynamic>;
expect(memberIds.length, 2);
expect(memberIds, containsAll([testPlayer1.id, testPlayer2.id]));
});
testWidgets('Team data is correct', (tester) async {
await database.teamDao.addTeam(team: testTeam);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final teams = decoded['teams'] as List<dynamic>;
expect(teams.length, 1);
final teamData = teams[0] as Map<String, dynamic>;
expect(teamData['id'], testTeam.id);
expect(teamData['name'], testTeam.name);
expect(teamData['memberIds'], isA<List>());
// Note: In this system, teams don't have independent members.
// Team members are only tracked through matches via PlayerMatchTable.
// Therefore, memberIds will be empty for standalone teams.
final memberIds = teamData['memberIds'] as List<dynamic>;
expect(memberIds, isEmpty);
});
testWidgets('Match data is correct', (tester) async {
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2],
);
await database.gameDao.addGame(game: testGame);
await database.groupDao.addGroup(group: testGroup);
await database.matchDao.addMatch(match: testMatch);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
final matchData = matches[0] as Map<String, dynamic>;
expect(matchData['id'], testMatch.id);
expect(matchData['name'], testMatch.name);
expect(matchData['gameId'], testGame.id);
expect(matchData['groupId'], testGroup.id);
expect(matchData['playerIds'], isA<List>());
expect(matchData['notes'], testMatch.notes);
// Check player ids
final playerIds = matchData['playerIds'] as List<dynamic>;
expect(playerIds.length, 2);
expect(playerIds, containsAll([testPlayer1.id, testPlayer2.id]));
// Check scores structure
final scoresJson = matchData['scores'] as Map<String, dynamic>;
expect(scoresJson, isA<Map<String, dynamic>>());
final scores = scoresJson.map(
(playerId, scoreList) => MapEntry(
playerId,
(scoreList as List)
.map((s) => ScoreEntry.fromJson(s as Map<String, dynamic>))
.toList(),
),
);
expect(scores, isA<Map<String, List<ScoreEntry>>>());
/* Player 1 scores */
// General structure
expect(scores[testPlayer1.id], isNotNull);
expect(scores[testPlayer1.id]!.length, 2);
// Round 1
expect(scores[testPlayer1.id]![0].roundNumber, 1);
expect(scores[testPlayer1.id]![0].score, 10);
expect(scores[testPlayer1.id]![0].change, 10);
// Round 2
expect(scores[testPlayer1.id]![1].roundNumber, 2);
expect(scores[testPlayer1.id]![1].score, 20);
expect(scores[testPlayer1.id]![1].change, 10);
/* Player 2 scores */
// General structure
expect(scores[testPlayer2.id], isNotNull);
expect(scores[testPlayer2.id]!.length, 2);
// Round 1
expect(scores[testPlayer2.id]![0].roundNumber, 1);
expect(scores[testPlayer2.id]![0].score, 15);
expect(scores[testPlayer2.id]![0].change, 15);
// Round 2
expect(scores[testPlayer2.id]![1].roundNumber, 2);
expect(scores[testPlayer2.id]![1].score, 25);
expect(scores[testPlayer2.id]![1].change, 10);
});
testWidgets('Match without group is handled correctly', (tester) async {
final matchWithoutGroup = Match(
name: 'No Group Match',
game: testGame,
group: null,
players: [testPlayer1],
notes: 'No group',
);
await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame);
await database.matchDao.addMatch(match: matchWithoutGroup);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
final matchData = matches[0] as Map<String, dynamic>;
expect(matchData['groupId'], isNull);
});
testWidgets('Match with endedAt is handled correctly', (tester) async {
final endedDate = DateTime(2025, 12, 1, 10, 0, 0);
final endedMatch = Match(
name: 'Ended Match',
game: testGame,
players: [testPlayer1],
endedAt: endedDate,
notes: 'Finished',
);
await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame);
await database.matchDao.addMatch(match: endedMatch);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
final matchData = matches[0] as Map<String, dynamic>;
expect(matchData['endedAt'], endedDate.toIso8601String());
});
testWidgets('Structure is consistent', (tester) async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame);
final ctx = await getContext(tester);
final jsonString1 = await DataTransferService.getAppDataAsJson(ctx);
final jsonString2 = await DataTransferService.getAppDataAsJson(ctx);
expect(jsonString1, equals(jsonString2));
});
testWidgets('Empty match notes is handled correctly', (tester) async {
final matchWithEmptyNotes = Match(
name: 'Empty Notes Match',
game: testGame,
players: [testPlayer1],
notes: '',
);
await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame);
await database.matchDao.addMatch(match: matchWithEmptyNotes);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
final matchData = matches[0] as Map<String, dynamic>;
expect(matchData['notes'], '');
});
testWidgets('Multiple players in match is handled correctly', (
tester,
) async {
final multiPlayerMatch = Match(
name: 'Multi Player Match',
game: testGame,
players: [testPlayer1, testPlayer2, testPlayer3],
notes: 'Three players',
);
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2, testPlayer3],
);
await database.gameDao.addGame(game: testGame);
await database.matchDao.addMatch(match: multiPlayerMatch);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
final matchData = matches[0] as Map<String, dynamic>;
final playerIds = matchData['playerIds'] as List<dynamic>;
expect(playerIds.length, 3);
expect(
playerIds,
containsAll([testPlayer1.id, testPlayer2.id, testPlayer3.id]),
);
});
testWidgets('All game colors are handled correctly', (tester) async {
final games = [
Game(
name: 'Red Game',
ruleset: Ruleset.singleWinner,
color: GameColor.red,
icon: 'icon',
),
Game(
name: 'Blue Game',
ruleset: Ruleset.singleWinner,
color: GameColor.blue,
icon: 'icon',
),
Game(
name: 'Green Game',
ruleset: Ruleset.singleWinner,
color: GameColor.green,
icon: 'icon',
),
];
await database.gameDao.addGamesAsList(games: games);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final gamesJson = decoded['games'] as List<dynamic>;
expect(gamesJson.length, 3);
expect(
gamesJson.map((g) => g['color']),
containsAll(['red', 'blue', 'green']),
);
});
testWidgets('All rulesets are handled correctly', (tester) async {
final games = [
Game(
name: 'Highest Score Game',
ruleset: Ruleset.highestScore,
color: GameColor.blue,
icon: 'icon',
),
Game(
name: 'Lowest Score Game',
ruleset: Ruleset.lowestScore,
color: GameColor.blue,
icon: 'icon',
),
Game(
name: 'Single Winner',
ruleset: Ruleset.singleWinner,
color: GameColor.blue,
icon: 'icon',
),
];
await database.gameDao.addGamesAsList(games: games);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final gamesJson = decoded['games'] as List<dynamic>;
expect(gamesJson.length, 3);
expect(
gamesJson.map((g) => g['ruleset']),
containsAll(['highestScore', 'lowestScore', 'singleWinner']),
);
});
});
});
group('Parse Methods', () {
test('parsePlayersFromJson()', () {
final jsonMap = {
'players': [
{
'id': testPlayer1.id,
'name': testPlayer1.name,
'description': testPlayer1.description,
'createdAt': testPlayer1.createdAt.toIso8601String(),
},
{
'id': testPlayer2.id,
'name': testPlayer2.name,
'description': testPlayer2.description,
'createdAt': testPlayer2.createdAt.toIso8601String(),
},
],
};
final players = DataTransferService.parsePlayersFromJson(jsonMap);
expect(players.length, 2);
expect(players[0].id, testPlayer1.id);
expect(players[0].name, testPlayer1.name);
expect(players[1].id, testPlayer2.id);
expect(players[1].name, testPlayer2.name);
});
test('parsePlayersFromJson() empty list', () {
final jsonMap = {'players': []};
final players = DataTransferService.parsePlayersFromJson(jsonMap);
expect(players, isEmpty);
});
test('parsePlayersFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final players = DataTransferService.parsePlayersFromJson(jsonMap);
expect(players, isEmpty);
});
test('parseGamesFromJson()', () {
final jsonMap = {
'games': [
{
'id': testGame.id,
'name': testGame.name,
'ruleset': testGame.ruleset.name,
'description': testGame.description,
'color': testGame.color.name,
'icon': testGame.icon,
'createdAt': testGame.createdAt.toIso8601String(),
},
],
};
final games = DataTransferService.parseGamesFromJson(jsonMap);
expect(games.length, 1);
expect(games[0].id, testGame.id);
expect(games[0].name, testGame.name);
expect(games[0].ruleset, testGame.ruleset);
});
test('parseGamesFromJson() empty list', () {
final jsonMap = {'games': []};
final games = DataTransferService.parseGamesFromJson(jsonMap);
expect(games, isEmpty);
});
test('parseGamesFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final games = DataTransferService.parseGamesFromJson(jsonMap);
expect(games, isEmpty);
});
test('parseGroupsFromJson()', () {
final playerById = {
testPlayer1.id: testPlayer1,
testPlayer2.id: testPlayer2,
};
final jsonMap = {
'groups': [
{
'id': testGroup.id,
'name': testGroup.name,
'description': testGroup.description,
'memberIds': [testPlayer1.id, testPlayer2.id],
'createdAt': testGroup.createdAt.toIso8601String(),
},
],
};
final groups = DataTransferService.parseGroupsFromJson(
jsonMap,
playerById,
);
expect(groups.length, 1);
expect(groups[0].id, testGroup.id);
expect(groups[0].name, testGroup.name);
expect(groups[0].members.length, 2);
expect(groups[0].members[0].id, testPlayer1.id);
expect(groups[0].members[1].id, testPlayer2.id);
});
test('parseGroupsFromJson() empty list', () {
final jsonMap = {'groups': []};
final groups = DataTransferService.parseGroupsFromJson(jsonMap, {});
expect(groups, isEmpty);
});
test('parseGroupsFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final groups = DataTransferService.parseGroupsFromJson(jsonMap, {});
expect(groups, isEmpty);
});
test('parseGroupsFromJson() ignores invalid player ids', () {
final playerById = {testPlayer1.id: testPlayer1};
final jsonMap = {
'groups': [
{
'id': testGroup.id,
'name': testGroup.name,
'description': testGroup.description,
'memberIds': [testPlayer1.id, 'invalid-id'],
'createdAt': testGroup.createdAt.toIso8601String(),
},
],
};
final groups = DataTransferService.parseGroupsFromJson(
jsonMap,
playerById,
);
expect(groups.length, 1);
expect(groups[0].members.length, 1);
expect(groups[0].members[0].id, testPlayer1.id);
});
test('parseTeamsFromJson()', () {
final playerById = {testPlayer1.id: testPlayer1};
final jsonMap = {
'teams': [
{
'id': testTeam.id,
'name': testTeam.name,
'memberIds': [testPlayer1.id],
'createdAt': testTeam.createdAt.toIso8601String(),
},
],
};
final teams = DataTransferService.parseTeamsFromJson(
jsonMap,
playerById,
);
expect(teams.length, 1);
expect(teams[0].id, testTeam.id);
expect(teams[0].name, testTeam.name);
expect(teams[0].members.length, 1);
expect(teams[0].members[0].id, testPlayer1.id);
});
test('parseTeamsFromJson() empty list', () {
final jsonMap = {'teams': []};
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {});
expect(teams, isEmpty);
});
test('parseTeamsFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {});
expect(teams, isEmpty);
});
test('parseMatchesFromJson()', () {
final playerById = {
testPlayer1.id: testPlayer1,
testPlayer2.id: testPlayer2,
};
final gameById = {testGame.id: testGame};
final groupById = {testGroup.id: testGroup};
final jsonMap = {
'matches': [
{
'id': testMatch.id,
'name': testMatch.name,
'gameId': testGame.id,
'groupId': testGroup.id,
'playerIds': [testPlayer1.id, testPlayer2.id],
'notes': testMatch.notes,
'createdAt': testMatch.createdAt.toIso8601String(),
},
],
};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
gameById,
groupById,
playerById,
);
expect(matches.length, 1);
expect(matches[0].id, testMatch.id);
expect(matches[0].name, testMatch.name);
expect(matches[0].game.id, testGame.id);
expect(matches[0].group?.id, testGroup.id);
expect(matches[0].players.length, 2);
});
test('parseMatchesFromJson() empty list', () {
final jsonMap = {'teams': []};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
{},
{},
{},
);
expect(matches, isEmpty);
});
test('parseMatchesFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
{},
{},
{},
);
expect(matches, isEmpty);
});
test('parseMatchesFromJson() creates unknown game for missing game', () {
final playerById = {testPlayer1.id: testPlayer1};
final gameById = <String, Game>{};
final groupById = <String, Group>{};
final jsonMap = {
'matches': [
{
'id': testMatch.id,
'name': testMatch.name,
'gameId': 'non-existent-game-id',
'playerIds': [testPlayer1.id],
'notes': '',
'createdAt': testMatch.createdAt.toIso8601String(),
},
],
};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
gameById,
groupById,
playerById,
);
expect(matches.length, 1);
expect(matches[0].game.name, 'Unknown');
expect(matches[0].game.ruleset, Ruleset.singleWinner);
});
test('parseMatchesFromJson() handles null group', () {
final playerById = {testPlayer1.id: testPlayer1};
final gameById = {testGame.id: testGame};
final groupById = <String, Group>{};
final jsonMap = {
'matches': [
{
'id': testMatch.id,
'name': testMatch.name,
'gameId': testGame.id,
'groupId': null,
'playerIds': [testPlayer1.id],
'notes': '',
'createdAt': testMatch.createdAt.toIso8601String(),
},
],
};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
gameById,
groupById,
playerById,
);
expect(matches.length, 1);
expect(matches[0].group, isNull);
});
test('parseMatchesFromJson() handles endedAt', () {
final playerById = {testPlayer1.id: testPlayer1};
final gameById = {testGame.id: testGame};
final groupById = <String, Group>{};
final endedDate = DateTime(2025, 12, 1, 10, 0, 0);
final jsonMap = {
'matches': [
{
'id': testMatch.id,
'name': testMatch.name,
'gameId': testGame.id,
'playerIds': [testPlayer1.id],
'notes': '',
'createdAt': testMatch.createdAt.toIso8601String(),
'endedAt': endedDate.toIso8601String(),
},
],
};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
gameById,
groupById,
playerById,
);
expect(matches.length, 1);
expect(matches[0].endedAt, endedDate);
});
});
test('validateJsonSchema()', () async {
final validJson = json.encode({
'players': [
{
'id': testPlayer1.id,
'name': testPlayer1.name,
'description': testPlayer1.description,
'createdAt': testPlayer1.createdAt.toIso8601String(),
},
],
'games': [
{
'id': testGame.id,
'name': testGame.name,
'ruleset': testGame.ruleset.name,
'description': testGame.description,
'color': testGame.color.name,
'icon': testGame.icon,
'createdAt': testGame.createdAt.toIso8601String(),
},
],
'groups': [
{
'id': testGroup.id,
'name': testGroup.name,
'description': testGroup.description,
'memberIds': [testPlayer1.id, testPlayer2.id],
'createdAt': testGroup.createdAt.toIso8601String(),
},
],
'teams': [
{
'id': testTeam.id,
'name': testTeam.name,
'memberIds': [testPlayer1.id, testPlayer2.id],
'createdAt': testTeam.createdAt.toIso8601String(),
},
],
'matches': [
{
'id': testMatch.id,
'name': testMatch.name,
'gameId': testGame.id,
'groupId': testGroup.id,
'playerIds': [testPlayer1.id, testPlayer2.id],
'notes': testMatch.notes,
'scores': {
testPlayer1.id: [
{'roundNumber': 1, 'score': 10, 'change': 10},
{'roundNumber': 2, 'score': 20, 'change': 10},
],
testPlayer2.id: [
{'roundNumber': 1, 'score': 15, 'change': 15},
{'roundNumber': 2, 'score': 25, 'change': 10},
],
},
'createdAt': testMatch.createdAt.toIso8601String(),
'endedAt': null,
},
],
});
final isValid = await DataTransferService.validateJsonSchema(validJson);
expect(isValid, true);
});
});
}