10 Commits

15 changed files with 447 additions and 306 deletions

137
assets/schema.json Normal file
View File

@@ -0,0 +1,137 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"games": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"players": {
"type": "null"
},
"group": {
"type": "null"
},
"winner": {
"type": "string"
}
},
"required": [
"id",
"name",
"players",
"group",
"winner"
]
}
]
},
"groups": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"members": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
}
]
}
},
"required": [
"id",
"name",
"members"
]
}
]
},
"players": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
}
]
}
},
"required": [
"games",
"groups",
"players"
]
}

View File

@@ -57,7 +57,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
GameTableCompanion.insert( GameTableCompanion.insert(
id: game.id, id: game.id,
name: game.name, name: game.name,
winnerId: game.winner, winnerId: Value(game.winner),
), ),
mode: InsertMode.insertOrReplace, mode: InsertMode.insertOrReplace,
); );
@@ -88,4 +88,12 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
final result = await query.getSingleOrNull(); final result = await query.getSingleOrNull();
return result != null; return result != null;
} }
/// Deletes all games from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllGames() async {
final query = delete(gameTable);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
} }

View File

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

View File

@@ -103,4 +103,12 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
final result = await query.getSingleOrNull(); final result = await query.getSingleOrNull();
return result != null; return result != null;
} }
/// Deletes all groups from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllGroups() async {
final query = delete(groupTable);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
} }

View File

@@ -5,7 +5,6 @@ part of 'group_game_dao.dart';
// ignore_for_file: type=lint // ignore_for_file: type=lint
mixin _$GroupGameDaoMixin on DatabaseAccessor<AppDatabase> { mixin _$GroupGameDaoMixin on DatabaseAccessor<AppDatabase> {
$GroupTableTable get groupTable => attachedDatabase.groupTable; $GroupTableTable get groupTable => attachedDatabase.groupTable;
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable; $GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable;
} }

View File

@@ -70,4 +70,12 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
.getSingle(); .getSingle();
return count ?? 0; return count ?? 0;
} }
/// Deletes all players from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllPlayers() async {
final query = delete(playerTable);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
} }

View File

@@ -40,6 +40,15 @@ class AppDatabase extends _$AppDatabase {
@override @override
int get schemaVersion => 1; int get schemaVersion => 1;
@override
MigrationStrategy get migration {
return MigrationStrategy(
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
},
);
}
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase( return driftDatabase(
name: 'gametracker_db', name: 'gametracker_db',

View File

@@ -444,12 +444,9 @@ class $GameTableTable extends GameTable
late final GeneratedColumn<String> winnerId = GeneratedColumn<String>( late final GeneratedColumn<String> winnerId = GeneratedColumn<String>(
'winner_id', 'winner_id',
aliasedName, aliasedName,
false, true,
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: true, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES player_table (id) ON DELETE CASCADE',
),
); );
@override @override
List<GeneratedColumn> get $columns => [id, name, winnerId]; List<GeneratedColumn> get $columns => [id, name, winnerId];
@@ -483,8 +480,6 @@ class $GameTableTable extends GameTable
_winnerIdMeta, _winnerIdMeta,
winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta),
); );
} else if (isInserting) {
context.missing(_winnerIdMeta);
} }
return context; return context;
} }
@@ -506,7 +501,7 @@ class $GameTableTable extends GameTable
winnerId: attachedDatabase.typeMapping.read( winnerId: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}winner_id'], data['${effectivePrefix}winner_id'],
)!, ),
); );
} }
@@ -519,18 +514,16 @@ class $GameTableTable extends GameTable
class GameTableData extends DataClass implements Insertable<GameTableData> { class GameTableData extends DataClass implements Insertable<GameTableData> {
final String id; final String id;
final String name; final String name;
final String winnerId; final String? winnerId;
const GameTableData({ const GameTableData({required this.id, required this.name, this.winnerId});
required this.id,
required this.name,
required this.winnerId,
});
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{}; final map = <String, Expression>{};
map['id'] = Variable<String>(id); map['id'] = Variable<String>(id);
map['name'] = Variable<String>(name); map['name'] = Variable<String>(name);
if (!nullToAbsent || winnerId != null) {
map['winner_id'] = Variable<String>(winnerId); map['winner_id'] = Variable<String>(winnerId);
}
return map; return map;
} }
@@ -538,7 +531,9 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
return GameTableCompanion( return GameTableCompanion(
id: Value(id), id: Value(id),
name: Value(name), name: Value(name),
winnerId: Value(winnerId), winnerId: winnerId == null && nullToAbsent
? const Value.absent()
: Value(winnerId),
); );
} }
@@ -550,7 +545,7 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
return GameTableData( return GameTableData(
id: serializer.fromJson<String>(json['id']), id: serializer.fromJson<String>(json['id']),
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
winnerId: serializer.fromJson<String>(json['winnerId']), winnerId: serializer.fromJson<String?>(json['winnerId']),
); );
} }
@override @override
@@ -559,15 +554,18 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
return <String, dynamic>{ return <String, dynamic>{
'id': serializer.toJson<String>(id), 'id': serializer.toJson<String>(id),
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'winnerId': serializer.toJson<String>(winnerId), 'winnerId': serializer.toJson<String?>(winnerId),
}; };
} }
GameTableData copyWith({String? id, String? name, String? winnerId}) => GameTableData copyWith({
GameTableData( String? id,
String? name,
Value<String?> winnerId = const Value.absent(),
}) => GameTableData(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
winnerId: winnerId ?? this.winnerId, winnerId: winnerId.present ? winnerId.value : this.winnerId,
); );
GameTableData copyWithCompanion(GameTableCompanion data) { GameTableData copyWithCompanion(GameTableCompanion data) {
return GameTableData( return GameTableData(
@@ -601,7 +599,7 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
class GameTableCompanion extends UpdateCompanion<GameTableData> { class GameTableCompanion extends UpdateCompanion<GameTableData> {
final Value<String> id; final Value<String> id;
final Value<String> name; final Value<String> name;
final Value<String> winnerId; final Value<String?> winnerId;
final Value<int> rowid; final Value<int> rowid;
const GameTableCompanion({ const GameTableCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
@@ -612,11 +610,10 @@ class GameTableCompanion extends UpdateCompanion<GameTableData> {
GameTableCompanion.insert({ GameTableCompanion.insert({
required String id, required String id,
required String name, required String name,
required String winnerId, this.winnerId = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
name = Value(name), name = Value(name);
winnerId = Value(winnerId);
static Insertable<GameTableData> custom({ static Insertable<GameTableData> custom({
Expression<String>? id, Expression<String>? id,
Expression<String>? name, Expression<String>? name,
@@ -634,7 +631,7 @@ class GameTableCompanion extends UpdateCompanion<GameTableData> {
GameTableCompanion copyWith({ GameTableCompanion copyWith({
Value<String>? id, Value<String>? id,
Value<String>? name, Value<String>? name,
Value<String>? winnerId, Value<String?>? winnerId,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
return GameTableCompanion( return GameTableCompanion(
@@ -1381,13 +1378,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
]; ];
@override @override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([
WritePropagation(
on: TableUpdateQuery.onTableName(
'player_table',
limitUpdateKind: UpdateKind.delete,
),
result: [TableUpdate('game_table', kind: UpdateKind.delete)],
),
WritePropagation( WritePropagation(
on: TableUpdateQuery.onTableName( on: TableUpdateQuery.onTableName(
'player_table', 'player_table',
@@ -1450,24 +1440,6 @@ final class $$PlayerTableTableReferences
extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> {
$$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult);
static MultiTypedResultKey<$GameTableTable, List<GameTableData>>
_gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.gameTable,
aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId),
);
$$GameTableTableProcessedTableManager get gameTableRefs {
final manager = $$GameTableTableTableManager(
$_db,
$_db.gameTable,
).filter((f) => f.winnerId.id.sqlEquals($_itemColumn<String>('id')!));
final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db));
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache),
);
}
static MultiTypedResultKey<$PlayerGroupTableTable, List<PlayerGroupTableData>> static MultiTypedResultKey<$PlayerGroupTableTable, List<PlayerGroupTableData>>
_playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.playerGroupTable, db.playerGroupTable,
@@ -1534,31 +1506,6 @@ class $$PlayerTableTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
Expression<bool> gameTableRefs(
Expression<bool> Function($$GameTableTableFilterComposer f) f,
) {
final $$GameTableTableFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.gameTable,
getReferencedColumn: (t) => t.winnerId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$GameTableTableFilterComposer(
$db: $db,
$table: $db.gameTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return f(composer);
}
Expression<bool> playerGroupTableRefs( Expression<bool> playerGroupTableRefs(
Expression<bool> Function($$PlayerGroupTableTableFilterComposer f) f, Expression<bool> Function($$PlayerGroupTableTableFilterComposer f) f,
) { ) {
@@ -1645,31 +1592,6 @@ class $$PlayerTableTableAnnotationComposer
GeneratedColumn<String> get name => GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column); $composableBuilder(column: $table.name, builder: (column) => column);
Expression<T> gameTableRefs<T extends Object>(
Expression<T> Function($$GameTableTableAnnotationComposer a) f,
) {
final $$GameTableTableAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.gameTable,
getReferencedColumn: (t) => t.winnerId,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$GameTableTableAnnotationComposer(
$db: $db,
$table: $db.gameTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return f(composer);
}
Expression<T> playerGroupTableRefs<T extends Object>( Expression<T> playerGroupTableRefs<T extends Object>(
Expression<T> Function($$PlayerGroupTableTableAnnotationComposer a) f, Expression<T> Function($$PlayerGroupTableTableAnnotationComposer a) f,
) { ) {
@@ -1735,7 +1657,6 @@ class $$PlayerTableTableTableManager
(PlayerTableData, $$PlayerTableTableReferences), (PlayerTableData, $$PlayerTableTableReferences),
PlayerTableData, PlayerTableData,
PrefetchHooks Function({ PrefetchHooks Function({
bool gameTableRefs,
bool playerGroupTableRefs, bool playerGroupTableRefs,
bool playerGameTableRefs, bool playerGameTableRefs,
}) })
@@ -1773,42 +1694,16 @@ class $$PlayerTableTableTableManager
) )
.toList(), .toList(),
prefetchHooksCallback: prefetchHooksCallback:
({ ({playerGroupTableRefs = false, playerGameTableRefs = false}) {
gameTableRefs = false,
playerGroupTableRefs = false,
playerGameTableRefs = false,
}) {
return PrefetchHooks( return PrefetchHooks(
db: db, db: db,
explicitlyWatchedTables: [ explicitlyWatchedTables: [
if (gameTableRefs) db.gameTable,
if (playerGroupTableRefs) db.playerGroupTable, if (playerGroupTableRefs) db.playerGroupTable,
if (playerGameTableRefs) db.playerGameTable, if (playerGameTableRefs) db.playerGameTable,
], ],
addJoins: null, addJoins: null,
getPrefetchedDataCallback: (items) async { getPrefetchedDataCallback: (items) async {
return [ return [
if (gameTableRefs)
await $_getPrefetchedData<
PlayerTableData,
$PlayerTableTable,
GameTableData
>(
currentTable: table,
referencedTable: $$PlayerTableTableReferences
._gameTableRefsTable(db),
managerFromTypedResult: (p0) =>
$$PlayerTableTableReferences(
db,
table,
p0,
).gameTableRefs,
referencedItemsForCurrentItem:
(item, referencedItems) => referencedItems.where(
(e) => e.winnerId == item.id,
),
typedResults: items,
),
if (playerGroupTableRefs) if (playerGroupTableRefs)
await $_getPrefetchedData< await $_getPrefetchedData<
PlayerTableData, PlayerTableData,
@@ -1872,7 +1767,6 @@ typedef $$PlayerTableTableProcessedTableManager =
(PlayerTableData, $$PlayerTableTableReferences), (PlayerTableData, $$PlayerTableTableReferences),
PlayerTableData, PlayerTableData,
PrefetchHooks Function({ PrefetchHooks Function({
bool gameTableRefs,
bool playerGroupTableRefs, bool playerGroupTableRefs,
bool playerGameTableRefs, bool playerGameTableRefs,
}) })
@@ -2227,14 +2121,14 @@ typedef $$GameTableTableCreateCompanionBuilder =
GameTableCompanion Function({ GameTableCompanion Function({
required String id, required String id,
required String name, required String name,
required String winnerId, Value<String?> winnerId,
Value<int> rowid, Value<int> rowid,
}); });
typedef $$GameTableTableUpdateCompanionBuilder = typedef $$GameTableTableUpdateCompanionBuilder =
GameTableCompanion Function({ GameTableCompanion Function({
Value<String> id, Value<String> id,
Value<String> name, Value<String> name,
Value<String> winnerId, Value<String?> winnerId,
Value<int> rowid, Value<int> rowid,
}); });
@@ -2242,25 +2136,6 @@ final class $$GameTableTableReferences
extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> {
$$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult);
static $PlayerTableTable _winnerIdTable(_$AppDatabase db) =>
db.playerTable.createAlias(
$_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id),
);
$$PlayerTableTableProcessedTableManager get winnerId {
final $_column = $_itemColumn<String>('winner_id')!;
final manager = $$PlayerTableTableTableManager(
$_db,
$_db.playerTable,
).filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_winnerIdTable($_db));
if (item == null) return manager;
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]),
);
}
static MultiTypedResultKey<$PlayerGameTableTable, List<PlayerGameTableData>> static MultiTypedResultKey<$PlayerGameTableTable, List<PlayerGameTableData>>
_playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
db.playerGameTable, db.playerGameTable,
@@ -2319,28 +2194,10 @@ class $$GameTableTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
$$PlayerTableTableFilterComposer get winnerId { ColumnFilters<String> get winnerId => $composableBuilder(
final $$PlayerTableTableFilterComposer composer = $composerBuilder( column: $table.winnerId,
composer: this, builder: (column) => ColumnFilters(column),
getCurrentColumn: (t) => t.winnerId,
referencedTable: $db.playerTable,
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$PlayerTableTableFilterComposer(
$db: $db,
$table: $db.playerTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
); );
return composer;
}
Expression<bool> playerGameTableRefs( Expression<bool> playerGameTableRefs(
Expression<bool> Function($$PlayerGameTableTableFilterComposer f) f, Expression<bool> Function($$PlayerGameTableTableFilterComposer f) f,
@@ -2412,28 +2269,10 @@ class $$GameTableTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
$$PlayerTableTableOrderingComposer get winnerId { ColumnOrderings<String> get winnerId => $composableBuilder(
final $$PlayerTableTableOrderingComposer composer = $composerBuilder( column: $table.winnerId,
composer: this, builder: (column) => ColumnOrderings(column),
getCurrentColumn: (t) => t.winnerId,
referencedTable: $db.playerTable,
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$PlayerTableTableOrderingComposer(
$db: $db,
$table: $db.playerTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
); );
return composer;
}
} }
class $$GameTableTableAnnotationComposer class $$GameTableTableAnnotationComposer
@@ -2451,28 +2290,8 @@ class $$GameTableTableAnnotationComposer
GeneratedColumn<String> get name => GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column); $composableBuilder(column: $table.name, builder: (column) => column);
$$PlayerTableTableAnnotationComposer get winnerId { GeneratedColumn<String> get winnerId =>
final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( $composableBuilder(column: $table.winnerId, builder: (column) => column);
composer: this,
getCurrentColumn: (t) => t.winnerId,
referencedTable: $db.playerTable,
getReferencedColumn: (t) => t.id,
builder:
(
joinBuilder, {
$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer,
}) => $$PlayerTableTableAnnotationComposer(
$db: $db,
$table: $db.playerTable,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
),
);
return composer;
}
Expression<T> playerGameTableRefs<T extends Object>( Expression<T> playerGameTableRefs<T extends Object>(
Expression<T> Function($$PlayerGameTableTableAnnotationComposer a) f, Expression<T> Function($$PlayerGameTableTableAnnotationComposer a) f,
@@ -2539,7 +2358,6 @@ class $$GameTableTableTableManager
(GameTableData, $$GameTableTableReferences), (GameTableData, $$GameTableTableReferences),
GameTableData, GameTableData,
PrefetchHooks Function({ PrefetchHooks Function({
bool winnerId,
bool playerGameTableRefs, bool playerGameTableRefs,
bool groupGameTableRefs, bool groupGameTableRefs,
}) })
@@ -2559,7 +2377,7 @@ class $$GameTableTableTableManager
({ ({
Value<String> id = const Value.absent(), Value<String> id = const Value.absent(),
Value<String> name = const Value.absent(), Value<String> name = const Value.absent(),
Value<String> winnerId = const Value.absent(), Value<String?> winnerId = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => GameTableCompanion( }) => GameTableCompanion(
id: id, id: id,
@@ -2571,7 +2389,7 @@ class $$GameTableTableTableManager
({ ({
required String id, required String id,
required String name, required String name,
required String winnerId, Value<String?> winnerId = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => GameTableCompanion.insert( }) => GameTableCompanion.insert(
id: id, id: id,
@@ -2588,49 +2406,14 @@ class $$GameTableTableTableManager
) )
.toList(), .toList(),
prefetchHooksCallback: prefetchHooksCallback:
({ ({playerGameTableRefs = false, groupGameTableRefs = false}) {
winnerId = false,
playerGameTableRefs = false,
groupGameTableRefs = false,
}) {
return PrefetchHooks( return PrefetchHooks(
db: db, db: db,
explicitlyWatchedTables: [ explicitlyWatchedTables: [
if (playerGameTableRefs) db.playerGameTable, if (playerGameTableRefs) db.playerGameTable,
if (groupGameTableRefs) db.groupGameTable, if (groupGameTableRefs) db.groupGameTable,
], ],
addJoins: addJoins: null,
<
T extends TableManagerState<
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic
>
>(state) {
if (winnerId) {
state =
state.withJoin(
currentTable: table,
currentColumn: table.winnerId,
referencedTable: $$GameTableTableReferences
._winnerIdTable(db),
referencedColumn: $$GameTableTableReferences
._winnerIdTable(db)
.id,
)
as T;
}
return state;
},
getPrefetchedDataCallback: (items) async { getPrefetchedDataCallback: (items) async {
return [ return [
if (playerGameTableRefs) if (playerGameTableRefs)
@@ -2696,7 +2479,6 @@ typedef $$GameTableTableProcessedTableManager =
(GameTableData, $$GameTableTableReferences), (GameTableData, $$GameTableTableReferences),
GameTableData, GameTableData,
PrefetchHooks Function({ PrefetchHooks Function({
bool winnerId,
bool playerGameTableRefs, bool playerGameTableRefs,
bool groupGameTableRefs, bool groupGameTableRefs,
}) })

View File

@@ -1,11 +1,9 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
class GameTable extends Table { class GameTable extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get name => text()(); TextColumn get name => text()();
TextColumn get winnerId => late final winnerId = text().nullable()();
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
@override @override
Set<Column<Object>> get primaryKey => {id}; Set<Column<Object>> get primaryKey => {id};

View File

@@ -7,18 +7,34 @@ class Game {
final String name; final String name;
final List<Player>? players; final List<Player>? players;
final Group? group; final Group? group;
final String winner; final String? winner;
Game({ Game({String? id, required this.name, this.players, this.group, this.winner})
String? id, : id = id ?? const Uuid().v4();
required this.name,
this.players,
this.group,
this.winner = '',
}) : id = id ?? const Uuid().v4();
@override @override
String toString() { String toString() {
return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
} }
/// Creates a Game instance from a JSON object.
Game.fromJson(Map<String, dynamic> json)
: id = json['id'],
name = json['name'],
players = json['players'] != null
? (json['players'] as List)
.map((playerJson) => Player.fromJson(playerJson))
.toList()
: null,
group = json['group'] != null ? Group.fromJson(json['group']) : null,
winner = json['winner'] ?? '';
/// Converts the Game instance to a JSON object.
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'players': players?.map((player) => player.toJson()).toList(),
'group': group?.toJson(),
'winner': winner,
};
} }

View File

@@ -13,4 +13,19 @@ class Group {
String toString() { String toString() {
return 'Group{id: $id, name: $name,members: $members}'; return 'Group{id: $id, name: $name,members: $members}';
} }
/// Creates a Group instance from a JSON object.
Group.fromJson(Map<String, dynamic> json)
: id = json['id'],
name = json['name'],
members = (json['members'] as List)
.map((memberJson) => Player.fromJson(memberJson))
.toList();
/// Converts the Group instance to a JSON object.
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'members': members.map((member) => member.toJson()).toList(),
};
} }

View File

@@ -10,4 +10,12 @@ class Player {
String toString() { String toString() {
return 'Player{id: $id,name: $name}'; return 'Player{id: $id,name: $name}';
} }
/// Creates a Player instance from a JSON object.
Player.fromJson(Map<String, dynamic> json)
: id = json['id'],
name = json['name'];
/// Converts the Player instance to a JSON object.
Map<String, dynamic> toJson() => {'id': id, 'name': name};
} }

View File

@@ -1,10 +1,41 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart';
import 'package:json_schema/json_schema.dart';
class SettingsView extends StatelessWidget { class SettingsView extends StatefulWidget {
const SettingsView({super.key}); const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
static Future<bool> validateJsonSchema(String jsonString) async {
final String schemaString;
schemaString = await rootBundle.loadString('assets/schema.json');
try {
final schema = JsonSchema.create(json.decode(schemaString));
final jsonData = json.decode(jsonString);
final result = schema.validate(jsonData);
if (result.isValid) {
return true;
}
return false;
} catch (e, stack) {
print('[validateJsonSchema] $e');
print(stack);
return false;
}
}
}
class _SettingsViewState extends State<SettingsView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -39,41 +70,30 @@ class SettingsView extends StatelessWidget {
), ),
), ),
), ),
SettingsListTile( SettingsListTile(
title: 'Export Data', title: 'Export data',
icon: Icons.upload_outlined, icon: Icons.upload_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () => exportData(), onPressed: () async {
final String json =
await DataTransferService.getAppDataAsJson(context);
await DataTransferService.exportData(
json,
'exported_data',
);
},
), ),
SettingsListTile( SettingsListTile(
title: 'Import Data', title: 'Import data',
icon: Icons.download_outlined, icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () => importData(), onPressed: () => DataTransferService.importData(context),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: Text(
textAlign: TextAlign.start,
'Example Headline',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
), ),
SettingsListTile( SettingsListTile(
title: 'Example Tile', title: 'Delete all data',
icon: Icons.upload_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () => print('Example Tile'),
),
SettingsListTile(
title: 'Example Tile',
icon: Icons.download_outlined, icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () => print('Example Tile'), onPressed: () => DataTransferService.deleteAllData(context),
), ),
], ],
), ),
@@ -81,10 +101,4 @@ class SettingsView extends StatelessWidget {
), ),
); );
} }
// TODO: Implement export functionality
void exportData() {}
// TODO: Implement import functionality
void importData() {}
} }

View File

@@ -0,0 +1,135 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view.dart';
import 'package:provider/provider.dart';
class DataTransferService {
/// Deletes all data from the database.
static Future<void> deleteAllData(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false);
await db.gameDao.deleteAllGames();
await db.groupDao.deleteAllGroups();
await db.playerDao.deleteAllPlayers();
print('[deleteAllData] All data deleted');
}
static Future<String> getAppDataAsJson(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false);
final games = await db.gameDao.getAllGames();
final groups = await db.groupDao.getAllGroups();
final players = await db.playerDao.getAllPlayers();
// Construct a JSON representation of the data
final Map<String, dynamic> jsonMap = {
'games': games.map((game) => game.toJson()).toList(),
'groups': groups.map((group) => group.toJson()).toList(),
'players': players.map((player) => player.toJson()).toList(),
};
return json.encode(jsonMap);
}
/// Exports the given JSON string to a file with the specified name.
static Future<bool> exportData(String jsonString, String fileName) async {
try {
final bytes = Uint8List.fromList(utf8.encode(jsonString));
await FilePicker.platform.saveFile(
fileName: '$fileName.json',
bytes: bytes,
);
return true;
} catch (e, stack) {
print('[exportData] $e');
print(stack);
return false;
}
}
/// Imports data from a selected JSON file into the database.
static Future<void> importData(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false);
final path = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (path == null) {
print('[importData] No file selected');
return;
}
try {
final jsonString = await _readFileContent(path.files.single);
if (await SettingsView.validateJsonSchema(jsonString)) {
final Map<String, dynamic> jsonData =
json.decode(jsonString) as Map<String, dynamic>;
final List<dynamic>? gamesJson = jsonData['games'] as List<dynamic>?;
final List<dynamic>? groupsJson = jsonData['groups'] as List<dynamic>?;
final List<dynamic>? playersJson =
jsonData['players'] as List<dynamic>?;
final List<Game> importedGames =
gamesJson
?.map((g) => Game.fromJson(g as Map<String, dynamic>))
.toList() ??
[];
final List<Group> importedGroups =
groupsJson
?.map((g) => Group.fromJson(g as Map<String, dynamic>))
.toList() ??
[];
final List<Player> importedPlayers =
playersJson
?.map((p) => Player.fromJson(p as Map<String, dynamic>))
.toList() ??
[];
for (Player player in importedPlayers) {
await db.playerDao.addPlayer(player: player);
}
for (Group group in importedGroups) {
await db.groupDao.addGroup(group: group);
}
for (Game game in importedGames) {
await db.gameDao.addGame(game: game);
}
} else {
print('[importData] Invalid JSON schema');
return;
}
print('[importData] Data imported successfully');
return;
} on FormatException catch (e, stack) {
print('[importData] FormatException');
print('[importData] $e');
print(stack);
return;
} on Exception catch (e, stack) {
print('[importData] Exception');
print('[importData] $e');
print(stack);
return;
}
}
/// Helper method to read file content from either bytes or path
static Future<String> _readFileContent(PlatformFile file) async {
if (file.bytes != null) return utf8.decode(file.bytes!);
if (file.path != null) return await File(file.path!).readAsString();
throw Exception('Die Datei hat keinen lesbaren Inhalt');
}
}

View File

@@ -20,6 +20,9 @@ dependencies:
provider: ^6.1.5 provider: ^6.1.5
skeletonizer: ^2.1.0+1 skeletonizer: ^2.1.0+1
uuid: ^4.5.2 uuid: ^4.5.2
file_picker: ^10.3.6
json_schema: ^5.2.2
file_saver: ^0.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -30,3 +33,5 @@ dev_dependencies:
flutter: flutter:
uses-material-design: true uses-material-design: true
assets:
- assets/schema.json