From 0aa8e1a2a13fbf28cc7187c29359f863f2675073 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 23 Jun 2025 18:41:54 +0200 Subject: [PATCH 001/563] added dependencies for drift db --- pubspec.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index aa5fa8a..df16c45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,9 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + drift: ^2.27.0 + drift_flutter: ^0.2.4 + path_provider: ^2.1.5 dev_dependencies: flutter_test: @@ -45,6 +48,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + drift_dev: ^2.27.0 + build_runner: ^2.5.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -- 2.49.1 From 5c334375190969d3d2509afc0edf6960d56f57e8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 23 Jun 2025 22:29:55 +0200 Subject: [PATCH 002/563] added provider requirement for state management --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index df16c45..b539585 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: drift: ^2.27.0 drift_flutter: ^0.2.4 path_provider: ^2.1.5 + provider: ^6.1.5 dev_dependencies: flutter_test: -- 2.49.1 From 78ea7f644e34f07b92a938744509562388f4e970 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:12:46 +0200 Subject: [PATCH 003/563] added provider for state management & exposed db using provider --- lib/main.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 1188332..c776485 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/data/database.dart'; import 'package:game_tracker/presentation/views/home_view.dart'; +import 'package:provider/provider.dart'; void main() { - runApp(const MyApp()); + runApp( + Provider( + create: (context) => AppDatabase(), + child: MyApp(), + dispose: (context, db) => db.close(), + ), + ); } class MyApp extends StatelessWidget { -- 2.49.1 From 7b7fbd47a58f8429ca282479a71926ef5156bfa2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:15:06 +0200 Subject: [PATCH 004/563] created tables for Group, User and UserGrouo --- lib/data/database.dart | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/data/database.dart diff --git a/lib/data/database.dart b/lib/data/database.dart new file mode 100644 index 0000000..2c04942 --- /dev/null +++ b/lib/data/database.dart @@ -0,0 +1,46 @@ +import 'package:drift/drift.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:path_provider/path_provider.dart'; + +part 'database.g.dart'; + +class User extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} + +class Group extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} + +class UserGroup extends Table { + TextColumn get userId => text().references(User, #id)(); + TextColumn get groupId => text().references(Group, #id)(); + + @override + Set> get primaryKey => {userId, groupId}; +} + +@DriftDatabase(tables: [User, Group, UserGroup]) +class AppDatabase extends _$AppDatabase { + AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); + + @override + int get schemaVersion => 1; + + static QueryExecutor _openConnection() { + return driftDatabase( + name: 'gametracker_db', + native: const DriftNativeOptions( + databaseDirectory: getApplicationSupportDirectory, + ), + ); + } +} \ No newline at end of file -- 2.49.1 From e990c1138b6ba138e1503a7a0d53fc4713c6a9c9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:16:50 +0200 Subject: [PATCH 005/563] added methods for interacting with User, Group and UserGroup in database --- lib/data/methods/group.dart | 28 ++++++++++++++++++++++++++++ lib/data/methods/user.dart | 28 ++++++++++++++++++++++++++++ lib/data/methods/user_group.dart | 26 ++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 lib/data/methods/group.dart create mode 100644 lib/data/methods/user.dart create mode 100644 lib/data/methods/user_group.dart diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart new file mode 100644 index 0000000..afc25c0 --- /dev/null +++ b/lib/data/methods/group.dart @@ -0,0 +1,28 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension Group on AppDatabase { + Future> getAllGroups() async { + return await select(group).get(); + } + + Future> getGroupById(String id) async { + return await (select(group)..where((g) => g.id.equals(id))).get(); + } + + Future addGroup(String id, String name) async { + await into(group).insert( + GroupCompanion.insert(id: id, name: name), + ); + } + + Future deleteGroup(String id) async { + await (delete(group)..where((u) => u.id.equals(id))).go(); + } + + Future updateGroupname(String id, String newName) async { + await (update(group)..where((u) => u.id.equals(id))).write( + GroupCompanion(name: Value(newName)), + ); + } +} \ No newline at end of file diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart new file mode 100644 index 0000000..46ed497 --- /dev/null +++ b/lib/data/methods/user.dart @@ -0,0 +1,28 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension User on AppDatabase { + Future> getAllUsers() async { + return await select(user).get(); + } + + Future> getUserById(String id) async { + return await (select(user)..where((u) => u.id.equals(id))).get(); + } + + Future addUser(String id, String name) async { + await into(user).insert( + UserCompanion.insert(id: id, name: name), + ); + } + + Future deleteUser(String id) async { + await (delete(user)..where((u) => u.id.equals(id))).go(); + } + + Future updateUsername(String id, String newName) async { + await (update(user)..where((u) => u.id.equals(id))).write( + UserCompanion(name: Value(newName)), + ); + } +} \ No newline at end of file diff --git a/lib/data/methods/user_group.dart b/lib/data/methods/user_group.dart new file mode 100644 index 0000000..41e1e60 --- /dev/null +++ b/lib/data/methods/user_group.dart @@ -0,0 +1,26 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension UserGroup on AppDatabase { + Future> getAllUsersAndGroups() async { + return await select(userGroup).get(); + } + + Future> getUsersGroups(String userId) async { + return await (select(userGroup)..where((uG) => uG.userId.equals(userId))).get(); + } + + Future> getGroupsUsers(String groupId) async { + return await (select(userGroup)..where((uG) => uG.groupId.equals(groupId))).get(); + } + + Future addUserToGroup(String userId, String groupId) async { + await into(userGroup).insert( + UserGroupCompanion.insert(userId: userId, groupId: groupId), + ); + } + + Future removeUserFromGroup(String userId, String groupId) async { + await (delete(userGroup)..where((uG) => uG.userId.equals(userId) & uG.groupId.equals(groupId))).go(); + } +} \ No newline at end of file -- 2.49.1 From c7f07e0ce7b922e8d724bcfc422114b440b8ec80 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:17:06 +0200 Subject: [PATCH 006/563] generated database code --- lib/data/database.g.dart | 1455 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1455 insertions(+) create mode 100644 lib/data/database.g.dart diff --git a/lib/data/database.g.dart b/lib/data/database.g.dart new file mode 100644 index 0000000..795f850 --- /dev/null +++ b/lib/data/database.g.dart @@ -0,0 +1,1455 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $UserTable extends User with TableInfo<$UserTable, UserData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $UserTable createAlias(String alias) { + return $UserTable(attachedDatabase, alias); + } +} + +class UserData extends DataClass implements Insertable { + final String id; + final String name; + const UserData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + UserCompanion toCompanion(bool nullToAbsent) { + return UserCompanion(id: Value(id), name: Value(name)); + } + + factory UserData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + UserData copyWith({String? id, String? name}) => + UserData(id: id ?? this.id, name: name ?? this.name); + UserData copyWithCompanion(UserCompanion data) { + return UserData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('UserData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserData && other.id == this.id && other.name == this.name); +} + +class UserCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const UserCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + UserCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return UserCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $GroupTable extends Group with TableInfo<$GroupTable, GroupData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GroupTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GroupData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $GroupTable createAlias(String alias) { + return $GroupTable(attachedDatabase, alias); + } +} + +class GroupData extends DataClass implements Insertable { + final String id; + final String name; + const GroupData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + GroupCompanion toCompanion(bool nullToAbsent) { + return GroupCompanion(id: Value(id), name: Value(name)); + } + + factory GroupData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + GroupData copyWith({String? id, String? name}) => + GroupData(id: id ?? this.id, name: name ?? this.name); + GroupData copyWithCompanion(GroupCompanion data) { + return GroupData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('GroupData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupData && other.id == this.id && other.name == this.name); +} + +class GroupCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const GroupCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return GroupCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $UserGroupTable extends UserGroup + with TableInfo<$UserGroupTable, UserGroupData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserGroupTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user (id)', + ), + ); + static const VerificationMeta _groupIdMeta = const VerificationMeta( + 'groupId', + ); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES "group" (id)', + ), + ); + @override + List get $columns => [userId, groupId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_group'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle( + _userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + ); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('group_id')) { + context.handle( + _groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + ); + } else if (isInserting) { + context.missing(_groupIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {userId, groupId}; + @override + UserGroupData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserGroupData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + )!, + ); + } + + @override + $UserGroupTable createAlias(String alias) { + return $UserGroupTable(attachedDatabase, alias); + } +} + +class UserGroupData extends DataClass implements Insertable { + final String userId; + final String groupId; + const UserGroupData({required this.userId, required this.groupId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['group_id'] = Variable(groupId); + return map; + } + + UserGroupCompanion toCompanion(bool nullToAbsent) { + return UserGroupCompanion(userId: Value(userId), groupId: Value(groupId)); + } + + factory UserGroupData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserGroupData( + userId: serializer.fromJson(json['userId']), + groupId: serializer.fromJson(json['groupId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'groupId': serializer.toJson(groupId), + }; + } + + UserGroupData copyWith({String? userId, String? groupId}) => UserGroupData( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + ); + UserGroupData copyWithCompanion(UserGroupCompanion data) { + return UserGroupData( + userId: data.userId.present ? data.userId.value : this.userId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, + ); + } + + @override + String toString() { + return (StringBuffer('UserGroupData(') + ..write('userId: $userId, ') + ..write('groupId: $groupId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, groupId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserGroupData && + other.userId == this.userId && + other.groupId == this.groupId); +} + +class UserGroupCompanion extends UpdateCompanion { + final Value userId; + final Value groupId; + final Value rowid; + const UserGroupCompanion({ + this.userId = const Value.absent(), + this.groupId = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserGroupCompanion.insert({ + required String userId, + required String groupId, + this.rowid = const Value.absent(), + }) : userId = Value(userId), + groupId = Value(groupId); + static Insertable custom({ + Expression? userId, + Expression? groupId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (groupId != null) 'group_id': groupId, + if (rowid != null) 'rowid': rowid, + }); + } + + UserGroupCompanion copyWith({ + Value? userId, + Value? groupId, + Value? rowid, + }) { + return UserGroupCompanion( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserGroupCompanion(') + ..write('userId: $userId, ') + ..write('groupId: $groupId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $UserTable user = $UserTable(this); + late final $GroupTable group = $GroupTable(this); + late final $UserGroupTable userGroup = $UserGroupTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [user, group, userGroup]; +} + +typedef $$UserTableCreateCompanionBuilder = + UserCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$UserTableUpdateCompanionBuilder = + UserCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +final class $$UserTableReferences + extends BaseReferences<_$AppDatabase, $UserTable, UserData> { + $$UserTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$UserGroupTable, List> + _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.userGroup, + aliasName: $_aliasNameGenerator(db.user.id, db.userGroup.userId), + ); + + $$UserGroupTableProcessedTableManager get userGroupRefs { + final manager = $$UserGroupTableTableManager( + $_db, + $_db.userGroup, + ).filter((f) => f.userId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + Expression userGroupRefs( + Expression Function($$UserGroupTableFilterComposer f) f, + ) { + final $$UserGroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableFilterComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$UserTableOrderingComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$UserTableAnnotationComposer + extends Composer<_$AppDatabase, $UserTable> { + $$UserTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + Expression userGroupRefs( + Expression Function($$UserGroupTableAnnotationComposer a) f, + ) { + final $$UserGroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableAnnotationComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$UserTableTableManager + extends + RootTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userGroupRefs}) + > { + $$UserTableTableManager(_$AppDatabase db, $UserTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => UserCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => UserCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$UserTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({userGroupRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (userGroupRefs) + await $_getPrefetchedData< + UserData, + $UserTable, + UserGroupData + >( + currentTable: table, + referencedTable: $$UserTableReferences + ._userGroupRefsTable(db), + managerFromTypedResult: (p0) => + $$UserTableReferences(db, table, p0).userGroupRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.userId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$UserTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userGroupRefs}) + >; +typedef $$GroupTableCreateCompanionBuilder = + GroupCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$GroupTableUpdateCompanionBuilder = + GroupCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +final class $$GroupTableReferences + extends BaseReferences<_$AppDatabase, $GroupTable, GroupData> { + $$GroupTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$UserGroupTable, List> + _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.userGroup, + aliasName: $_aliasNameGenerator(db.group.id, db.userGroup.groupId), + ); + + $$UserGroupTableProcessedTableManager get userGroupRefs { + final manager = $$UserGroupTableTableManager( + $_db, + $_db.userGroup, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$GroupTableFilterComposer extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + Expression userGroupRefs( + Expression Function($$UserGroupTableFilterComposer f) f, + ) { + final $$UserGroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableFilterComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GroupTableOrderingComposer + extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$GroupTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + Expression userGroupRefs( + Expression Function($$UserGroupTableAnnotationComposer a) f, + ) { + final $$UserGroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableAnnotationComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GroupTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GroupTable, + GroupData, + $$GroupTableFilterComposer, + $$GroupTableOrderingComposer, + $$GroupTableAnnotationComposer, + $$GroupTableCreateCompanionBuilder, + $$GroupTableUpdateCompanionBuilder, + (GroupData, $$GroupTableReferences), + GroupData, + PrefetchHooks Function({bool userGroupRefs}) + > { + $$GroupTableTableManager(_$AppDatabase db, $GroupTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GroupTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GroupTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GroupTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => GroupCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => GroupCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$GroupTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({userGroupRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (userGroupRefs) + await $_getPrefetchedData< + GroupData, + $GroupTable, + UserGroupData + >( + currentTable: table, + referencedTable: $$GroupTableReferences + ._userGroupRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableReferences(db, table, p0).userGroupRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.groupId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$GroupTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GroupTable, + GroupData, + $$GroupTableFilterComposer, + $$GroupTableOrderingComposer, + $$GroupTableAnnotationComposer, + $$GroupTableCreateCompanionBuilder, + $$GroupTableUpdateCompanionBuilder, + (GroupData, $$GroupTableReferences), + GroupData, + PrefetchHooks Function({bool userGroupRefs}) + >; +typedef $$UserGroupTableCreateCompanionBuilder = + UserGroupCompanion Function({ + required String userId, + required String groupId, + Value rowid, + }); +typedef $$UserGroupTableUpdateCompanionBuilder = + UserGroupCompanion Function({ + Value userId, + Value groupId, + Value rowid, + }); + +final class $$UserGroupTableReferences + extends BaseReferences<_$AppDatabase, $UserGroupTable, UserGroupData> { + $$UserGroupTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $UserTable _userIdTable(_$AppDatabase db) => db.user.createAlias( + $_aliasNameGenerator(db.userGroup.userId, db.user.id), + ); + + $$UserTableProcessedTableManager get userId { + final $_column = $_itemColumn('user_id')!; + + final manager = $$UserTableTableManager( + $_db, + $_db.user, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTable _groupIdTable(_$AppDatabase db) => db.group.createAlias( + $_aliasNameGenerator(db.userGroup.groupId, db.group.id), + ); + + $$GroupTableProcessedTableManager get groupId { + final $_column = $_itemColumn('group_id')!; + + final manager = $$GroupTableTableManager( + $_db, + $_db.group, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$UserGroupTableFilterComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableFilterComposer get userId { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableFilterComposer get groupId { + final $$GroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableFilterComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableOrderingComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableOrderingComposer get userId { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableOrderingComposer get groupId { + final $$GroupTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableOrderingComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableAnnotationComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableAnnotationComposer get userId { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableAnnotationComposer get groupId { + final $$GroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableAnnotationComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableTableManager + extends + RootTableManager< + _$AppDatabase, + $UserGroupTable, + UserGroupData, + $$UserGroupTableFilterComposer, + $$UserGroupTableOrderingComposer, + $$UserGroupTableAnnotationComposer, + $$UserGroupTableCreateCompanionBuilder, + $$UserGroupTableUpdateCompanionBuilder, + (UserGroupData, $$UserGroupTableReferences), + UserGroupData, + PrefetchHooks Function({bool userId, bool groupId}) + > { + $$UserGroupTableTableManager(_$AppDatabase db, $UserGroupTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserGroupTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserGroupTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserGroupTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value groupId = const Value.absent(), + Value rowid = const Value.absent(), + }) => UserGroupCompanion( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String userId, + required String groupId, + Value rowid = const Value.absent(), + }) => UserGroupCompanion.insert( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$UserGroupTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({userId = false, groupId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (userId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: $$UserGroupTableReferences + ._userIdTable(db), + referencedColumn: $$UserGroupTableReferences + ._userIdTable(db) + .id, + ) + as T; + } + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: $$UserGroupTableReferences + ._groupIdTable(db), + referencedColumn: $$UserGroupTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$UserGroupTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $UserGroupTable, + UserGroupData, + $$UserGroupTableFilterComposer, + $$UserGroupTableOrderingComposer, + $$UserGroupTableAnnotationComposer, + $$UserGroupTableCreateCompanionBuilder, + $$UserGroupTableUpdateCompanionBuilder, + (UserGroupData, $$UserGroupTableReferences), + UserGroupData, + PrefetchHooks Function({bool userId, bool groupId}) + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$UserTableTableManager get user => $$UserTableTableManager(_db, _db.user); + $$GroupTableTableManager get group => + $$GroupTableTableManager(_db, _db.group); + $$UserGroupTableTableManager get userGroup => + $$UserGroupTableTableManager(_db, _db.userGroup); +} -- 2.49.1 From e42064da2c5182c6df7011cee76f98c70a3495ef Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:26:34 +0200 Subject: [PATCH 007/563] Update lib/data/methods/group.dart renamed var in deleteGroup to g instead of u Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/data/methods/group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index afc25c0..bb5a131 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -17,7 +17,7 @@ extension Group on AppDatabase { } Future deleteGroup(String id) async { - await (delete(group)..where((u) => u.id.equals(id))).go(); + await (delete(group)..where((g) => g.id.equals(id))).go(); } Future updateGroupname(String id, String newName) async { -- 2.49.1 From be08b286a41b3cfacd2cffd79e1c4b029e361ab0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:28:26 +0200 Subject: [PATCH 008/563] renamed class names to avoid conflicts --- lib/data/methods/group.dart | 2 +- lib/data/methods/user.dart | 2 +- lib/data/methods/user_group.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index bb5a131..d996af6 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension Group on AppDatabase { +extension GroupMethods on AppDatabase { Future> getAllGroups() async { return await select(group).get(); } diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart index 46ed497..ddf26b0 100644 --- a/lib/data/methods/user.dart +++ b/lib/data/methods/user.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension User on AppDatabase { +extension UserMethods on AppDatabase { Future> getAllUsers() async { return await select(user).get(); } diff --git a/lib/data/methods/user_group.dart b/lib/data/methods/user_group.dart index 41e1e60..206c1f0 100644 --- a/lib/data/methods/user_group.dart +++ b/lib/data/methods/user_group.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension UserGroup on AppDatabase { +extension UserGroupMethods on AppDatabase { Future> getAllUsersAndGroups() async { return await select(userGroup).get(); } -- 2.49.1 From cd508190a3cfcfd34dcedd3c80e4e894e282022c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:35:30 +0200 Subject: [PATCH 009/563] changed return type of getGroupById and getUserById to match expected result --- lib/data/methods/group.dart | 4 ++-- lib/data/methods/user.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index d996af6..50a1745 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -6,8 +6,8 @@ extension GroupMethods on AppDatabase { return await select(group).get(); } - Future> getGroupById(String id) async { - return await (select(group)..where((g) => g.id.equals(id))).get(); + Future getGroupById(String id) async { + return await (select(group)..where((g) => g.id.equals(id))).getSingle(); } Future addGroup(String id, String name) async { diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart index ddf26b0..6f4c508 100644 --- a/lib/data/methods/user.dart +++ b/lib/data/methods/user.dart @@ -6,8 +6,8 @@ extension UserMethods on AppDatabase { return await select(user).get(); } - Future> getUserById(String id) async { - return await (select(user)..where((u) => u.id.equals(id))).get(); + Future getUserById(String id) async { + return await (select(user)..where((u) => u.id.equals(id))).getSingle(); } Future addUser(String id, String name) async { -- 2.49.1 From 0c57a83dfcec9598ba6c3334333d64a851a6c40d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:38:29 +0200 Subject: [PATCH 010/563] renamed lambda var in updateGroupname from u to g --- lib/data/methods/group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index 50a1745..a5346ba 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -21,7 +21,7 @@ extension GroupMethods on AppDatabase { } Future updateGroupname(String id, String newName) async { - await (update(group)..where((u) => u.id.equals(id))).write( + await (update(group)..where((g) => g.id.equals(id))).write( GroupCompanion(name: Value(newName)), ); } -- 2.49.1 From b3c9990685798eb24c17b4377efd1e8d3607410c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:40:19 +0200 Subject: [PATCH 011/563] added const to MyApp in Providers child --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index c776485..e3f8a91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,7 +7,7 @@ void main() { runApp( Provider( create: (context) => AppDatabase(), - child: MyApp(), + child: const MyApp(), dispose: (context, db) => db.close(), ), ); -- 2.49.1 From 5d505858abc107dae6d17d3704494f6c2e06bc3a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 23 Jun 2025 22:52:44 +0200 Subject: [PATCH 012/563] Added ios platform --- .metadata | 14 +- ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 26 + ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 619 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 49 ++ ios/Runner/Runner-Bridging-Header.h | 1 + ios/RunnerTests/RunnerTests.swift | 12 + 40 files changed, 1117 insertions(+), 13 deletions(-) create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 ios/RunnerTests/RunnerTests.swift diff --git a/.metadata b/.metadata index dc84ef0..85b54cb 100644 --- a/.metadata +++ b/.metadata @@ -15,19 +15,7 @@ migration: - platform: root create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - - platform: android - create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - - platform: linux - create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - - platform: macos - create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - - platform: web - create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 - - platform: windows + - platform: ios create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8 diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e663485 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = RJB4MM6RVS; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = RJB4MM6RVS; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = RJB4MM6RVS; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 zcmV;526g#~P){YQnis^a@{&-nmRmq)<&%Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT> z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g( z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=# zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~ z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{` zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550 z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8 z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~ z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2 z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H= zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f% z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`? zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91 z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a} z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3< zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7 zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9 zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5&#r7J#c`3Z7x!LpTc01dx zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..048157d --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Game Tracker + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + game_tracker + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} -- 2.49.1 From 12bb2da31c46836b30088d86368d4b4100ec05c6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 23 Jun 2025 23:01:05 +0200 Subject: [PATCH 013/563] Updated pubspec.yaml --- pubspec.yaml | 75 +++------------------------------------------------- 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index aa5fa8a..7f2a6a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,89 +1,20 @@ name: game_tracker -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +description: "Game Tracking App for Card Games" +publish_to: 'none' +version: 0.0.1+1 environment: sdk: ^3.8.1 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package -- 2.49.1 From 36a42a7b8de935ca61480a23df789828d5a1cd5a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 23 Jun 2025 23:47:40 +0200 Subject: [PATCH 014/563] Added mocked views --- .../views/main_menu/game_history_view.dart | 10 ++++++++++ lib/presentation/views/main_menu/groups_view.dart | 10 ++++++++++ lib/presentation/views/main_menu/home_view.dart | 10 ++++++++++ lib/presentation/views/main_menu/settings_view.dart | 10 ++++++++++ lib/presentation/views/main_menu/statistics_view.dart | 10 ++++++++++ 5 files changed, 50 insertions(+) create mode 100644 lib/presentation/views/main_menu/game_history_view.dart create mode 100644 lib/presentation/views/main_menu/groups_view.dart create mode 100644 lib/presentation/views/main_menu/home_view.dart create mode 100644 lib/presentation/views/main_menu/settings_view.dart create mode 100644 lib/presentation/views/main_menu/statistics_view.dart diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart new file mode 100644 index 0000000..4516dd6 --- /dev/null +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class GameHistoryView extends StatelessWidget { + const GameHistoryView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Game History View')); + } +} diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart new file mode 100644 index 0000000..485d516 --- /dev/null +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class GroupsView extends StatelessWidget { + const GroupsView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Groups View')); + } +} diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart new file mode 100644 index 0000000..fb23bc1 --- /dev/null +++ b/lib/presentation/views/main_menu/home_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class HomeView extends StatelessWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Home View')); + } +} diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart new file mode 100644 index 0000000..39bd978 --- /dev/null +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class SettingsView extends StatelessWidget { + const SettingsView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Settings View')); + } +} diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart new file mode 100644 index 0000000..84ccf77 --- /dev/null +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class StatisticsView extends StatelessWidget { + const StatisticsView({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Statistics View')); + } +} -- 2.49.1 From c78a3ed171b959e79a82e6619ca65d14279e5ee0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 23 Jun 2025 23:47:59 +0200 Subject: [PATCH 015/563] Implemented custom navigation bar --- lib/core/custom_theme.dart | 19 ++++ lib/main.dart | 12 ++- lib/presentation/views/home_view.dart | 10 -- .../main_menu/custom_navigation_bar.dart | 101 ++++++++++++++++++ pubspec.yaml | 3 +- 5 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 lib/core/custom_theme.dart delete mode 100644 lib/presentation/views/home_view.dart create mode 100644 lib/presentation/views/main_menu/custom_navigation_bar.dart diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart new file mode 100644 index 0000000..afc8dee --- /dev/null +++ b/lib/core/custom_theme.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class CustomTheme { + static Color primaryColor = const Color(0xFF71C0BB); + static Color secondaryColor = const Color(0xFF2A4759); + static Color backgroundColor = const Color(0xFF1A1A1A); + + static AppBarTheme appBarTheme = const AppBarTheme( + backgroundColor: Color(0xFF1A1A1A), + foregroundColor: Colors.white, + elevation: 0, + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + iconTheme: IconThemeData(color: Colors.white), + ); +} diff --git a/lib/main.dart b/lib/main.dart index 1188332..656788b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/presentation/views/home_view.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; void main() { runApp(const MyApp()); @@ -11,11 +12,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + debugShowCheckedModeBanner: false, title: 'Game Tracker', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const HomeView(), + themeMode: ThemeMode.dark, + darkTheme: ThemeData.dark(), + theme: ThemeData(appBarTheme: CustomTheme.appBarTheme), + home: const CustomNavigationBar(), ); } } diff --git a/lib/presentation/views/home_view.dart b/lib/presentation/views/home_view.dart deleted file mode 100644 index 234dc1a..0000000 --- a/lib/presentation/views/home_view.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; - -class HomeView extends StatelessWidget { - const HomeView({super.key}); - - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart new file mode 100644 index 0000000..0be94fe --- /dev/null +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; + +class CustomNavigationBar extends StatefulWidget { + const CustomNavigationBar({super.key}); + + @override + State createState() => _CustomNavigationBarState(); +} + +class _CustomNavigationBarState extends State + with SingleTickerProviderStateMixin { + int currentIndex = 0; + final List tabs = [ + const HomeView(), + const GameHistoryView(), + const GroupsView(), + const StatisticsView(), + ]; + + void onTabTapped(int index) { + setState(() { + currentIndex = index; + }); + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + actions: [ + IconButton(onPressed: () {}, icon: const Icon(Icons.settings)), + ], + elevation: 0, + ), + backgroundColor: CustomTheme.backgroundColor, + body: tabs[currentIndex], + extendBody: true, + floatingActionButton: FloatingActionButton( + shape: const CircleBorder(), + backgroundColor: CustomTheme.primaryColor, + onPressed: () {}, + child: const Icon(Icons.add), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + bottomNavigationBar: BottomAppBar( + padding: const EdgeInsets.symmetric(horizontal: 10), + elevation: 10, + height: 60, + color: CustomTheme.primaryColor, + shape: const CircularNotchedRectangle(), + notchMargin: 5, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + Icons.home, + color: currentIndex == 0 ? Colors.white : Colors.black, + ), + onPressed: () => onTabTapped(0), + ), + IconButton( + icon: Icon( + Icons.history, + color: currentIndex == 1 ? Colors.white : Colors.black, + ), + onPressed: () => onTabTapped(1), + ), + const SizedBox(width: 40), + IconButton( + icon: Icon( + Icons.group, + color: currentIndex == 2 ? Colors.white : Colors.black, + ), + onPressed: () => onTabTapped(2), + ), + IconButton( + icon: Icon( + Icons.bar_chart, + color: currentIndex == 3 ? Colors.white : Colors.black, + ), + onPressed: () => onTabTapped(3), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7f2a6a8..992bd8f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+1 +version: 0.0.1+18 environment: sdk: ^3.8.1 @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + material_symbols_icons: ^4.2815.1 dev_dependencies: -- 2.49.1 From 348b812f9ccb9603fe8eedb1d0db2d6dacd96552 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 23 Jun 2025 23:55:25 +0200 Subject: [PATCH 016/563] Theme changes & settings navigation --- lib/main.dart | 15 +++++++++++-- .../main_menu/custom_navigation_bar.dart | 21 ++++++++++++------- .../views/main_menu/settings_view.dart | 7 +++++-- pubspec.yaml | 2 +- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 656788b..9577865 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,9 +14,20 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', - themeMode: ThemeMode.dark, darkTheme: ThemeData.dark(), - theme: ThemeData(appBarTheme: CustomTheme.appBarTheme), + + themeMode: ThemeMode.dark, // forces light mode + theme: ThemeData( + primaryColor: CustomTheme.primaryColor, + scaffoldBackgroundColor: CustomTheme.backgroundColor, + appBarTheme: CustomTheme.appBarTheme, + + colorScheme: ColorScheme.fromSeed( + seedColor: CustomTheme.primaryColor, + brightness: Brightness.dark, + ).copyWith(surface: CustomTheme.backgroundColor), + ), + home: const CustomNavigationBar(), ); } diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 0be94fe..3b2629a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; class CustomNavigationBar extends StatefulWidget { @@ -22,12 +23,6 @@ class _CustomNavigationBarState extends State const StatisticsView(), ]; - void onTabTapped(int index) { - setState(() { - currentIndex = index; - }); - } - @override void initState() { super.initState(); @@ -39,7 +34,13 @@ class _CustomNavigationBarState extends State appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, actions: [ - IconButton(onPressed: () {}, icon: const Icon(Icons.settings)), + IconButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), + ), + icon: const Icon(Icons.settings), + ), ], elevation: 0, ), @@ -98,4 +99,10 @@ class _CustomNavigationBarState extends State ), ); } + + void onTabTapped(int index) { + setState(() { + currentIndex = index; + }); + } } diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 39bd978..c3e75f3 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,10 +1,13 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; class SettingsView extends StatelessWidget { const SettingsView({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('Settings View')); + return Scaffold( + appBar: AppBar(title: const Text('Einstellungen')), + body: const Center(child: Text('Settings View')), + ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 992bd8f..abc2e18 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+18 +version: 0.0.1+21 environment: sdk: ^3.8.1 -- 2.49.1 From 27f501646b5f06f405acef0e137f5da8eb19fd22 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 13:57:03 +0200 Subject: [PATCH 017/563] fix appbar color change on scroll --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 3b2629a..6bf16d2 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -33,6 +33,7 @@ class _CustomNavigationBarState extends State return Scaffold( appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, actions: [ IconButton( onPressed: () => Navigator.push( -- 2.49.1 From 502515002dd92039b47b2c185024609812d869d1 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 14:30:33 +0200 Subject: [PATCH 018/563] added changing appbar title for each tab --- .../main_menu/custom_navigation_bar.dart | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 6bf16d2..57b8519 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -32,6 +32,14 @@ class _CustomNavigationBarState extends State Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + centerTitle: true, + title: Text( + _currentTabTitle(), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, actions: [ @@ -83,7 +91,7 @@ class _CustomNavigationBarState extends State const SizedBox(width: 40), IconButton( icon: Icon( - Icons.group, + Icons.groups, color: currentIndex == 2 ? Colors.white : Colors.black, ), onPressed: () => onTabTapped(2), @@ -106,4 +114,18 @@ class _CustomNavigationBarState extends State currentIndex = index; }); } -} + String _currentTabTitle() { + switch (currentIndex) { + case 0: + return 'Home'; + case 1: + return 'Game History'; + case 2: + return 'Groups'; + case 3: + return 'Statistics'; + default: + return ''; + } + } +} \ No newline at end of file -- 2.49.1 From 0c2ec4167e541af945b99a23b4dc419c37a77c70 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 14:31:06 +0200 Subject: [PATCH 019/563] implemented game history view with searchbar --- .../views/main_menu/game_history_view.dart | 183 +++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 4516dd6..85d8454 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,10 +1,187 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/game_history_empty_builder.dart'; +import 'package:game_tracker/presentation/widgets/game_history_listtile.dart'; -class GameHistoryView extends StatelessWidget { +class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); + @override + State createState() => _GameHistoryViewState(); +} + +class _GameHistoryViewState extends State { + final allGameData = [ + { + 'game': 'Schach', + 'title': 'Abendpartie', + 'players': 2, + 'group': 'Familie', + 'date': '01.06.2024', + }, + { + 'game': 'Monopoly', + 'title': 'Wochenendspaß', + 'players': 4, + 'group': 'Freunde', + 'date': '28.05.2024', + }, + { + 'game': 'Catan', + 'title': 'Strategieabend', + 'players': 3, + 'group': 'Brettspieler', + 'date': '25.05.2024', + }, + { + 'game': 'Uno', + 'title': 'Schnelle Runde', + 'players': 5, + 'group': 'Kollegen', + 'date': '22.05.2024', + }, + { + 'game': 'Poker', + 'title': 'Freitagspoker', + 'players': 6, + 'group': 'Pokerclub', + 'date': '20.05.2024', + }, + { + 'game': 'Scrabble', + 'title': 'Wortschlacht', + 'players': 4, + 'group': 'Familie', + 'date': '18.05.2024', + }, + { + 'game': 'Risiko', + 'title': 'Weltherrschaft', + 'players': 5, + 'group': 'Strategiegruppe', + 'date': '15.05.2024', + }, + { + 'game': 'Zug um Zug', + 'title': 'Zug-Abenteuer', + 'players': 4, + 'group': 'Reisende', + 'date': '12.05.2024', + }, + { + 'game': 'Carcassonne', + 'title': 'Plättchenlegen', + 'players': 3, + 'group': 'Brettspieler', + 'date': '10.05.2024', + }, + { + 'game': 'Pandemie', + 'title': 'Welt retten', + 'players': 4, + 'group': 'Koop-Team', + 'date': '08.05.2024', + }, + { + 'game': 'Cluedo', + 'title': 'Krimiabend', + 'players': 6, + 'group': 'Detektive', + 'date': '05.05.2024', + }, + { + 'game': 'Dixit', + 'title': 'Fantasiespiel', + 'players': 5, + 'group': 'Künstler', + 'date': '02.05.2024', + }, + { + 'game': 'Azul', + 'title': 'Plättchenmeister', + 'players': 4, + 'group': 'Familie', + 'date': '30.04.2024', + }, + { + 'game': 'Splendor', + 'title': 'Edelsteinhändler', + 'players': 3, + 'group': 'Freunde', + 'date': '28.04.2024', + }, + { + 'game': '7 Wonders', + 'title': 'Antike Reiche', + 'players': 7, + 'group': 'Geschichtsfreunde', + 'date': '25.04.2024', + }, + ]; + late List> suggestedGameData; + + @override + void initState() { + super.initState(); + suggestedGameData = List.from(allGameData); + } + @override Widget build(BuildContext context) { - return const Center(child: Text('Game History View')); + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + body: Stack( + children: [ + Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 75), + ), + Expanded( + child: gameHistoryListView(allGameData, suggestedGameData), + ), + ], + ), + Container( + margin: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), + child: SearchBar( + leading: Icon(Icons.search), + onChanged:(value) { + if (value.isEmpty) { + setState(() { + suggestedGameData.clear(); + suggestedGameData.addAll(allGameData); + }); + return; + } + final suggestions = allGameData.where((currentGame) { + return currentGame['game'].toString().toLowerCase().contains(value.toLowerCase()) || + currentGame['title'].toString().toLowerCase().contains(value.toLowerCase()) || + currentGame['group'].toString().toLowerCase().contains(value.toLowerCase()); + }); + setState(() { + suggestedGameData.clear(); + suggestedGameData.addAll(suggestions); + }); + }, + ), + ), + ], + ), + ); } } + +Widget gameHistoryListView(allGameData, suggestedGameData) { + if (suggestedGameData.isEmpty && allGameData.isEmpty) { + return GameHistoryEmptyBuilder("Keine Spiele erstellt"); + } else if (suggestedGameData.isEmpty) { + return GameHistoryEmptyBuilder("Kein Spiel mit den Suchparametern gefunden."); + } + return ListView.builder( + itemCount: suggestedGameData.length, + itemBuilder: (context, index) { + final currentGame = suggestedGameData[index]; + return GameHistoryListTile(currentGame); + }); +} \ No newline at end of file -- 2.49.1 From c644a06173dfbbbd5a67ced317d0ab0aac269c09 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 14:31:33 +0200 Subject: [PATCH 020/563] implemented empty builder widget for game history view --- .../widgets/game_history_empty_builder.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/presentation/widgets/game_history_empty_builder.dart diff --git a/lib/presentation/widgets/game_history_empty_builder.dart b/lib/presentation/widgets/game_history_empty_builder.dart new file mode 100644 index 0000000..db3c974 --- /dev/null +++ b/lib/presentation/widgets/game_history_empty_builder.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/game_history_listtile.dart'; + +Widget GameHistoryEmptyBuilder(message) { + return Container( + padding: EdgeInsets.only(top:100), + margin: EdgeInsets.only(left: 10, right: 10), + alignment: Alignment.topCenter, + child: Text( + "$message", + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ) + ); +} \ No newline at end of file -- 2.49.1 From 8e2863d70a15977b037247cece7c6ae0130cdf39 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 14:31:47 +0200 Subject: [PATCH 021/563] implemented list tile widget for game history view --- .../widgets/game_history_listtile.dart | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lib/presentation/widgets/game_history_listtile.dart diff --git a/lib/presentation/widgets/game_history_listtile.dart b/lib/presentation/widgets/game_history_listtile.dart new file mode 100644 index 0000000..473476c --- /dev/null +++ b/lib/presentation/widgets/game_history_listtile.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +Widget GameHistoryListTile(currentGame){ + return Container( + margin: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: CustomTheme.secondaryColor, + ), + child: Column( + children: [ + Row( + children: [ + Text("${currentGame['game']}: ", style: TextStyle(fontSize: 20)), + Text("${currentGame['title']}", style: TextStyle(fontSize: 20)), + Spacer(), + Text("${currentGame['players']} Spieler", style: TextStyle(fontSize: 20)) + ], + ), + Row( + children: [ + Text("${currentGame['group']}", style: TextStyle(fontSize: 20)), + Spacer(), + Text("${currentGame['date']}", style: TextStyle(fontSize: 20)) + ], + ), + ], + ), + ); +} \ No newline at end of file -- 2.49.1 From 80e47589caacc64938282af4e4226ffa00a314e2 Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:49:50 +0200 Subject: [PATCH 022/563] added type annotation for currentgame in GameHistoryListTile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/presentation/widgets/game_history_listtile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/game_history_listtile.dart b/lib/presentation/widgets/game_history_listtile.dart index 473476c..ccc818d 100644 --- a/lib/presentation/widgets/game_history_listtile.dart +++ b/lib/presentation/widgets/game_history_listtile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -Widget GameHistoryListTile(currentGame){ +Widget GameHistoryListTile(Map currentGame){ return Container( margin: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), padding: EdgeInsets.all(10), -- 2.49.1 From 46404e3ebf93ba9931b2ec4fe79936cfaadf0707 Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:50:24 +0200 Subject: [PATCH 023/563] removed unnecessary import Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/presentation/widgets/game_history_empty_builder.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/game_history_empty_builder.dart b/lib/presentation/widgets/game_history_empty_builder.dart index db3c974..f3442d7 100644 --- a/lib/presentation/widgets/game_history_empty_builder.dart +++ b/lib/presentation/widgets/game_history_empty_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/presentation/widgets/game_history_listtile.dart'; Widget GameHistoryEmptyBuilder(message) { return Container( -- 2.49.1 From fc832d35dba2e612193c738172217b88bbcaec7a Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:50:41 +0200 Subject: [PATCH 024/563] added type annotation for message in GameHistoryEmptyBuilder Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/presentation/widgets/game_history_empty_builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/game_history_empty_builder.dart b/lib/presentation/widgets/game_history_empty_builder.dart index f3442d7..f34b6f8 100644 --- a/lib/presentation/widgets/game_history_empty_builder.dart +++ b/lib/presentation/widgets/game_history_empty_builder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -Widget GameHistoryEmptyBuilder(message) { +Widget GameHistoryEmptyBuilder(String message) { return Container( padding: EdgeInsets.only(top:100), margin: EdgeInsets.only(left: 10, right: 10), -- 2.49.1 From 879a81567a85625595d4ab56caf475d456ae08e2 Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:50:57 +0200 Subject: [PATCH 025/563] fixed typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 9577865..42c1d0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,7 +16,7 @@ class MyApp extends StatelessWidget { title: 'Game Tracker', darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, // forces light mode + themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, scaffoldBackgroundColor: CustomTheme.backgroundColor, -- 2.49.1 From 56116a74fcd872f8284c72ece05d9ae8f20b49ea Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 14:52:27 +0200 Subject: [PATCH 026/563] fixed type of suggestedGameData map --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 85d8454..bd68e5a 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -118,7 +118,7 @@ class _GameHistoryViewState extends State { 'date': '25.04.2024', }, ]; - late List> suggestedGameData; + late List> suggestedGameData; @override void initState() { -- 2.49.1 From 0c39f4836efcfd144617a5d19c1dc73debc14446 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 16:36:39 +0200 Subject: [PATCH 027/563] renamed game_history_empty_builder to top_centered_message --- lib/presentation/views/main_menu/game_history_view.dart | 9 +++++---- ...tory_empty_builder.dart => top_centered_message.dart} | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) rename lib/presentation/widgets/{game_history_empty_builder.dart => top_centered_message.dart} (86%) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index bd68e5a..32c41db 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/game_history_empty_builder.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/game_history_listtile.dart'; class GameHistoryView extends StatefulWidget { @@ -174,14 +174,15 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return GameHistoryEmptyBuilder("Keine Spiele erstellt"); + return TopCenteredMessage("Keine Spiele erstellt"); } else if (suggestedGameData.isEmpty) { - return GameHistoryEmptyBuilder("Kein Spiel mit den Suchparametern gefunden."); + return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); } return ListView.builder( itemCount: suggestedGameData.length, itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; return GameHistoryListTile(currentGame); - }); + } + ); } \ No newline at end of file diff --git a/lib/presentation/widgets/game_history_empty_builder.dart b/lib/presentation/widgets/top_centered_message.dart similarity index 86% rename from lib/presentation/widgets/game_history_empty_builder.dart rename to lib/presentation/widgets/top_centered_message.dart index f34b6f8..be29d88 100644 --- a/lib/presentation/widgets/game_history_empty_builder.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -Widget GameHistoryEmptyBuilder(String message) { +Widget TopCenteredMessage(String message) { return Container( padding: EdgeInsets.only(top:100), margin: EdgeInsets.only(left: 10, right: 10), -- 2.49.1 From b41821df472c33c2c2f5f4fbade2a60b6a67675c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 16:39:51 +0200 Subject: [PATCH 028/563] format with dart format --- .../main_menu/custom_navigation_bar.dart | 8 +-- .../views/main_menu/game_history_view.dart | 34 ++++++----- .../widgets/game_history_listtile.dart | 59 ++++++++++--------- .../widgets/top_centered_message.dart | 20 +++---- 4 files changed, 63 insertions(+), 58 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 57b8519..c18d876 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -35,10 +35,7 @@ class _CustomNavigationBarState extends State centerTitle: true, title: Text( _currentTabTitle(), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, @@ -114,6 +111,7 @@ class _CustomNavigationBarState extends State currentIndex = index; }); } + String _currentTabTitle() { switch (currentIndex) { case 0: @@ -128,4 +126,4 @@ class _CustomNavigationBarState extends State return ''; } } -} \ No newline at end of file +} diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 32c41db..6d0179a 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -119,7 +119,7 @@ class _GameHistoryViewState extends State { }, ]; late List> suggestedGameData; - + @override void initState() { super.initState(); @@ -134,9 +134,7 @@ class _GameHistoryViewState extends State { children: [ Column( children: [ - Container( - margin: EdgeInsets.only(bottom: 75), - ), + Container(margin: EdgeInsets.only(bottom: 75)), Expanded( child: gameHistoryListView(allGameData, suggestedGameData), ), @@ -146,7 +144,7 @@ class _GameHistoryViewState extends State { margin: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), child: SearchBar( leading: Icon(Icons.search), - onChanged:(value) { + onChanged: (value) { if (value.isEmpty) { setState(() { suggestedGameData.clear(); @@ -155,9 +153,15 @@ class _GameHistoryViewState extends State { return; } final suggestions = allGameData.where((currentGame) { - return currentGame['game'].toString().toLowerCase().contains(value.toLowerCase()) || - currentGame['title'].toString().toLowerCase().contains(value.toLowerCase()) || - currentGame['group'].toString().toLowerCase().contains(value.toLowerCase()); + return currentGame['game'].toString().toLowerCase().contains( + value.toLowerCase(), + ) || + currentGame['title'].toString().toLowerCase().contains( + value.toLowerCase(), + ) || + currentGame['group'].toString().toLowerCase().contains( + value.toLowerCase(), + ); }); setState(() { suggestedGameData.clear(); @@ -179,10 +183,10 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); } return ListView.builder( - itemCount: suggestedGameData.length, - itemBuilder: (context, index) { - final currentGame = suggestedGameData[index]; - return GameHistoryListTile(currentGame); - } - ); -} \ No newline at end of file + itemCount: suggestedGameData.length, + itemBuilder: (context, index) { + final currentGame = suggestedGameData[index]; + return GameHistoryListTile(currentGame); + }, + ); +} diff --git a/lib/presentation/widgets/game_history_listtile.dart b/lib/presentation/widgets/game_history_listtile.dart index ccc818d..66b18b2 100644 --- a/lib/presentation/widgets/game_history_listtile.dart +++ b/lib/presentation/widgets/game_history_listtile.dart @@ -1,32 +1,35 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -Widget GameHistoryListTile(Map currentGame){ - return Container( - margin: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: CustomTheme.secondaryColor, +Widget GameHistoryListTile(Map currentGame) { + return Container( + margin: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: CustomTheme.secondaryColor, + ), + child: Column( + children: [ + Row( + children: [ + Text("${currentGame['game']}: ", style: TextStyle(fontSize: 20)), + Text("${currentGame['title']}", style: TextStyle(fontSize: 20)), + Spacer(), + Text( + "${currentGame['players']} Spieler", + style: TextStyle(fontSize: 20), ), - child: Column( - children: [ - Row( - children: [ - Text("${currentGame['game']}: ", style: TextStyle(fontSize: 20)), - Text("${currentGame['title']}", style: TextStyle(fontSize: 20)), - Spacer(), - Text("${currentGame['players']} Spieler", style: TextStyle(fontSize: 20)) - ], - ), - Row( - children: [ - Text("${currentGame['group']}", style: TextStyle(fontSize: 20)), - Spacer(), - Text("${currentGame['date']}", style: TextStyle(fontSize: 20)) - ], - ), - ], - ), - ); -} \ No newline at end of file + ], + ), + Row( + children: [ + Text("${currentGame['group']}", style: TextStyle(fontSize: 20)), + Spacer(), + Text("${currentGame['date']}", style: TextStyle(fontSize: 20)), + ], + ), + ], + ), + ); +} diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index be29d88..6fe34ff 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; Widget TopCenteredMessage(String message) { return Container( - padding: EdgeInsets.only(top:100), - margin: EdgeInsets.only(left: 10, right: 10), - alignment: Alignment.topCenter, - child: Text( - "$message", - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ) - ); -} \ No newline at end of file + padding: EdgeInsets.only(top: 100), + margin: EdgeInsets.only(left: 10, right: 10), + alignment: Alignment.topCenter, + child: Text( + "$message", + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ); +} -- 2.49.1 From 55857bd92cd394b32ae1a454b42580ae570e20cf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 17:06:20 +0200 Subject: [PATCH 029/563] rebuilt tile to be reusable and added TextOverflow behaviour --- .../widgets/double_row_info_tile.dart | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/presentation/widgets/double_row_info_tile.dart diff --git a/lib/presentation/widgets/double_row_info_tile.dart b/lib/presentation/widgets/double_row_info_tile.dart new file mode 100644 index 0000000..621cc74 --- /dev/null +++ b/lib/presentation/widgets/double_row_info_tile.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +Widget doubleRowInfoTile( + String titleOneUpperLeft, + String titleTwoUpperLeft, + String titleUpperRight, + String titleLowerLeft, + String titleLowerRight, +) { + return Container( + margin: EdgeInsets.symmetric(vertical: 5, horizontal: 10), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: CustomTheme.secondaryColor, + ), + child: Column( + children: [ + Row( + children: [ + Expanded( + flex: 10, + child: Text( + "$titleOneUpperLeft $titleTwoUpperLeft", + style: TextStyle(fontSize: 20), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + Spacer(), + Expanded( + flex: 3, + child: Text( + "$titleUpperRight", + style: TextStyle(fontSize: 20), + overflow: TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.end, + ), + ), + ], + ), + Row( + children: [ + Expanded( + flex: 10, + child: Text( + "$titleLowerLeft", + style: TextStyle(fontSize: 20), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + Spacer(), + Expanded( + flex: 4, + child: Text( + "$titleLowerRight", + style: TextStyle(fontSize: 20), + overflow: TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.end, + ), + ), + ], + ), + ], + ), + ); +} -- 2.49.1 From a4c7dcda5ceed4bdbb019cf61252c62bad9b6801 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 17:08:13 +0200 Subject: [PATCH 030/563] fixed imports and method calls due to renaming --- .../views/main_menu/game_history_view.dart | 12 +++++-- .../widgets/game_history_listtile.dart | 35 ------------------- 2 files changed, 9 insertions(+), 38 deletions(-) delete mode 100644 lib/presentation/widgets/game_history_listtile.dart diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 6d0179a..de75ae6 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -import 'package:game_tracker/presentation/widgets/game_history_listtile.dart'; +import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -21,7 +21,7 @@ class _GameHistoryViewState extends State { }, { 'game': 'Monopoly', - 'title': 'Wochenendspaß', + 'title': 'Wochenendspaß mit Gras du Saas', 'players': 4, 'group': 'Freunde', 'date': '28.05.2024', @@ -186,7 +186,13 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { itemCount: suggestedGameData.length, itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; - return GameHistoryListTile(currentGame); + return doubleRowInfoTile( + currentGame['game'] + ": ", + currentGame['title'], + currentGame['players'].toString() + " Spieler", + currentGame['group'], + currentGame['date'], + ); }, ); } diff --git a/lib/presentation/widgets/game_history_listtile.dart b/lib/presentation/widgets/game_history_listtile.dart deleted file mode 100644 index 66b18b2..0000000 --- a/lib/presentation/widgets/game_history_listtile.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -Widget GameHistoryListTile(Map currentGame) { - return Container( - margin: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: CustomTheme.secondaryColor, - ), - child: Column( - children: [ - Row( - children: [ - Text("${currentGame['game']}: ", style: TextStyle(fontSize: 20)), - Text("${currentGame['title']}", style: TextStyle(fontSize: 20)), - Spacer(), - Text( - "${currentGame['players']} Spieler", - style: TextStyle(fontSize: 20), - ), - ], - ), - Row( - children: [ - Text("${currentGame['group']}", style: TextStyle(fontSize: 20)), - Spacer(), - Text("${currentGame['date']}", style: TextStyle(fontSize: 20)), - ], - ), - ], - ), - ); -} -- 2.49.1 From d3633c4b0062178fe97ec39bfc7a0f2ac8b65196 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 23 Jun 2025 18:41:54 +0200 Subject: [PATCH 031/563] added dependencies for drift db --- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 43 ++++++++++++++++++++++++++++++++++++ pubspec.yaml | 8 +++++++ 4 files changed, 53 insertions(+) create mode 100644 ios/Podfile diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/pubspec.yaml b/pubspec.yaml index abc2e18..7c3520d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,11 +11,19 @@ dependencies: sdk: flutter material_symbols_icons: ^4.2815.1 + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + drift: ^2.27.0 + drift_flutter: ^0.2.4 + path_provider: ^2.1.5 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + drift_dev: ^2.27.0 + build_runner: ^2.5.4 flutter: uses-material-design: true -- 2.49.1 From 00ad4ad16d21aa38721461db799ea66977d24606 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 23 Jun 2025 22:29:55 +0200 Subject: [PATCH 032/563] added provider requirement for state management --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 7c3520d..5fa8019 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: drift: ^2.27.0 drift_flutter: ^0.2.4 path_provider: ^2.1.5 + provider: ^6.1.5 dev_dependencies: flutter_test: -- 2.49.1 From b2d686b2306f318190848b3516715c1a8165fa60 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:12:46 +0200 Subject: [PATCH 033/563] added provider for state management & exposed db using provider --- lib/main.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 42c1d0c..3f9b3ec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; +import 'package:game_tracker/data/database.dart'; +import 'package:provider/provider.dart'; void main() { - runApp(const MyApp()); + runApp( + Provider( + create: (context) => AppDatabase(), + child: MyApp(), + dispose: (context, db) => db.close(), + ), + ); } class MyApp extends StatelessWidget { -- 2.49.1 From 998c34e555331cd8b73d1ee4524df5f29bce6ee0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:15:06 +0200 Subject: [PATCH 034/563] created tables for Group, User and UserGrouo --- lib/data/database.dart | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/data/database.dart diff --git a/lib/data/database.dart b/lib/data/database.dart new file mode 100644 index 0000000..2c04942 --- /dev/null +++ b/lib/data/database.dart @@ -0,0 +1,46 @@ +import 'package:drift/drift.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:path_provider/path_provider.dart'; + +part 'database.g.dart'; + +class User extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} + +class Group extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} + +class UserGroup extends Table { + TextColumn get userId => text().references(User, #id)(); + TextColumn get groupId => text().references(Group, #id)(); + + @override + Set> get primaryKey => {userId, groupId}; +} + +@DriftDatabase(tables: [User, Group, UserGroup]) +class AppDatabase extends _$AppDatabase { + AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); + + @override + int get schemaVersion => 1; + + static QueryExecutor _openConnection() { + return driftDatabase( + name: 'gametracker_db', + native: const DriftNativeOptions( + databaseDirectory: getApplicationSupportDirectory, + ), + ); + } +} \ No newline at end of file -- 2.49.1 From 2b60909c43c429d3d5d0c186d0403a1f722b5615 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:16:50 +0200 Subject: [PATCH 035/563] added methods for interacting with User, Group and UserGroup in database --- lib/data/methods/group.dart | 28 ++++++++++++++++++++++++++++ lib/data/methods/user.dart | 28 ++++++++++++++++++++++++++++ lib/data/methods/user_group.dart | 26 ++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 lib/data/methods/group.dart create mode 100644 lib/data/methods/user.dart create mode 100644 lib/data/methods/user_group.dart diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart new file mode 100644 index 0000000..afc25c0 --- /dev/null +++ b/lib/data/methods/group.dart @@ -0,0 +1,28 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension Group on AppDatabase { + Future> getAllGroups() async { + return await select(group).get(); + } + + Future> getGroupById(String id) async { + return await (select(group)..where((g) => g.id.equals(id))).get(); + } + + Future addGroup(String id, String name) async { + await into(group).insert( + GroupCompanion.insert(id: id, name: name), + ); + } + + Future deleteGroup(String id) async { + await (delete(group)..where((u) => u.id.equals(id))).go(); + } + + Future updateGroupname(String id, String newName) async { + await (update(group)..where((u) => u.id.equals(id))).write( + GroupCompanion(name: Value(newName)), + ); + } +} \ No newline at end of file diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart new file mode 100644 index 0000000..46ed497 --- /dev/null +++ b/lib/data/methods/user.dart @@ -0,0 +1,28 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension User on AppDatabase { + Future> getAllUsers() async { + return await select(user).get(); + } + + Future> getUserById(String id) async { + return await (select(user)..where((u) => u.id.equals(id))).get(); + } + + Future addUser(String id, String name) async { + await into(user).insert( + UserCompanion.insert(id: id, name: name), + ); + } + + Future deleteUser(String id) async { + await (delete(user)..where((u) => u.id.equals(id))).go(); + } + + Future updateUsername(String id, String newName) async { + await (update(user)..where((u) => u.id.equals(id))).write( + UserCompanion(name: Value(newName)), + ); + } +} \ No newline at end of file diff --git a/lib/data/methods/user_group.dart b/lib/data/methods/user_group.dart new file mode 100644 index 0000000..41e1e60 --- /dev/null +++ b/lib/data/methods/user_group.dart @@ -0,0 +1,26 @@ +import 'package:game_tracker/data/database.dart'; +import 'package:drift/drift.dart'; + +extension UserGroup on AppDatabase { + Future> getAllUsersAndGroups() async { + return await select(userGroup).get(); + } + + Future> getUsersGroups(String userId) async { + return await (select(userGroup)..where((uG) => uG.userId.equals(userId))).get(); + } + + Future> getGroupsUsers(String groupId) async { + return await (select(userGroup)..where((uG) => uG.groupId.equals(groupId))).get(); + } + + Future addUserToGroup(String userId, String groupId) async { + await into(userGroup).insert( + UserGroupCompanion.insert(userId: userId, groupId: groupId), + ); + } + + Future removeUserFromGroup(String userId, String groupId) async { + await (delete(userGroup)..where((uG) => uG.userId.equals(userId) & uG.groupId.equals(groupId))).go(); + } +} \ No newline at end of file -- 2.49.1 From a32df2420a71564fca2b3f04d04cfcf8e2069a4f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:17:06 +0200 Subject: [PATCH 036/563] generated database code --- lib/data/database.g.dart | 1455 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1455 insertions(+) create mode 100644 lib/data/database.g.dart diff --git a/lib/data/database.g.dart b/lib/data/database.g.dart new file mode 100644 index 0000000..795f850 --- /dev/null +++ b/lib/data/database.g.dart @@ -0,0 +1,1455 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $UserTable extends User with TableInfo<$UserTable, UserData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $UserTable createAlias(String alias) { + return $UserTable(attachedDatabase, alias); + } +} + +class UserData extends DataClass implements Insertable { + final String id; + final String name; + const UserData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + UserCompanion toCompanion(bool nullToAbsent) { + return UserCompanion(id: Value(id), name: Value(name)); + } + + factory UserData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + UserData copyWith({String? id, String? name}) => + UserData(id: id ?? this.id, name: name ?? this.name); + UserData copyWithCompanion(UserCompanion data) { + return UserData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('UserData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserData && other.id == this.id && other.name == this.name); +} + +class UserCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const UserCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + UserCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return UserCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $GroupTable extends Group with TableInfo<$GroupTable, GroupData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GroupTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GroupData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $GroupTable createAlias(String alias) { + return $GroupTable(attachedDatabase, alias); + } +} + +class GroupData extends DataClass implements Insertable { + final String id; + final String name; + const GroupData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + GroupCompanion toCompanion(bool nullToAbsent) { + return GroupCompanion(id: Value(id), name: Value(name)); + } + + factory GroupData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + GroupData copyWith({String? id, String? name}) => + GroupData(id: id ?? this.id, name: name ?? this.name); + GroupData copyWithCompanion(GroupCompanion data) { + return GroupData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('GroupData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupData && other.id == this.id && other.name == this.name); +} + +class GroupCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const GroupCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return GroupCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $UserGroupTable extends UserGroup + with TableInfo<$UserGroupTable, UserGroupData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserGroupTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user (id)', + ), + ); + static const VerificationMeta _groupIdMeta = const VerificationMeta( + 'groupId', + ); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES "group" (id)', + ), + ); + @override + List get $columns => [userId, groupId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_group'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle( + _userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + ); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('group_id')) { + context.handle( + _groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + ); + } else if (isInserting) { + context.missing(_groupIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {userId, groupId}; + @override + UserGroupData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserGroupData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + )!, + ); + } + + @override + $UserGroupTable createAlias(String alias) { + return $UserGroupTable(attachedDatabase, alias); + } +} + +class UserGroupData extends DataClass implements Insertable { + final String userId; + final String groupId; + const UserGroupData({required this.userId, required this.groupId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['group_id'] = Variable(groupId); + return map; + } + + UserGroupCompanion toCompanion(bool nullToAbsent) { + return UserGroupCompanion(userId: Value(userId), groupId: Value(groupId)); + } + + factory UserGroupData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserGroupData( + userId: serializer.fromJson(json['userId']), + groupId: serializer.fromJson(json['groupId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'groupId': serializer.toJson(groupId), + }; + } + + UserGroupData copyWith({String? userId, String? groupId}) => UserGroupData( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + ); + UserGroupData copyWithCompanion(UserGroupCompanion data) { + return UserGroupData( + userId: data.userId.present ? data.userId.value : this.userId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, + ); + } + + @override + String toString() { + return (StringBuffer('UserGroupData(') + ..write('userId: $userId, ') + ..write('groupId: $groupId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, groupId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserGroupData && + other.userId == this.userId && + other.groupId == this.groupId); +} + +class UserGroupCompanion extends UpdateCompanion { + final Value userId; + final Value groupId; + final Value rowid; + const UserGroupCompanion({ + this.userId = const Value.absent(), + this.groupId = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserGroupCompanion.insert({ + required String userId, + required String groupId, + this.rowid = const Value.absent(), + }) : userId = Value(userId), + groupId = Value(groupId); + static Insertable custom({ + Expression? userId, + Expression? groupId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (groupId != null) 'group_id': groupId, + if (rowid != null) 'rowid': rowid, + }); + } + + UserGroupCompanion copyWith({ + Value? userId, + Value? groupId, + Value? rowid, + }) { + return UserGroupCompanion( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserGroupCompanion(') + ..write('userId: $userId, ') + ..write('groupId: $groupId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $UserTable user = $UserTable(this); + late final $GroupTable group = $GroupTable(this); + late final $UserGroupTable userGroup = $UserGroupTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [user, group, userGroup]; +} + +typedef $$UserTableCreateCompanionBuilder = + UserCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$UserTableUpdateCompanionBuilder = + UserCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +final class $$UserTableReferences + extends BaseReferences<_$AppDatabase, $UserTable, UserData> { + $$UserTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$UserGroupTable, List> + _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.userGroup, + aliasName: $_aliasNameGenerator(db.user.id, db.userGroup.userId), + ); + + $$UserGroupTableProcessedTableManager get userGroupRefs { + final manager = $$UserGroupTableTableManager( + $_db, + $_db.userGroup, + ).filter((f) => f.userId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + Expression userGroupRefs( + Expression Function($$UserGroupTableFilterComposer f) f, + ) { + final $$UserGroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableFilterComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$UserTableOrderingComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$UserTableAnnotationComposer + extends Composer<_$AppDatabase, $UserTable> { + $$UserTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + Expression userGroupRefs( + Expression Function($$UserGroupTableAnnotationComposer a) f, + ) { + final $$UserGroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableAnnotationComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$UserTableTableManager + extends + RootTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userGroupRefs}) + > { + $$UserTableTableManager(_$AppDatabase db, $UserTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => UserCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => UserCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$UserTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({userGroupRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (userGroupRefs) + await $_getPrefetchedData< + UserData, + $UserTable, + UserGroupData + >( + currentTable: table, + referencedTable: $$UserTableReferences + ._userGroupRefsTable(db), + managerFromTypedResult: (p0) => + $$UserTableReferences(db, table, p0).userGroupRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.userId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$UserTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userGroupRefs}) + >; +typedef $$GroupTableCreateCompanionBuilder = + GroupCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$GroupTableUpdateCompanionBuilder = + GroupCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +final class $$GroupTableReferences + extends BaseReferences<_$AppDatabase, $GroupTable, GroupData> { + $$GroupTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$UserGroupTable, List> + _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.userGroup, + aliasName: $_aliasNameGenerator(db.group.id, db.userGroup.groupId), + ); + + $$UserGroupTableProcessedTableManager get userGroupRefs { + final manager = $$UserGroupTableTableManager( + $_db, + $_db.userGroup, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$GroupTableFilterComposer extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + Expression userGroupRefs( + Expression Function($$UserGroupTableFilterComposer f) f, + ) { + final $$UserGroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableFilterComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GroupTableOrderingComposer + extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$GroupTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupTable> { + $$GroupTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + Expression userGroupRefs( + Expression Function($$UserGroupTableAnnotationComposer a) f, + ) { + final $$UserGroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userGroup, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserGroupTableAnnotationComposer( + $db: $db, + $table: $db.userGroup, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GroupTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GroupTable, + GroupData, + $$GroupTableFilterComposer, + $$GroupTableOrderingComposer, + $$GroupTableAnnotationComposer, + $$GroupTableCreateCompanionBuilder, + $$GroupTableUpdateCompanionBuilder, + (GroupData, $$GroupTableReferences), + GroupData, + PrefetchHooks Function({bool userGroupRefs}) + > { + $$GroupTableTableManager(_$AppDatabase db, $GroupTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GroupTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GroupTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GroupTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => GroupCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => GroupCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$GroupTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({userGroupRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (userGroupRefs) + await $_getPrefetchedData< + GroupData, + $GroupTable, + UserGroupData + >( + currentTable: table, + referencedTable: $$GroupTableReferences + ._userGroupRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableReferences(db, table, p0).userGroupRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.groupId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$GroupTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GroupTable, + GroupData, + $$GroupTableFilterComposer, + $$GroupTableOrderingComposer, + $$GroupTableAnnotationComposer, + $$GroupTableCreateCompanionBuilder, + $$GroupTableUpdateCompanionBuilder, + (GroupData, $$GroupTableReferences), + GroupData, + PrefetchHooks Function({bool userGroupRefs}) + >; +typedef $$UserGroupTableCreateCompanionBuilder = + UserGroupCompanion Function({ + required String userId, + required String groupId, + Value rowid, + }); +typedef $$UserGroupTableUpdateCompanionBuilder = + UserGroupCompanion Function({ + Value userId, + Value groupId, + Value rowid, + }); + +final class $$UserGroupTableReferences + extends BaseReferences<_$AppDatabase, $UserGroupTable, UserGroupData> { + $$UserGroupTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $UserTable _userIdTable(_$AppDatabase db) => db.user.createAlias( + $_aliasNameGenerator(db.userGroup.userId, db.user.id), + ); + + $$UserTableProcessedTableManager get userId { + final $_column = $_itemColumn('user_id')!; + + final manager = $$UserTableTableManager( + $_db, + $_db.user, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTable _groupIdTable(_$AppDatabase db) => db.group.createAlias( + $_aliasNameGenerator(db.userGroup.groupId, db.group.id), + ); + + $$GroupTableProcessedTableManager get groupId { + final $_column = $_itemColumn('group_id')!; + + final manager = $$GroupTableTableManager( + $_db, + $_db.group, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$UserGroupTableFilterComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableFilterComposer get userId { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableFilterComposer get groupId { + final $$GroupTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableFilterComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableOrderingComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableOrderingComposer get userId { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableOrderingComposer get groupId { + final $$GroupTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableOrderingComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableAnnotationComposer + extends Composer<_$AppDatabase, $UserGroupTable> { + $$UserGroupTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$UserTableAnnotationComposer get userId { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableAnnotationComposer get groupId { + final $$GroupTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.group, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableAnnotationComposer( + $db: $db, + $table: $db.group, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserGroupTableTableManager + extends + RootTableManager< + _$AppDatabase, + $UserGroupTable, + UserGroupData, + $$UserGroupTableFilterComposer, + $$UserGroupTableOrderingComposer, + $$UserGroupTableAnnotationComposer, + $$UserGroupTableCreateCompanionBuilder, + $$UserGroupTableUpdateCompanionBuilder, + (UserGroupData, $$UserGroupTableReferences), + UserGroupData, + PrefetchHooks Function({bool userId, bool groupId}) + > { + $$UserGroupTableTableManager(_$AppDatabase db, $UserGroupTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserGroupTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserGroupTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserGroupTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value groupId = const Value.absent(), + Value rowid = const Value.absent(), + }) => UserGroupCompanion( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String userId, + required String groupId, + Value rowid = const Value.absent(), + }) => UserGroupCompanion.insert( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$UserGroupTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({userId = false, groupId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (userId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: $$UserGroupTableReferences + ._userIdTable(db), + referencedColumn: $$UserGroupTableReferences + ._userIdTable(db) + .id, + ) + as T; + } + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: $$UserGroupTableReferences + ._groupIdTable(db), + referencedColumn: $$UserGroupTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$UserGroupTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $UserGroupTable, + UserGroupData, + $$UserGroupTableFilterComposer, + $$UserGroupTableOrderingComposer, + $$UserGroupTableAnnotationComposer, + $$UserGroupTableCreateCompanionBuilder, + $$UserGroupTableUpdateCompanionBuilder, + (UserGroupData, $$UserGroupTableReferences), + UserGroupData, + PrefetchHooks Function({bool userId, bool groupId}) + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$UserTableTableManager get user => $$UserTableTableManager(_db, _db.user); + $$GroupTableTableManager get group => + $$GroupTableTableManager(_db, _db.group); + $$UserGroupTableTableManager get userGroup => + $$UserGroupTableTableManager(_db, _db.userGroup); +} -- 2.49.1 From 6371d7de68f8d209bd9aae1329e0b729a390d44e Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:26:34 +0200 Subject: [PATCH 037/563] Update lib/data/methods/group.dart renamed var in deleteGroup to g instead of u Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/data/methods/group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index afc25c0..bb5a131 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -17,7 +17,7 @@ extension Group on AppDatabase { } Future deleteGroup(String id) async { - await (delete(group)..where((u) => u.id.equals(id))).go(); + await (delete(group)..where((g) => g.id.equals(id))).go(); } Future updateGroupname(String id, String newName) async { -- 2.49.1 From d771f78810e3191b4a89483653618002be7e3e2f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:28:26 +0200 Subject: [PATCH 038/563] renamed class names to avoid conflicts --- lib/data/methods/group.dart | 2 +- lib/data/methods/user.dart | 2 +- lib/data/methods/user_group.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index bb5a131..d996af6 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension Group on AppDatabase { +extension GroupMethods on AppDatabase { Future> getAllGroups() async { return await select(group).get(); } diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart index 46ed497..ddf26b0 100644 --- a/lib/data/methods/user.dart +++ b/lib/data/methods/user.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension User on AppDatabase { +extension UserMethods on AppDatabase { Future> getAllUsers() async { return await select(user).get(); } diff --git a/lib/data/methods/user_group.dart b/lib/data/methods/user_group.dart index 41e1e60..206c1f0 100644 --- a/lib/data/methods/user_group.dart +++ b/lib/data/methods/user_group.dart @@ -1,7 +1,7 @@ import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; -extension UserGroup on AppDatabase { +extension UserGroupMethods on AppDatabase { Future> getAllUsersAndGroups() async { return await select(userGroup).get(); } -- 2.49.1 From b4e91b1211d34c0bf2324ba1821824fc54392190 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:35:30 +0200 Subject: [PATCH 039/563] changed return type of getGroupById and getUserById to match expected result --- lib/data/methods/group.dart | 4 ++-- lib/data/methods/user.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index d996af6..50a1745 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -6,8 +6,8 @@ extension GroupMethods on AppDatabase { return await select(group).get(); } - Future> getGroupById(String id) async { - return await (select(group)..where((g) => g.id.equals(id))).get(); + Future getGroupById(String id) async { + return await (select(group)..where((g) => g.id.equals(id))).getSingle(); } Future addGroup(String id, String name) async { diff --git a/lib/data/methods/user.dart b/lib/data/methods/user.dart index ddf26b0..6f4c508 100644 --- a/lib/data/methods/user.dart +++ b/lib/data/methods/user.dart @@ -6,8 +6,8 @@ extension UserMethods on AppDatabase { return await select(user).get(); } - Future> getUserById(String id) async { - return await (select(user)..where((u) => u.id.equals(id))).get(); + Future getUserById(String id) async { + return await (select(user)..where((u) => u.id.equals(id))).getSingle(); } Future addUser(String id, String name) async { -- 2.49.1 From 00cedf8647a3dd7ed49dccb04c03f9d67d1eb6f0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:38:29 +0200 Subject: [PATCH 040/563] renamed lambda var in updateGroupname from u to g --- lib/data/methods/group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart index 50a1745..a5346ba 100644 --- a/lib/data/methods/group.dart +++ b/lib/data/methods/group.dart @@ -21,7 +21,7 @@ extension GroupMethods on AppDatabase { } Future updateGroupname(String id, String newName) async { - await (update(group)..where((u) => u.id.equals(id))).write( + await (update(group)..where((g) => g.id.equals(id))).write( GroupCompanion(name: Value(newName)), ); } -- 2.49.1 From 41e9eb2c91bb21c159cf5b255dfc66b058de7512 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 24 Jun 2025 20:40:19 +0200 Subject: [PATCH 041/563] added const to MyApp in Providers child --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 3f9b3ec..dc582a4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,7 @@ void main() { runApp( Provider( create: (context) => AppDatabase(), - child: MyApp(), + child: const MyApp(), dispose: (context, db) => db.close(), ), ); -- 2.49.1 From ed275b0f7c108c4ae4ac4371fbe21042bc028dd9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 25 Jun 2025 23:24:33 +0200 Subject: [PATCH 042/563] fixed import of custom nav bar --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index dc582a4..478c094 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/database.dart'; import 'package:provider/provider.dart'; -- 2.49.1 From 6898ed51a38c7add6774e28c4e743fb0eb9022a2 Mon Sep 17 00:00:00 2001 From: Sneeex <65130981+mathiskir@users.noreply.github.com> Date: Wed, 25 Jun 2025 23:46:54 +0200 Subject: [PATCH 043/563] remove unused home view import --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 478c094..261ff1b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/database.dart'; -- 2.49.1 From a795732da606d9321e8158f00cb1cf53a53f63b6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 8 Nov 2025 13:49:08 +0100 Subject: [PATCH 044/563] Updated theme --- lib/core/custom_theme.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index afc8dee..1a5741a 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; class CustomTheme { - static Color primaryColor = const Color(0xFF71C0BB); - static Color secondaryColor = const Color(0xFF2A4759); + static Color primaryColor = const Color(0xFF7505E4); + static Color secondaryColor = const Color(0xFFAFA2FF); static Color backgroundColor = const Color(0xFF1A1A1A); - static AppBarTheme appBarTheme = const AppBarTheme( - backgroundColor: Color(0xFF1A1A1A), + static AppBarTheme appBarTheme = AppBarTheme( + backgroundColor: backgroundColor, foregroundColor: Colors.white, elevation: 0, - titleTextStyle: TextStyle( + titleTextStyle: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), - iconTheme: IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: Colors.white), ); } -- 2.49.1 From 9db8c80d63a04fa4a06e8b4c3a027927b53b4f9e Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:29:39 +0100 Subject: [PATCH 045/563] removed floating button --- .../views/main_menu/custom_navigation_bar.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index c18d876..a78a9f9 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -53,20 +53,12 @@ class _CustomNavigationBarState extends State backgroundColor: CustomTheme.backgroundColor, body: tabs[currentIndex], extendBody: true, - floatingActionButton: FloatingActionButton( - shape: const CircleBorder(), - backgroundColor: CustomTheme.primaryColor, - onPressed: () {}, - child: const Icon(Icons.add), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: BottomAppBar( padding: const EdgeInsets.symmetric(horizontal: 10), elevation: 10, height: 60, color: CustomTheme.primaryColor, - shape: const CircularNotchedRectangle(), - notchMargin: 5, + shape: null, child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -85,7 +77,6 @@ class _CustomNavigationBarState extends State ), onPressed: () => onTabTapped(1), ), - const SizedBox(width: 40), IconButton( icon: Icon( Icons.groups, -- 2.49.1 From 4503574443da27e43d83863956644b70b7dc4930 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 8 Nov 2025 15:21:05 +0100 Subject: [PATCH 046/563] Implemented basic structure --- lib/data/dao/group_dao.dart | 42 + lib/data/dao/group_dao.g.dart | 8 + .../user.dart => dao/player_dao.dart} | 21 +- lib/data/dao/player_dao.g.dart | 8 + lib/data/dao/player_group_dao.dart | 40 + lib/data/dao/player_group_dao.g.dart | 9 + lib/data/database.dart | 46 - lib/data/database.g.dart | 1455 ----------------- lib/data/db/database.dart | 31 + lib/data/db/database.g.dart | 1092 +++++++++++++ lib/data/db/tables/group_table.dart | 9 + lib/data/db/tables/player_group_table.dart | 11 + lib/data/db/tables/player_table.dart | 9 + lib/data/dto/game.dart | 6 + lib/data/dto/group.dart | 9 + lib/data/dto/player.dart | 6 + lib/data/methods/group.dart | 28 - lib/data/methods/user_group.dart | 26 - lib/main.dart | 4 +- 19 files changed, 1295 insertions(+), 1565 deletions(-) create mode 100644 lib/data/dao/group_dao.dart create mode 100644 lib/data/dao/group_dao.g.dart rename lib/data/{methods/user.dart => dao/player_dao.dart} (51%) create mode 100644 lib/data/dao/player_dao.g.dart create mode 100644 lib/data/dao/player_group_dao.dart create mode 100644 lib/data/dao/player_group_dao.g.dart delete mode 100644 lib/data/database.dart delete mode 100644 lib/data/database.g.dart create mode 100644 lib/data/db/database.dart create mode 100644 lib/data/db/database.g.dart create mode 100644 lib/data/db/tables/group_table.dart create mode 100644 lib/data/db/tables/player_group_table.dart create mode 100644 lib/data/db/tables/player_table.dart create mode 100644 lib/data/dto/game.dart create mode 100644 lib/data/dto/group.dart create mode 100644 lib/data/dto/player.dart delete mode 100644 lib/data/methods/group.dart delete mode 100644 lib/data/methods/user_group.dart diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart new file mode 100644 index 0000000..d2f529c --- /dev/null +++ b/lib/data/dao/group_dao.dart @@ -0,0 +1,42 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; + +part 'group_dao.g.dart'; + +@DriftAccessor(tables: [GroupTable]) +class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { + GroupDao(super.db); + + Future> getAllGroups() async { + final query = select(groupTable); + final result = await query.get(); + return result + .map((row) => Group(id: row.id, name: row.name, members: [])) + .toList(); + } + + Future getGroupById(String id) async { + final query = select(groupTable)..where((g) => g.id.equals(id)); + final result = await query.getSingle(); + + // todo: Get group members + + return Group(id: result.id, name: result.name, members: []); + } + + Future addGroup(String id, String name) async { + await into(group).insert(GroupCompanion.insert(id: id, name: name)); + } + + Future deleteGroup(String id) async { + await (delete(group)..where((g) => g.id.equals(id))).go(); + } + + Future updateGroupname(String id, String newName) async { + await (update(group)..where((g) => g.id.equals(id))).write( + GroupCompanion(name: Value(newName)), + ); + } +} diff --git a/lib/data/dao/group_dao.g.dart b/lib/data/dao/group_dao.g.dart new file mode 100644 index 0000000..4a09208 --- /dev/null +++ b/lib/data/dao/group_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'group_dao.dart'; + +// ignore_for_file: type=lint +mixin _$GroupDaoMixin on DatabaseAccessor { + $GroupTableTable get groupTable => attachedDatabase.groupTable; +} diff --git a/lib/data/methods/user.dart b/lib/data/dao/player_dao.dart similarity index 51% rename from lib/data/methods/user.dart rename to lib/data/dao/player_dao.dart index 6f4c508..d7a7596 100644 --- a/lib/data/methods/user.dart +++ b/lib/data/dao/player_dao.dart @@ -1,9 +1,16 @@ -import 'package:game_tracker/data/database.dart'; import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; +import 'package:game_tracker/data/dto/player.dart'; -extension UserMethods on AppDatabase { - Future> getAllUsers() async { - return await select(user).get(); +part 'player_dao.g.dart'; + +@DriftAccessor(tables: [PlayerTable]) +class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { + PlayerDao(super.db); + + Future> getAllUsers() async { + return await select(UserTable).get(); } Future getUserById(String id) async { @@ -11,9 +18,7 @@ extension UserMethods on AppDatabase { } Future addUser(String id, String name) async { - await into(user).insert( - UserCompanion.insert(id: id, name: name), - ); + await into(user).insert(UserCompanion.insert(id: id, name: name)); } Future deleteUser(String id) async { @@ -25,4 +30,4 @@ extension UserMethods on AppDatabase { UserCompanion(name: Value(newName)), ); } -} \ No newline at end of file +} diff --git a/lib/data/dao/player_dao.g.dart b/lib/data/dao/player_dao.g.dart new file mode 100644 index 0000000..c517581 --- /dev/null +++ b/lib/data/dao/player_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PlayerDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; +} diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart new file mode 100644 index 0000000..c4daf21 --- /dev/null +++ b/lib/data/dao/player_group_dao.dart @@ -0,0 +1,40 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/player_group_table.dart'; + +part 'player_group_dao.g.dart'; + +@DriftAccessor(tables: [PlayerGroupTable]) +class PlayerGroupDao extends DatabaseAccessor + with _$PlayerGroupDaoMixin { + PlayerGroupDao(super.db); + + Future> getAllUsersAndGroups() async { + return await select(userGroup).get(); + } + + Future> getUsersGroups(String userId) async { + return await (select( + userGroup, + )..where((uG) => uG.userId.equals(userId))).get(); + } + + Future> getGroupsUsers(String groupId) async { + return await (select( + userGroup, + )..where((uG) => uG.groupId.equals(groupId))).get(); + } + + Future addUserToGroup(String userId, String groupId) async { + await into( + userGroup, + ).insert(UserGroupCompanion.insert(userId: userId, groupId: groupId)); + } + + Future removeUserFromGroup(String userId, String groupId) async { + await (delete( + userGroup, + )..where((uG) => uG.userId.equals(userId) & uG.groupId.equals(groupId))) + .go(); + } +} diff --git a/lib/data/dao/player_group_dao.g.dart b/lib/data/dao/player_group_dao.g.dart new file mode 100644 index 0000000..d512bb4 --- /dev/null +++ b/lib/data/dao/player_group_dao.g.dart @@ -0,0 +1,9 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_group_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PlayerGroupDaoMixin on DatabaseAccessor { + $PlayerGroupTableTable get playerGroupTable => + attachedDatabase.playerGroupTable; +} diff --git a/lib/data/database.dart b/lib/data/database.dart deleted file mode 100644 index 2c04942..0000000 --- a/lib/data/database.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; -import 'package:path_provider/path_provider.dart'; - -part 'database.g.dart'; - -class User extends Table { - TextColumn get id => text()(); - TextColumn get name => text()(); - - @override - Set> get primaryKey => {id}; -} - -class Group extends Table { - TextColumn get id => text()(); - TextColumn get name => text()(); - - @override - Set> get primaryKey => {id}; -} - -class UserGroup extends Table { - TextColumn get userId => text().references(User, #id)(); - TextColumn get groupId => text().references(Group, #id)(); - - @override - Set> get primaryKey => {userId, groupId}; -} - -@DriftDatabase(tables: [User, Group, UserGroup]) -class AppDatabase extends _$AppDatabase { - AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); - - @override - int get schemaVersion => 1; - - static QueryExecutor _openConnection() { - return driftDatabase( - name: 'gametracker_db', - native: const DriftNativeOptions( - databaseDirectory: getApplicationSupportDirectory, - ), - ); - } -} \ No newline at end of file diff --git a/lib/data/database.g.dart b/lib/data/database.g.dart deleted file mode 100644 index 795f850..0000000 --- a/lib/data/database.g.dart +++ /dev/null @@ -1,1455 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'database.dart'; - -// ignore_for_file: type=lint -class $UserTable extends User with TableInfo<$UserTable, UserData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $UserTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _nameMeta = const VerificationMeta('name'); - @override - late final GeneratedColumn name = GeneratedColumn( - 'name', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - @override - List get $columns => [id, name]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'user'; - @override - VerificationContext validateIntegrity( - Insertable instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } else if (isInserting) { - context.missing(_idMeta); - } - if (data.containsKey('name')) { - context.handle( - _nameMeta, - name.isAcceptableOrUnknown(data['name']!, _nameMeta), - ); - } else if (isInserting) { - context.missing(_nameMeta); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - UserData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return UserData( - id: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}id'], - )!, - name: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}name'], - )!, - ); - } - - @override - $UserTable createAlias(String alias) { - return $UserTable(attachedDatabase, alias); - } -} - -class UserData extends DataClass implements Insertable { - final String id; - final String name; - const UserData({required this.id, required this.name}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['name'] = Variable(name); - return map; - } - - UserCompanion toCompanion(bool nullToAbsent) { - return UserCompanion(id: Value(id), name: Value(name)); - } - - factory UserData.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return UserData( - id: serializer.fromJson(json['id']), - name: serializer.fromJson(json['name']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'name': serializer.toJson(name), - }; - } - - UserData copyWith({String? id, String? name}) => - UserData(id: id ?? this.id, name: name ?? this.name); - UserData copyWithCompanion(UserCompanion data) { - return UserData( - id: data.id.present ? data.id.value : this.id, - name: data.name.present ? data.name.value : this.name, - ); - } - - @override - String toString() { - return (StringBuffer('UserData(') - ..write('id: $id, ') - ..write('name: $name') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, name); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is UserData && other.id == this.id && other.name == this.name); -} - -class UserCompanion extends UpdateCompanion { - final Value id; - final Value name; - final Value rowid; - const UserCompanion({ - this.id = const Value.absent(), - this.name = const Value.absent(), - this.rowid = const Value.absent(), - }); - UserCompanion.insert({ - required String id, - required String name, - this.rowid = const Value.absent(), - }) : id = Value(id), - name = Value(name); - static Insertable custom({ - Expression? id, - Expression? name, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (name != null) 'name': name, - if (rowid != null) 'rowid': rowid, - }); - } - - UserCompanion copyWith({ - Value? id, - Value? name, - Value? rowid, - }) { - return UserCompanion( - id: id ?? this.id, - name: name ?? this.name, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (name.present) { - map['name'] = Variable(name.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('UserCompanion(') - ..write('id: $id, ') - ..write('name: $name, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -class $GroupTable extends Group with TableInfo<$GroupTable, GroupData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $GroupTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _nameMeta = const VerificationMeta('name'); - @override - late final GeneratedColumn name = GeneratedColumn( - 'name', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - @override - List get $columns => [id, name]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'group'; - @override - VerificationContext validateIntegrity( - Insertable instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } else if (isInserting) { - context.missing(_idMeta); - } - if (data.containsKey('name')) { - context.handle( - _nameMeta, - name.isAcceptableOrUnknown(data['name']!, _nameMeta), - ); - } else if (isInserting) { - context.missing(_nameMeta); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - GroupData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GroupData( - id: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}id'], - )!, - name: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}name'], - )!, - ); - } - - @override - $GroupTable createAlias(String alias) { - return $GroupTable(attachedDatabase, alias); - } -} - -class GroupData extends DataClass implements Insertable { - final String id; - final String name; - const GroupData({required this.id, required this.name}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['name'] = Variable(name); - return map; - } - - GroupCompanion toCompanion(bool nullToAbsent) { - return GroupCompanion(id: Value(id), name: Value(name)); - } - - factory GroupData.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return GroupData( - id: serializer.fromJson(json['id']), - name: serializer.fromJson(json['name']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'name': serializer.toJson(name), - }; - } - - GroupData copyWith({String? id, String? name}) => - GroupData(id: id ?? this.id, name: name ?? this.name); - GroupData copyWithCompanion(GroupCompanion data) { - return GroupData( - id: data.id.present ? data.id.value : this.id, - name: data.name.present ? data.name.value : this.name, - ); - } - - @override - String toString() { - return (StringBuffer('GroupData(') - ..write('id: $id, ') - ..write('name: $name') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, name); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is GroupData && other.id == this.id && other.name == this.name); -} - -class GroupCompanion extends UpdateCompanion { - final Value id; - final Value name; - final Value rowid; - const GroupCompanion({ - this.id = const Value.absent(), - this.name = const Value.absent(), - this.rowid = const Value.absent(), - }); - GroupCompanion.insert({ - required String id, - required String name, - this.rowid = const Value.absent(), - }) : id = Value(id), - name = Value(name); - static Insertable custom({ - Expression? id, - Expression? name, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (name != null) 'name': name, - if (rowid != null) 'rowid': rowid, - }); - } - - GroupCompanion copyWith({ - Value? id, - Value? name, - Value? rowid, - }) { - return GroupCompanion( - id: id ?? this.id, - name: name ?? this.name, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (name.present) { - map['name'] = Variable(name.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('GroupCompanion(') - ..write('id: $id, ') - ..write('name: $name, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -class $UserGroupTable extends UserGroup - with TableInfo<$UserGroupTable, UserGroupData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $UserGroupTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); - @override - late final GeneratedColumn userId = GeneratedColumn( - 'user_id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES user (id)', - ), - ); - static const VerificationMeta _groupIdMeta = const VerificationMeta( - 'groupId', - ); - @override - late final GeneratedColumn groupId = GeneratedColumn( - 'group_id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES "group" (id)', - ), - ); - @override - List get $columns => [userId, groupId]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'user_group'; - @override - VerificationContext validateIntegrity( - Insertable instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('user_id')) { - context.handle( - _userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), - ); - } else if (isInserting) { - context.missing(_userIdMeta); - } - if (data.containsKey('group_id')) { - context.handle( - _groupIdMeta, - groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), - ); - } else if (isInserting) { - context.missing(_groupIdMeta); - } - return context; - } - - @override - Set get $primaryKey => {userId, groupId}; - @override - UserGroupData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return UserGroupData( - userId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}user_id'], - )!, - groupId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}group_id'], - )!, - ); - } - - @override - $UserGroupTable createAlias(String alias) { - return $UserGroupTable(attachedDatabase, alias); - } -} - -class UserGroupData extends DataClass implements Insertable { - final String userId; - final String groupId; - const UserGroupData({required this.userId, required this.groupId}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['user_id'] = Variable(userId); - map['group_id'] = Variable(groupId); - return map; - } - - UserGroupCompanion toCompanion(bool nullToAbsent) { - return UserGroupCompanion(userId: Value(userId), groupId: Value(groupId)); - } - - factory UserGroupData.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return UserGroupData( - userId: serializer.fromJson(json['userId']), - groupId: serializer.fromJson(json['groupId']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'userId': serializer.toJson(userId), - 'groupId': serializer.toJson(groupId), - }; - } - - UserGroupData copyWith({String? userId, String? groupId}) => UserGroupData( - userId: userId ?? this.userId, - groupId: groupId ?? this.groupId, - ); - UserGroupData copyWithCompanion(UserGroupCompanion data) { - return UserGroupData( - userId: data.userId.present ? data.userId.value : this.userId, - groupId: data.groupId.present ? data.groupId.value : this.groupId, - ); - } - - @override - String toString() { - return (StringBuffer('UserGroupData(') - ..write('userId: $userId, ') - ..write('groupId: $groupId') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(userId, groupId); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is UserGroupData && - other.userId == this.userId && - other.groupId == this.groupId); -} - -class UserGroupCompanion extends UpdateCompanion { - final Value userId; - final Value groupId; - final Value rowid; - const UserGroupCompanion({ - this.userId = const Value.absent(), - this.groupId = const Value.absent(), - this.rowid = const Value.absent(), - }); - UserGroupCompanion.insert({ - required String userId, - required String groupId, - this.rowid = const Value.absent(), - }) : userId = Value(userId), - groupId = Value(groupId); - static Insertable custom({ - Expression? userId, - Expression? groupId, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (userId != null) 'user_id': userId, - if (groupId != null) 'group_id': groupId, - if (rowid != null) 'rowid': rowid, - }); - } - - UserGroupCompanion copyWith({ - Value? userId, - Value? groupId, - Value? rowid, - }) { - return UserGroupCompanion( - userId: userId ?? this.userId, - groupId: groupId ?? this.groupId, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (userId.present) { - map['user_id'] = Variable(userId.value); - } - if (groupId.present) { - map['group_id'] = Variable(groupId.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('UserGroupCompanion(') - ..write('userId: $userId, ') - ..write('groupId: $groupId, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -abstract class _$AppDatabase extends GeneratedDatabase { - _$AppDatabase(QueryExecutor e) : super(e); - $AppDatabaseManager get managers => $AppDatabaseManager(this); - late final $UserTable user = $UserTable(this); - late final $GroupTable group = $GroupTable(this); - late final $UserGroupTable userGroup = $UserGroupTable(this); - @override - Iterable> get allTables => - allSchemaEntities.whereType>(); - @override - List get allSchemaEntities => [user, group, userGroup]; -} - -typedef $$UserTableCreateCompanionBuilder = - UserCompanion Function({ - required String id, - required String name, - Value rowid, - }); -typedef $$UserTableUpdateCompanionBuilder = - UserCompanion Function({ - Value id, - Value name, - Value rowid, - }); - -final class $$UserTableReferences - extends BaseReferences<_$AppDatabase, $UserTable, UserData> { - $$UserTableReferences(super.$_db, super.$_table, super.$_typedResult); - - static MultiTypedResultKey<$UserGroupTable, List> - _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.userGroup, - aliasName: $_aliasNameGenerator(db.user.id, db.userGroup.userId), - ); - - $$UserGroupTableProcessedTableManager get userGroupRefs { - final manager = $$UserGroupTableTableManager( - $_db, - $_db.userGroup, - ).filter((f) => f.userId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } -} - -class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { - $$UserTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnFilters(column), - ); - - Expression userGroupRefs( - Expression Function($$UserGroupTableFilterComposer f) f, - ) { - final $$UserGroupTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.userGroup, - getReferencedColumn: (t) => t.userId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserGroupTableFilterComposer( - $db: $db, - $table: $db.userGroup, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } -} - -class $$UserTableOrderingComposer extends Composer<_$AppDatabase, $UserTable> { - $$UserTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnOrderings(column), - ); -} - -class $$UserTableAnnotationComposer - extends Composer<_$AppDatabase, $UserTable> { - $$UserTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); - - Expression userGroupRefs( - Expression Function($$UserGroupTableAnnotationComposer a) f, - ) { - final $$UserGroupTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.userGroup, - getReferencedColumn: (t) => t.userId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserGroupTableAnnotationComposer( - $db: $db, - $table: $db.userGroup, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } -} - -class $$UserTableTableManager - extends - RootTableManager< - _$AppDatabase, - $UserTable, - UserData, - $$UserTableFilterComposer, - $$UserTableOrderingComposer, - $$UserTableAnnotationComposer, - $$UserTableCreateCompanionBuilder, - $$UserTableUpdateCompanionBuilder, - (UserData, $$UserTableReferences), - UserData, - PrefetchHooks Function({bool userGroupRefs}) - > { - $$UserTableTableManager(_$AppDatabase db, $UserTable table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$UserTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$UserTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$UserTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value id = const Value.absent(), - Value name = const Value.absent(), - Value rowid = const Value.absent(), - }) => UserCompanion(id: id, name: name, rowid: rowid), - createCompanionCallback: - ({ - required String id, - required String name, - Value rowid = const Value.absent(), - }) => UserCompanion.insert(id: id, name: name, rowid: rowid), - withReferenceMapper: (p0) => p0 - .map( - (e) => - (e.readTable(table), $$UserTableReferences(db, table, e)), - ) - .toList(), - prefetchHooksCallback: ({userGroupRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (userGroupRefs) - await $_getPrefetchedData< - UserData, - $UserTable, - UserGroupData - >( - currentTable: table, - referencedTable: $$UserTableReferences - ._userGroupRefsTable(db), - managerFromTypedResult: (p0) => - $$UserTableReferences(db, table, p0).userGroupRefs, - referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.userId == item.id), - typedResults: items, - ), - ]; - }, - ); - }, - ), - ); -} - -typedef $$UserTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $UserTable, - UserData, - $$UserTableFilterComposer, - $$UserTableOrderingComposer, - $$UserTableAnnotationComposer, - $$UserTableCreateCompanionBuilder, - $$UserTableUpdateCompanionBuilder, - (UserData, $$UserTableReferences), - UserData, - PrefetchHooks Function({bool userGroupRefs}) - >; -typedef $$GroupTableCreateCompanionBuilder = - GroupCompanion Function({ - required String id, - required String name, - Value rowid, - }); -typedef $$GroupTableUpdateCompanionBuilder = - GroupCompanion Function({ - Value id, - Value name, - Value rowid, - }); - -final class $$GroupTableReferences - extends BaseReferences<_$AppDatabase, $GroupTable, GroupData> { - $$GroupTableReferences(super.$_db, super.$_table, super.$_typedResult); - - static MultiTypedResultKey<$UserGroupTable, List> - _userGroupRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.userGroup, - aliasName: $_aliasNameGenerator(db.group.id, db.userGroup.groupId), - ); - - $$UserGroupTableProcessedTableManager get userGroupRefs { - final manager = $$UserGroupTableTableManager( - $_db, - $_db.userGroup, - ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_userGroupRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } -} - -class $$GroupTableFilterComposer extends Composer<_$AppDatabase, $GroupTable> { - $$GroupTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnFilters(column), - ); - - Expression userGroupRefs( - Expression Function($$UserGroupTableFilterComposer f) f, - ) { - final $$UserGroupTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.userGroup, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserGroupTableFilterComposer( - $db: $db, - $table: $db.userGroup, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } -} - -class $$GroupTableOrderingComposer - extends Composer<_$AppDatabase, $GroupTable> { - $$GroupTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnOrderings(column), - ); -} - -class $$GroupTableAnnotationComposer - extends Composer<_$AppDatabase, $GroupTable> { - $$GroupTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); - - Expression userGroupRefs( - Expression Function($$UserGroupTableAnnotationComposer a) f, - ) { - final $$UserGroupTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.userGroup, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserGroupTableAnnotationComposer( - $db: $db, - $table: $db.userGroup, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } -} - -class $$GroupTableTableManager - extends - RootTableManager< - _$AppDatabase, - $GroupTable, - GroupData, - $$GroupTableFilterComposer, - $$GroupTableOrderingComposer, - $$GroupTableAnnotationComposer, - $$GroupTableCreateCompanionBuilder, - $$GroupTableUpdateCompanionBuilder, - (GroupData, $$GroupTableReferences), - GroupData, - PrefetchHooks Function({bool userGroupRefs}) - > { - $$GroupTableTableManager(_$AppDatabase db, $GroupTable table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$GroupTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$GroupTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$GroupTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value id = const Value.absent(), - Value name = const Value.absent(), - Value rowid = const Value.absent(), - }) => GroupCompanion(id: id, name: name, rowid: rowid), - createCompanionCallback: - ({ - required String id, - required String name, - Value rowid = const Value.absent(), - }) => GroupCompanion.insert(id: id, name: name, rowid: rowid), - withReferenceMapper: (p0) => p0 - .map( - (e) => - (e.readTable(table), $$GroupTableReferences(db, table, e)), - ) - .toList(), - prefetchHooksCallback: ({userGroupRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [if (userGroupRefs) db.userGroup], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (userGroupRefs) - await $_getPrefetchedData< - GroupData, - $GroupTable, - UserGroupData - >( - currentTable: table, - referencedTable: $$GroupTableReferences - ._userGroupRefsTable(db), - managerFromTypedResult: (p0) => - $$GroupTableReferences(db, table, p0).userGroupRefs, - referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.groupId == item.id), - typedResults: items, - ), - ]; - }, - ); - }, - ), - ); -} - -typedef $$GroupTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $GroupTable, - GroupData, - $$GroupTableFilterComposer, - $$GroupTableOrderingComposer, - $$GroupTableAnnotationComposer, - $$GroupTableCreateCompanionBuilder, - $$GroupTableUpdateCompanionBuilder, - (GroupData, $$GroupTableReferences), - GroupData, - PrefetchHooks Function({bool userGroupRefs}) - >; -typedef $$UserGroupTableCreateCompanionBuilder = - UserGroupCompanion Function({ - required String userId, - required String groupId, - Value rowid, - }); -typedef $$UserGroupTableUpdateCompanionBuilder = - UserGroupCompanion Function({ - Value userId, - Value groupId, - Value rowid, - }); - -final class $$UserGroupTableReferences - extends BaseReferences<_$AppDatabase, $UserGroupTable, UserGroupData> { - $$UserGroupTableReferences(super.$_db, super.$_table, super.$_typedResult); - - static $UserTable _userIdTable(_$AppDatabase db) => db.user.createAlias( - $_aliasNameGenerator(db.userGroup.userId, db.user.id), - ); - - $$UserTableProcessedTableManager get userId { - final $_column = $_itemColumn('user_id')!; - - final manager = $$UserTableTableManager( - $_db, - $_db.user, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_userIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - - static $GroupTable _groupIdTable(_$AppDatabase db) => db.group.createAlias( - $_aliasNameGenerator(db.userGroup.groupId, db.group.id), - ); - - $$GroupTableProcessedTableManager get groupId { - final $_column = $_itemColumn('group_id')!; - - final manager = $$GroupTableTableManager( - $_db, - $_db.group, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } -} - -class $$UserGroupTableFilterComposer - extends Composer<_$AppDatabase, $UserGroupTable> { - $$UserGroupTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$UserTableFilterComposer get userId { - final $$UserTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.userId, - referencedTable: $db.user, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserTableFilterComposer( - $db: $db, - $table: $db.user, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$GroupTableFilterComposer get groupId { - final $$GroupTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.group, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableFilterComposer( - $db: $db, - $table: $db.group, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$UserGroupTableOrderingComposer - extends Composer<_$AppDatabase, $UserGroupTable> { - $$UserGroupTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$UserTableOrderingComposer get userId { - final $$UserTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.userId, - referencedTable: $db.user, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserTableOrderingComposer( - $db: $db, - $table: $db.user, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$GroupTableOrderingComposer get groupId { - final $$GroupTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.group, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableOrderingComposer( - $db: $db, - $table: $db.group, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$UserGroupTableAnnotationComposer - extends Composer<_$AppDatabase, $UserGroupTable> { - $$UserGroupTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$UserTableAnnotationComposer get userId { - final $$UserTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.userId, - referencedTable: $db.user, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$UserTableAnnotationComposer( - $db: $db, - $table: $db.user, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$GroupTableAnnotationComposer get groupId { - final $$GroupTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.group, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableAnnotationComposer( - $db: $db, - $table: $db.group, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$UserGroupTableTableManager - extends - RootTableManager< - _$AppDatabase, - $UserGroupTable, - UserGroupData, - $$UserGroupTableFilterComposer, - $$UserGroupTableOrderingComposer, - $$UserGroupTableAnnotationComposer, - $$UserGroupTableCreateCompanionBuilder, - $$UserGroupTableUpdateCompanionBuilder, - (UserGroupData, $$UserGroupTableReferences), - UserGroupData, - PrefetchHooks Function({bool userId, bool groupId}) - > { - $$UserGroupTableTableManager(_$AppDatabase db, $UserGroupTable table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$UserGroupTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$UserGroupTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$UserGroupTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value userId = const Value.absent(), - Value groupId = const Value.absent(), - Value rowid = const Value.absent(), - }) => UserGroupCompanion( - userId: userId, - groupId: groupId, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String userId, - required String groupId, - Value rowid = const Value.absent(), - }) => UserGroupCompanion.insert( - userId: userId, - groupId: groupId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map( - (e) => ( - e.readTable(table), - $$UserGroupTableReferences(db, table, e), - ), - ) - .toList(), - prefetchHooksCallback: ({userId = false, groupId = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (userId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.userId, - referencedTable: $$UserGroupTableReferences - ._userIdTable(db), - referencedColumn: $$UserGroupTableReferences - ._userIdTable(db) - .id, - ) - as T; - } - if (groupId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.groupId, - referencedTable: $$UserGroupTableReferences - ._groupIdTable(db), - referencedColumn: $$UserGroupTableReferences - ._groupIdTable(db) - .id, - ) - as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return []; - }, - ); - }, - ), - ); -} - -typedef $$UserGroupTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $UserGroupTable, - UserGroupData, - $$UserGroupTableFilterComposer, - $$UserGroupTableOrderingComposer, - $$UserGroupTableAnnotationComposer, - $$UserGroupTableCreateCompanionBuilder, - $$UserGroupTableUpdateCompanionBuilder, - (UserGroupData, $$UserGroupTableReferences), - UserGroupData, - PrefetchHooks Function({bool userId, bool groupId}) - >; - -class $AppDatabaseManager { - final _$AppDatabase _db; - $AppDatabaseManager(this._db); - $$UserTableTableManager get user => $$UserTableTableManager(_db, _db.user); - $$GroupTableTableManager get group => - $$GroupTableTableManager(_db, _db.group); - $$UserGroupTableTableManager get userGroup => - $$UserGroupTableTableManager(_db, _db.userGroup); -} diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart new file mode 100644 index 0000000..6d4dedc --- /dev/null +++ b/lib/data/db/database.dart @@ -0,0 +1,31 @@ +import 'package:drift/drift.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:game_tracker/data/dao/group_dao.dart'; +import 'package:game_tracker/data/dao/player_dao.dart'; +import 'package:game_tracker/data/dao/player_group_dao.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; +import 'package:path_provider/path_provider.dart'; + +part 'database.g.dart'; + +@DriftDatabase( + tables: [PlayerTable, GroupTable, PlayerGroupTable], + daos: [GroupDao, PlayerDao, PlayerGroupDao], +) +class AppDatabase extends _$AppDatabase { + AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); + + @override + int get schemaVersion => 1; + + static QueryExecutor _openConnection() { + return driftDatabase( + name: 'gametracker_db', + native: const DriftNativeOptions( + databaseDirectory: getApplicationSupportDirectory, + ), + ); + } +} diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart new file mode 100644 index 0000000..150b8a3 --- /dev/null +++ b/lib/data/db/database.g.dart @@ -0,0 +1,1092 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $PlayerTableTable extends PlayerTable + with TableInfo<$PlayerTableTable, PlayerTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PlayerTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'player_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PlayerTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlayerTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $PlayerTableTable createAlias(String alias) { + return $PlayerTableTable(attachedDatabase, alias); + } +} + +class PlayerTableData extends DataClass implements Insertable { + final String id; + final String name; + const PlayerTableData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + PlayerTableCompanion toCompanion(bool nullToAbsent) { + return PlayerTableCompanion(id: Value(id), name: Value(name)); + } + + factory PlayerTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlayerTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + PlayerTableData copyWith({String? id, String? name}) => + PlayerTableData(id: id ?? this.id, name: name ?? this.name); + PlayerTableData copyWithCompanion(PlayerTableCompanion data) { + return PlayerTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('PlayerTableData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlayerTableData && + other.id == this.id && + other.name == this.name); +} + +class PlayerTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const PlayerTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + PlayerTableCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + PlayerTableCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return PlayerTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlayerTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $GroupTableTable extends GroupTable + with TableInfo<$GroupTableTable, GroupTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GroupTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GroupTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $GroupTableTable createAlias(String alias) { + return $GroupTableTable(attachedDatabase, alias); + } +} + +class GroupTableData extends DataClass implements Insertable { + final String id; + final String name; + const GroupTableData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + GroupTableCompanion toCompanion(bool nullToAbsent) { + return GroupTableCompanion(id: Value(id), name: Value(name)); + } + + factory GroupTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + GroupTableData copyWith({String? id, String? name}) => + GroupTableData(id: id ?? this.id, name: name ?? this.name); + GroupTableData copyWithCompanion(GroupTableCompanion data) { + return GroupTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('GroupTableData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupTableData && + other.id == this.id && + other.name == this.name); +} + +class GroupTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const GroupTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupTableCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupTableCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return GroupTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $PlayerGroupTableTable extends PlayerGroupTable + with TableInfo<$PlayerGroupTableTable, PlayerGroupTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PlayerGroupTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _groupIdMeta = const VerificationMeta( + 'groupId', + ); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, groupId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'player_group_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle( + _userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + ); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('group_id')) { + context.handle( + _groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + ); + } else if (isInserting) { + context.missing(_groupIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {userId, groupId}; + @override + PlayerGroupTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlayerGroupTableData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + )!, + ); + } + + @override + $PlayerGroupTableTable createAlias(String alias) { + return $PlayerGroupTableTable(attachedDatabase, alias); + } +} + +class PlayerGroupTableData extends DataClass + implements Insertable { + final String userId; + final String groupId; + const PlayerGroupTableData({required this.userId, required this.groupId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['group_id'] = Variable(groupId); + return map; + } + + PlayerGroupTableCompanion toCompanion(bool nullToAbsent) { + return PlayerGroupTableCompanion( + userId: Value(userId), + groupId: Value(groupId), + ); + } + + factory PlayerGroupTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlayerGroupTableData( + userId: serializer.fromJson(json['userId']), + groupId: serializer.fromJson(json['groupId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'groupId': serializer.toJson(groupId), + }; + } + + PlayerGroupTableData copyWith({String? userId, String? groupId}) => + PlayerGroupTableData( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + ); + PlayerGroupTableData copyWithCompanion(PlayerGroupTableCompanion data) { + return PlayerGroupTableData( + userId: data.userId.present ? data.userId.value : this.userId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, + ); + } + + @override + String toString() { + return (StringBuffer('PlayerGroupTableData(') + ..write('userId: $userId, ') + ..write('groupId: $groupId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, groupId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlayerGroupTableData && + other.userId == this.userId && + other.groupId == this.groupId); +} + +class PlayerGroupTableCompanion extends UpdateCompanion { + final Value userId; + final Value groupId; + final Value rowid; + const PlayerGroupTableCompanion({ + this.userId = const Value.absent(), + this.groupId = const Value.absent(), + this.rowid = const Value.absent(), + }); + PlayerGroupTableCompanion.insert({ + required String userId, + required String groupId, + this.rowid = const Value.absent(), + }) : userId = Value(userId), + groupId = Value(groupId); + static Insertable custom({ + Expression? userId, + Expression? groupId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (groupId != null) 'group_id': groupId, + if (rowid != null) 'rowid': rowid, + }); + } + + PlayerGroupTableCompanion copyWith({ + Value? userId, + Value? groupId, + Value? rowid, + }) { + return PlayerGroupTableCompanion( + userId: userId ?? this.userId, + groupId: groupId ?? this.groupId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlayerGroupTableCompanion(') + ..write('userId: $userId, ') + ..write('groupId: $groupId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $PlayerTableTable playerTable = $PlayerTableTable(this); + late final $GroupTableTable groupTable = $GroupTableTable(this); + late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( + this, + ); + late final GroupDao groupDao = GroupDao(this as AppDatabase); + late final PlayerDao playerDao = PlayerDao(this as AppDatabase); + late final PlayerGroupDao playerGroupDao = PlayerGroupDao( + this as AppDatabase, + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + playerTable, + groupTable, + playerGroupTable, + ]; +} + +typedef $$PlayerTableTableCreateCompanionBuilder = + PlayerTableCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$PlayerTableTableUpdateCompanionBuilder = + PlayerTableCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +class $$PlayerTableTableFilterComposer + extends Composer<_$AppDatabase, $PlayerTableTable> { + $$PlayerTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); +} + +class $$PlayerTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerTableTable> { + $$PlayerTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$PlayerTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerTableTable> { + $$PlayerTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); +} + +class $$PlayerTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $PlayerTableTable, + PlayerTableData, + $$PlayerTableTableFilterComposer, + $$PlayerTableTableOrderingComposer, + $$PlayerTableTableAnnotationComposer, + $$PlayerTableTableCreateCompanionBuilder, + $$PlayerTableTableUpdateCompanionBuilder, + ( + PlayerTableData, + BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData>, + ), + PlayerTableData, + PrefetchHooks Function() + > { + $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$PlayerTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$PlayerTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$PlayerTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => PlayerTableCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => + PlayerTableCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$PlayerTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $PlayerTableTable, + PlayerTableData, + $$PlayerTableTableFilterComposer, + $$PlayerTableTableOrderingComposer, + $$PlayerTableTableAnnotationComposer, + $$PlayerTableTableCreateCompanionBuilder, + $$PlayerTableTableUpdateCompanionBuilder, + ( + PlayerTableData, + BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData>, + ), + PlayerTableData, + PrefetchHooks Function() + >; +typedef $$GroupTableTableCreateCompanionBuilder = + GroupTableCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$GroupTableTableUpdateCompanionBuilder = + GroupTableCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +class $$GroupTableTableFilterComposer + extends Composer<_$AppDatabase, $GroupTableTable> { + $$GroupTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); +} + +class $$GroupTableTableOrderingComposer + extends Composer<_$AppDatabase, $GroupTableTable> { + $$GroupTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$GroupTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupTableTable> { + $$GroupTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); +} + +class $$GroupTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GroupTableTable, + GroupTableData, + $$GroupTableTableFilterComposer, + $$GroupTableTableOrderingComposer, + $$GroupTableTableAnnotationComposer, + $$GroupTableTableCreateCompanionBuilder, + $$GroupTableTableUpdateCompanionBuilder, + ( + GroupTableData, + BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData>, + ), + GroupTableData, + PrefetchHooks Function() + > { + $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GroupTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GroupTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GroupTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => GroupTableCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => + GroupTableCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$GroupTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GroupTableTable, + GroupTableData, + $$GroupTableTableFilterComposer, + $$GroupTableTableOrderingComposer, + $$GroupTableTableAnnotationComposer, + $$GroupTableTableCreateCompanionBuilder, + $$GroupTableTableUpdateCompanionBuilder, + ( + GroupTableData, + BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData>, + ), + GroupTableData, + PrefetchHooks Function() + >; +typedef $$PlayerGroupTableTableCreateCompanionBuilder = + PlayerGroupTableCompanion Function({ + required String userId, + required String groupId, + Value rowid, + }); +typedef $$PlayerGroupTableTableUpdateCompanionBuilder = + PlayerGroupTableCompanion Function({ + Value userId, + Value groupId, + Value rowid, + }); + +class $$PlayerGroupTableTableFilterComposer + extends Composer<_$AppDatabase, $PlayerGroupTableTable> { + $$PlayerGroupTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get userId => $composableBuilder( + column: $table.userId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get groupId => $composableBuilder( + column: $table.groupId, + builder: (column) => ColumnFilters(column), + ); +} + +class $$PlayerGroupTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerGroupTableTable> { + $$PlayerGroupTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get userId => $composableBuilder( + column: $table.userId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get groupId => $composableBuilder( + column: $table.groupId, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$PlayerGroupTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerGroupTableTable> { + $$PlayerGroupTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get userId => + $composableBuilder(column: $table.userId, builder: (column) => column); + + GeneratedColumn get groupId => + $composableBuilder(column: $table.groupId, builder: (column) => column); +} + +class $$PlayerGroupTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $PlayerGroupTableTable, + PlayerGroupTableData, + $$PlayerGroupTableTableFilterComposer, + $$PlayerGroupTableTableOrderingComposer, + $$PlayerGroupTableTableAnnotationComposer, + $$PlayerGroupTableTableCreateCompanionBuilder, + $$PlayerGroupTableTableUpdateCompanionBuilder, + ( + PlayerGroupTableData, + BaseReferences< + _$AppDatabase, + $PlayerGroupTableTable, + PlayerGroupTableData + >, + ), + PlayerGroupTableData, + PrefetchHooks Function() + > { + $$PlayerGroupTableTableTableManager( + _$AppDatabase db, + $PlayerGroupTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$PlayerGroupTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$PlayerGroupTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$PlayerGroupTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value groupId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PlayerGroupTableCompanion( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String userId, + required String groupId, + Value rowid = const Value.absent(), + }) => PlayerGroupTableCompanion.insert( + userId: userId, + groupId: groupId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$PlayerGroupTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $PlayerGroupTableTable, + PlayerGroupTableData, + $$PlayerGroupTableTableFilterComposer, + $$PlayerGroupTableTableOrderingComposer, + $$PlayerGroupTableTableAnnotationComposer, + $$PlayerGroupTableTableCreateCompanionBuilder, + $$PlayerGroupTableTableUpdateCompanionBuilder, + ( + PlayerGroupTableData, + BaseReferences< + _$AppDatabase, + $PlayerGroupTableTable, + PlayerGroupTableData + >, + ), + PlayerGroupTableData, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db, _db.playerTable); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db, _db.groupTable); + $$PlayerGroupTableTableTableManager get playerGroupTable => + $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); +} diff --git a/lib/data/db/tables/group_table.dart b/lib/data/db/tables/group_table.dart new file mode 100644 index 0000000..dc9335d --- /dev/null +++ b/lib/data/db/tables/group_table.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; + +class GroupTable extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/db/tables/player_group_table.dart b/lib/data/db/tables/player_group_table.dart new file mode 100644 index 0000000..a045789 --- /dev/null +++ b/lib/data/db/tables/player_group_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/methods/db/tables/group_table.dart'; +import 'package:game_tracker/data/methods/db/tables/user_table.dart'; + +class PlayerGroupTable extends Table { + TextColumn get userId => text().references(UserTable, #id)(); + TextColumn get groupId => text().references(GroupTable, #id)(); + + @override + Set> get primaryKey => {userId, groupId}; +} diff --git a/lib/data/db/tables/player_table.dart b/lib/data/db/tables/player_table.dart new file mode 100644 index 0000000..3d97459 --- /dev/null +++ b/lib/data/db/tables/player_table.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; + +class PlayerTable extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart new file mode 100644 index 0000000..7457b54 --- /dev/null +++ b/lib/data/dto/game.dart @@ -0,0 +1,6 @@ +class Game { + final String id; + final String name; + + Game({required this.id, required this.name}); +} diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart new file mode 100644 index 0000000..316b712 --- /dev/null +++ b/lib/data/dto/group.dart @@ -0,0 +1,9 @@ +import 'package:game_tracker/data/methods/dto/player.dart'; + +class Group { + final String id; + final String name; + final List members; + + Group({required this.id, required this.name, required this.members}); +} diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart new file mode 100644 index 0000000..331645a --- /dev/null +++ b/lib/data/dto/player.dart @@ -0,0 +1,6 @@ +class Player { + final String id; + final String username; + + Player({required this.id, required this.username}); +} diff --git a/lib/data/methods/group.dart b/lib/data/methods/group.dart deleted file mode 100644 index a5346ba..0000000 --- a/lib/data/methods/group.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:game_tracker/data/database.dart'; -import 'package:drift/drift.dart'; - -extension GroupMethods on AppDatabase { - Future> getAllGroups() async { - return await select(group).get(); - } - - Future getGroupById(String id) async { - return await (select(group)..where((g) => g.id.equals(id))).getSingle(); - } - - Future addGroup(String id, String name) async { - await into(group).insert( - GroupCompanion.insert(id: id, name: name), - ); - } - - Future deleteGroup(String id) async { - await (delete(group)..where((g) => g.id.equals(id))).go(); - } - - Future updateGroupname(String id, String newName) async { - await (update(group)..where((g) => g.id.equals(id))).write( - GroupCompanion(name: Value(newName)), - ); - } -} \ No newline at end of file diff --git a/lib/data/methods/user_group.dart b/lib/data/methods/user_group.dart deleted file mode 100644 index 206c1f0..0000000 --- a/lib/data/methods/user_group.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:game_tracker/data/database.dart'; -import 'package:drift/drift.dart'; - -extension UserGroupMethods on AppDatabase { - Future> getAllUsersAndGroups() async { - return await select(userGroup).get(); - } - - Future> getUsersGroups(String userId) async { - return await (select(userGroup)..where((uG) => uG.userId.equals(userId))).get(); - } - - Future> getGroupsUsers(String groupId) async { - return await (select(userGroup)..where((uG) => uG.groupId.equals(groupId))).get(); - } - - Future addUserToGroup(String userId, String groupId) async { - await into(userGroup).insert( - UserGroupCompanion.insert(userId: userId, groupId: groupId), - ); - } - - Future removeUserFromGroup(String userId, String groupId) async { - await (delete(userGroup)..where((uG) => uG.userId.equals(userId) & uG.groupId.equals(groupId))).go(); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 261ff1b..dd19f34 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/database.dart'; +import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bardb/database.dart'; import 'package:provider/provider.dart'; void main() { @@ -10,7 +10,7 @@ void main() { create: (context) => AppDatabase(), child: const MyApp(), dispose: (context, db) => db.close(), - ), + ), ); } -- 2.49.1 From 0895f6df0a7be52e844a9169f53776ddd2fa8a4c Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:41:20 +0100 Subject: [PATCH 047/563] add text and change icons --- .../main_menu/custom_navigation_bar.dart | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index a78a9f9..95dd408 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -63,40 +63,45 @@ class _CustomNavigationBarState extends State mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - IconButton( - icon: Icon( - Icons.home, - color: currentIndex == 0 ? Colors.white : Colors.black, - ), - onPressed: () => onTabTapped(0), - ), - IconButton( - icon: Icon( - Icons.history, - color: currentIndex == 1 ? Colors.white : Colors.black, - ), - onPressed: () => onTabTapped(1), - ), - IconButton( - icon: Icon( - Icons.groups, - color: currentIndex == 2 ? Colors.white : Colors.black, - ), - onPressed: () => onTabTapped(2), - ), - IconButton( - icon: Icon( - Icons.bar_chart, - color: currentIndex == 3 ? Colors.white : Colors.black, - ), - onPressed: () => onTabTapped(3), - ), + const SizedBox(width: 0), + _buildNavItem(Icons.home, 'Home', 0), + _buildNavItem(Icons.history, 'History', 1), + _buildNavItem(Icons.groups, 'Groups', 2), + _buildNavItem(Icons.bar_chart, 'Stats', 3), + const SizedBox(width: 0), ], ), ), ); } + Widget _buildNavItem(IconData icon, String label, int index) { + final isSelected = currentIndex == index; + + return GestureDetector( + onTap: () => onTabTapped(index), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + color: isSelected ? Colors.white : Colors.black, + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : Colors.black, + fontSize: 12, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ), + ); + } + void onTabTapped(int index) { setState(() { currentIndex = index; -- 2.49.1 From 5baeda8b32fa4b0763a972e3123554f41c84d23e Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:41:56 +0100 Subject: [PATCH 048/563] change history text to games --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 95dd408..bd85c63 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -65,7 +65,7 @@ class _CustomNavigationBarState extends State children: [ const SizedBox(width: 0), _buildNavItem(Icons.home, 'Home', 0), - _buildNavItem(Icons.history, 'History', 1), + _buildNavItem(Icons.history, 'Games', 1), _buildNavItem(Icons.groups, 'Groups', 2), _buildNavItem(Icons.bar_chart, 'Stats', 3), const SizedBox(width: 0), -- 2.49.1 From d5315a8f00fbf39737161733aa57200dc62d7bd7 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 15:53:28 +0100 Subject: [PATCH 049/563] change spacing of icons --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index bd85c63..184b9bd 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -61,14 +61,12 @@ class _CustomNavigationBarState extends State shape: null, child: Row( mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - const SizedBox(width: 0), _buildNavItem(Icons.home, 'Home', 0), _buildNavItem(Icons.history, 'Games', 1), _buildNavItem(Icons.groups, 'Groups', 2), _buildNavItem(Icons.bar_chart, 'Stats', 3), - const SizedBox(width: 0), ], ), ), -- 2.49.1 From 5bbb5c18882bcacfba0661ee4ce5577152c6507d Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 16:14:52 +0100 Subject: [PATCH 050/563] add floating and rounded navbar --- .../main_menu/custom_navigation_bar.dart | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 184b9bd..d1732a8 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -52,22 +52,28 @@ class _CustomNavigationBarState extends State ), backgroundColor: CustomTheme.backgroundColor, body: tabs[currentIndex], - extendBody: true, - bottomNavigationBar: BottomAppBar( - padding: const EdgeInsets.symmetric(horizontal: 10), - elevation: 10, - height: 60, - color: CustomTheme.primaryColor, - shape: null, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem(Icons.home, 'Home', 0), - _buildNavItem(Icons.history, 'Games', 1), - _buildNavItem(Icons.groups, 'Groups', 2), - _buildNavItem(Icons.bar_chart, 'Stats', 3), - ], + extendBody: true, // Enables floating effect + bottomNavigationBar: Padding( + padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 8.0), + child: Material( + elevation: 10, + borderRadius: BorderRadius.circular(24), + color: CustomTheme.primaryColor, + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildNavItem(Icons.home, 'Home', 0), + _buildNavItem(Icons.history, 'Games', 1), + _buildNavItem(Icons.groups, 'Groups', 2), + _buildNavItem(Icons.bar_chart, 'Stats', 3), + ], + ), + ), + ), ), ), ); -- 2.49.1 From b03979b0c6229b7f87301b90c5a62568ce0ff7e3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 8 Nov 2025 17:09:02 +0100 Subject: [PATCH 051/563] Completed daos, added basic GameTable & GameDao --- lib/data/dao/game_dao.dart | 25 + lib/data/dao/game_dao.g.dart | 8 + lib/data/dao/group_dao.dart | 35 +- lib/data/dao/player_dao.dart | 44 +- lib/data/dao/player_group_dao.dart | 50 +- lib/data/dao/player_group_dao.g.dart | 2 + lib/data/db/database.dart | 6 +- lib/data/db/database.g.dart | 905 +++++++++++++++++++-- lib/data/db/tables/game_table.dart | 9 + lib/data/db/tables/player_group_table.dart | 6 +- lib/data/dto/group.dart | 2 +- lib/data/dto/player.dart | 4 +- lib/main.dart | 4 +- 13 files changed, 984 insertions(+), 116 deletions(-) create mode 100644 lib/data/dao/game_dao.dart create mode 100644 lib/data/dao/game_dao.g.dart create mode 100644 lib/data/db/tables/game_table.dart diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart new file mode 100644 index 0000000..e59fae4 --- /dev/null +++ b/lib/data/dao/game_dao.dart @@ -0,0 +1,25 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/game_table.dart'; +import 'package:game_tracker/data/dto/game.dart'; + +part 'game_dao.g.dart'; + +@DriftAccessor(tables: [GameTable]) +class GameDao extends DatabaseAccessor with _$GameDaoMixin { + GameDao(super.db); + + /// Retrieves all games from the database. + Future> getAllGames() async { + final query = select(gameTable); + final result = await query.get(); + return result.map((row) => Game(id: row.id, name: row.name)).toList(); + } + + /// Retrieves a [Game] by its [id]. + Future getGameById(String id) async { + final query = select(gameTable)..where((g) => g.id.equals(id)); + final result = await query.getSingle(); + return Game(id: result.id, name: result.name); + } +} diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart new file mode 100644 index 0000000..b5a29fe --- /dev/null +++ b/lib/data/dao/game_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'game_dao.dart'; + +// ignore_for_file: type=lint +mixin _$GameDaoMixin on DatabaseAccessor { + $GameTableTable get gameTable => attachedDatabase.gameTable; +} diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index d2f529c..c1932d3 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; part 'group_dao.g.dart'; @@ -9,6 +10,7 @@ part 'group_dao.g.dart'; class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { GroupDao(super.db); + /// Retrieves all groups from the database. Future> getAllGroups() async { final query = select(groupTable); final result = await query.get(); @@ -17,26 +19,41 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .toList(); } + /// Retrieves a [Group] by its [id], including its members. Future getGroupById(String id) async { final query = select(groupTable)..where((g) => g.id.equals(id)); final result = await query.getSingle(); - // todo: Get group members + List members = []; - return Group(id: result.id, name: result.name, members: []); + members = await db.playerGroupDao.getPlayersOfGroupById(id); + + return Group(id: result.id, name: result.name, members: members); } + /// Adds a new group with the given [id] and [name] to the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future addGroup(String id, String name) async { - await into(group).insert(GroupCompanion.insert(id: id, name: name)); + await into( + groupTable, + ).insert(GroupTableCompanion.insert(id: id, name: name)); } - Future deleteGroup(String id) async { - await (delete(group)..where((g) => g.id.equals(id))).go(); + /// Deletes the group with the given [id] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteGroup(String id) async { + final query = (delete(groupTable)..where((g) => g.id.equals(id))); + final rowsAffected = await query.go(); + return rowsAffected > 0; } - Future updateGroupname(String id, String newName) async { - await (update(group)..where((g) => g.id.equals(id))).write( - GroupCompanion(name: Value(newName)), - ); + /// Updates the name of the group with the given [id] to [newName]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateGroupname(String id, String newName) async { + final rowsAffected = + await (update(groupTable)..where((g) => g.id.equals(id))).write( + GroupTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; } } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index d7a7596..2d3c0ab 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -9,25 +9,47 @@ part 'player_dao.g.dart'; class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { PlayerDao(super.db); - Future> getAllUsers() async { - return await select(UserTable).get(); + /// Retrieves all players from the database. + Future> getAllPlayers() async { + final query = select(playerTable); + final result = await query.get(); + return result.map((row) => Player(id: row.id, name: row.name)).toList(); } - Future getUserById(String id) async { - return await (select(user)..where((u) => u.id.equals(id))).getSingle(); + /// Retrieves a [Player] by their [id]. + Future getPlayerById(String id) async { + final query = select(playerTable)..where((p) => p.id.equals(id)); + final result = await query.getSingle(); + return Player(id: result.id, name: result.name); } - Future addUser(String id, String name) async { - await into(user).insert(UserCompanion.insert(id: id, name: name)); + /// Adds a new [player] to the database. + Future addPlayer(Player player) async { + await into( + playerTable, + ).insert(PlayerTableCompanion.insert(id: player.id, name: player.name)); } - Future deleteUser(String id) async { - await (delete(user)..where((u) => u.id.equals(id))).go(); + /// Deletes the player with the given [id] from the database. + /// Returns `true` if the player was deleted, `false` if the player did not exist. + Future deletePlayer(String id) async { + final query = delete(playerTable)..where((p) => p.id.equals(id)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } - Future updateUsername(String id, String newName) async { - await (update(user)..where((u) => u.id.equals(id))).write( - UserCompanion(name: Value(newName)), + /// Checks if a player with the given [id] exists in the database. + /// Returns `true` if the player exists, `false` otherwise. + Future playerExists(String id) async { + final query = select(playerTable)..where((p) => p.id.equals(id)); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Updates the name of the player with the given [id] to [newName]. + Future updatePlayername(String id, String newName) async { + await (update(playerTable)..where((p) => p.id.equals(id))).write( + PlayerTableCompanion(name: Value(newName)), ); } } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index c4daf21..b68ed09 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -1,6 +1,7 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/dto/player.dart'; part 'player_group_dao.g.dart'; @@ -9,32 +10,35 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); - Future> getAllUsersAndGroups() async { - return await select(userGroup).get(); + /// Retrieves all players belonging to a specific group by [groupId]. + Future> getPlayersOfGroupById(String groupId) async { + final query = select(playerGroupTable) + ..where((pG) => pG.groupId.equals(groupId)); + final result = await query.get(); + + List groupMembers = []; + + for (var entry in result) { + final player = await db.playerDao.getPlayerById(entry.userId); + groupMembers.add(player); + } + + return groupMembers; } - Future> getUsersGroups(String userId) async { - return await (select( - userGroup, - )..where((uG) => uG.userId.equals(userId))).get(); + /// Removes a player from a group based on [userId] and [groupId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removePlayerFromGroup(String userId, String groupId) async { + final query = delete(playerGroupTable) + ..where((p) => p.userId.equals(userId) & p.groupId.equals(groupId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } - Future> getGroupsUsers(String groupId) async { - return await (select( - userGroup, - )..where((uG) => uG.groupId.equals(groupId))).get(); - } - - Future addUserToGroup(String userId, String groupId) async { - await into( - userGroup, - ).insert(UserGroupCompanion.insert(userId: userId, groupId: groupId)); - } - - Future removeUserFromGroup(String userId, String groupId) async { - await (delete( - userGroup, - )..where((uG) => uG.userId.equals(userId) & uG.groupId.equals(groupId))) - .go(); + /// Adds a player to a group with the given [userId] and [groupId]. + Future addPlayerToGroup(String userId, String groupId) async { + await into(playerGroupTable).insert( + PlayerGroupTableCompanion.insert(userId: userId, groupId: groupId), + ); } } diff --git a/lib/data/dao/player_group_dao.g.dart b/lib/data/dao/player_group_dao.g.dart index d512bb4..d54f979 100644 --- a/lib/data/dao/player_group_dao.g.dart +++ b/lib/data/dao/player_group_dao.g.dart @@ -4,6 +4,8 @@ part of 'player_group_dao.dart'; // ignore_for_file: type=lint mixin _$PlayerGroupDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $GroupTableTable get groupTable => attachedDatabase.groupTable; $PlayerGroupTableTable get playerGroupTable => attachedDatabase.playerGroupTable; } diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 6d4dedc..e35513e 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -1,8 +1,10 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; +import 'package:game_tracker/data/dao/game_dao.dart'; import 'package:game_tracker/data/dao/group_dao.dart'; import 'package:game_tracker/data/dao/player_dao.dart'; import 'package:game_tracker/data/dao/player_group_dao.dart'; +import 'package:game_tracker/data/db/tables/game_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; @@ -11,8 +13,8 @@ import 'package:path_provider/path_provider.dart'; part 'database.g.dart'; @DriftDatabase( - tables: [PlayerTable, GroupTable, PlayerGroupTable], - daos: [GroupDao, PlayerDao, PlayerGroupDao], + tables: [PlayerTable, GroupTable, PlayerGroupTable, GameTable], + daos: [GroupDao, PlayerDao, PlayerGroupDao, GameDao], ) class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 150b8a3..4075912 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -427,6 +427,9 @@ class $PlayerGroupTableTable extends PlayerGroupTable false, type: DriftSqlType.string, requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES player_table (id)', + ), ); static const VerificationMeta _groupIdMeta = const VerificationMeta( 'groupId', @@ -438,6 +441,9 @@ class $PlayerGroupTableTable extends PlayerGroupTable false, type: DriftSqlType.string, requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES group_table (id)', + ), ); @override List get $columns => [userId, groupId]; @@ -630,6 +636,211 @@ class PlayerGroupTableCompanion extends UpdateCompanion { } } +class $GameTableTable extends GameTable + with TableInfo<$GameTableTable, GameTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GameTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'game_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GameTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GameTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + ); + } + + @override + $GameTableTable createAlias(String alias) { + return $GameTableTable(attachedDatabase, alias); + } +} + +class GameTableData extends DataClass implements Insertable { + final String id; + final String name; + const GameTableData({required this.id, required this.name}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + return map; + } + + GameTableCompanion toCompanion(bool nullToAbsent) { + return GameTableCompanion(id: Value(id), name: Value(name)); + } + + factory GameTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GameTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + }; + } + + GameTableData copyWith({String? id, String? name}) => + GameTableData(id: id ?? this.id, name: name ?? this.name); + GameTableData copyWithCompanion(GameTableCompanion data) { + return GameTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ); + } + + @override + String toString() { + return (StringBuffer('GameTableData(') + ..write('id: $id, ') + ..write('name: $name') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GameTableData && + other.id == this.id && + other.name == this.name); +} + +class GameTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value rowid; + const GameTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.rowid = const Value.absent(), + }); + GameTableCompanion.insert({ + required String id, + required String name, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (rowid != null) 'rowid': rowid, + }); + } + + GameTableCompanion copyWith({ + Value? id, + Value? name, + Value? rowid, + }) { + return GameTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GameTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); @@ -638,11 +849,13 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); + late final $GameTableTable gameTable = $GameTableTable(this); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final PlayerGroupDao playerGroupDao = PlayerGroupDao( this as AppDatabase, ); + late final GameDao gameDao = GameDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -651,6 +864,7 @@ abstract class _$AppDatabase extends GeneratedDatabase { playerTable, groupTable, playerGroupTable, + gameTable, ]; } @@ -667,6 +881,34 @@ typedef $$PlayerTableTableUpdateCompanionBuilder = Value rowid, }); +final class $$PlayerTableTableReferences + extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { + $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$PlayerGroupTableTable, List> + _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerGroupTable, + aliasName: $_aliasNameGenerator( + db.playerTable.id, + db.playerGroupTable.userId, + ), + ); + + $$PlayerGroupTableTableProcessedTableManager get playerGroupTableRefs { + final manager = $$PlayerGroupTableTableTableManager( + $_db, + $_db.playerGroupTable, + ).filter((f) => f.userId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerGroupTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + class $$PlayerTableTableFilterComposer extends Composer<_$AppDatabase, $PlayerTableTable> { $$PlayerTableTableFilterComposer({ @@ -685,6 +927,31 @@ class $$PlayerTableTableFilterComposer column: $table.name, builder: (column) => ColumnFilters(column), ); + + Expression playerGroupTableRefs( + Expression Function($$PlayerGroupTableTableFilterComposer f) f, + ) { + final $$PlayerGroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGroupTable, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGroupTableTableFilterComposer( + $db: $db, + $table: $db.playerGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableOrderingComposer @@ -721,6 +988,31 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + + Expression playerGroupTableRefs( + Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, + ) { + final $$PlayerGroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGroupTable, + getReferencedColumn: (t) => t.userId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGroupTableTableAnnotationComposer( + $db: $db, + $table: $db.playerGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableTableManager @@ -734,12 +1026,9 @@ class $$PlayerTableTableTableManager $$PlayerTableTableAnnotationComposer, $$PlayerTableTableCreateCompanionBuilder, $$PlayerTableTableUpdateCompanionBuilder, - ( - PlayerTableData, - BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData>, - ), + (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, - PrefetchHooks Function() + PrefetchHooks Function({bool playerGroupTableRefs}) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) : super( @@ -766,9 +1055,45 @@ class $$PlayerTableTableTableManager }) => PlayerTableCompanion.insert(id: id, name: name, rowid: rowid), withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .map( + (e) => ( + e.readTable(table), + $$PlayerTableTableReferences(db, table, e), + ), + ) .toList(), - prefetchHooksCallback: null, + prefetchHooksCallback: ({playerGroupTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerGroupTableRefs) db.playerGroupTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (playerGroupTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + PlayerGroupTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._playerGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).playerGroupTableRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.userId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, ), ); } @@ -783,12 +1108,9 @@ typedef $$PlayerTableTableProcessedTableManager = $$PlayerTableTableAnnotationComposer, $$PlayerTableTableCreateCompanionBuilder, $$PlayerTableTableUpdateCompanionBuilder, - ( - PlayerTableData, - BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData>, - ), + (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, - PrefetchHooks Function() + PrefetchHooks Function({bool playerGroupTableRefs}) >; typedef $$GroupTableTableCreateCompanionBuilder = GroupTableCompanion Function({ @@ -803,6 +1125,34 @@ typedef $$GroupTableTableUpdateCompanionBuilder = Value rowid, }); +final class $$GroupTableTableReferences + extends BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData> { + $$GroupTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$PlayerGroupTableTable, List> + _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerGroupTable, + aliasName: $_aliasNameGenerator( + db.groupTable.id, + db.playerGroupTable.groupId, + ), + ); + + $$PlayerGroupTableTableProcessedTableManager get playerGroupTableRefs { + final manager = $$PlayerGroupTableTableTableManager( + $_db, + $_db.playerGroupTable, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerGroupTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + class $$GroupTableTableFilterComposer extends Composer<_$AppDatabase, $GroupTableTable> { $$GroupTableTableFilterComposer({ @@ -821,6 +1171,31 @@ class $$GroupTableTableFilterComposer column: $table.name, builder: (column) => ColumnFilters(column), ); + + Expression playerGroupTableRefs( + Expression Function($$PlayerGroupTableTableFilterComposer f) f, + ) { + final $$PlayerGroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGroupTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGroupTableTableFilterComposer( + $db: $db, + $table: $db.playerGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableOrderingComposer @@ -857,6 +1232,31 @@ class $$GroupTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + + Expression playerGroupTableRefs( + Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, + ) { + final $$PlayerGroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGroupTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGroupTableTableAnnotationComposer( + $db: $db, + $table: $db.playerGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableTableManager @@ -870,12 +1270,9 @@ class $$GroupTableTableTableManager $$GroupTableTableAnnotationComposer, $$GroupTableTableCreateCompanionBuilder, $$GroupTableTableUpdateCompanionBuilder, - ( - GroupTableData, - BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData>, - ), + (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function() + PrefetchHooks Function({bool playerGroupTableRefs}) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) : super( @@ -902,9 +1299,45 @@ class $$GroupTableTableTableManager }) => GroupTableCompanion.insert(id: id, name: name, rowid: rowid), withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .map( + (e) => ( + e.readTable(table), + $$GroupTableTableReferences(db, table, e), + ), + ) .toList(), - prefetchHooksCallback: null, + prefetchHooksCallback: ({playerGroupTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerGroupTableRefs) db.playerGroupTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (playerGroupTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + PlayerGroupTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._playerGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).playerGroupTableRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.groupId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, ), ); } @@ -919,12 +1352,9 @@ typedef $$GroupTableTableProcessedTableManager = $$GroupTableTableAnnotationComposer, $$GroupTableTableCreateCompanionBuilder, $$GroupTableTableUpdateCompanionBuilder, - ( - GroupTableData, - BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData>, - ), + (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function() + PrefetchHooks Function({bool playerGroupTableRefs}) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = PlayerGroupTableCompanion Function({ @@ -939,6 +1369,58 @@ typedef $$PlayerGroupTableTableUpdateCompanionBuilder = Value rowid, }); +final class $$PlayerGroupTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $PlayerGroupTableTable, + PlayerGroupTableData + > { + $$PlayerGroupTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $PlayerTableTable _userIdTable(_$AppDatabase db) => + db.playerTable.createAlias( + $_aliasNameGenerator(db.playerGroupTable.userId, db.playerTable.id), + ); + + $$PlayerTableTableProcessedTableManager get userId { + final $_column = $_itemColumn('user_id')!; + + final manager = $$PlayerTableTableTableManager( + $_db, + $_db.playerTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTableTable _groupIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.playerGroupTable.groupId, db.groupTable.id), + ); + + $$GroupTableTableProcessedTableManager get groupId { + final $_column = $_itemColumn('group_id')!; + + final manager = $$GroupTableTableTableManager( + $_db, + $_db.groupTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + class $$PlayerGroupTableTableFilterComposer extends Composer<_$AppDatabase, $PlayerGroupTableTable> { $$PlayerGroupTableTableFilterComposer({ @@ -948,15 +1430,51 @@ class $$PlayerGroupTableTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, - builder: (column) => ColumnFilters(column), - ); + $$PlayerTableTableFilterComposer get userId { + final $$PlayerTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableFilterComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } - ColumnFilters get groupId => $composableBuilder( - column: $table.groupId, - builder: (column) => ColumnFilters(column), - ); + $$GroupTableTableFilterComposer get groupId { + final $$GroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableFilterComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$PlayerGroupTableTableOrderingComposer @@ -968,15 +1486,51 @@ class $$PlayerGroupTableTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, - builder: (column) => ColumnOrderings(column), - ); + $$PlayerTableTableOrderingComposer get userId { + final $$PlayerTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableOrderingComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } - ColumnOrderings get groupId => $composableBuilder( - column: $table.groupId, - builder: (column) => ColumnOrderings(column), - ); + $$GroupTableTableOrderingComposer get groupId { + final $$GroupTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableOrderingComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$PlayerGroupTableTableAnnotationComposer @@ -988,11 +1542,51 @@ class $$PlayerGroupTableTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + $$PlayerTableTableAnnotationComposer get userId { + final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableAnnotationComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } - GeneratedColumn get groupId => - $composableBuilder(column: $table.groupId, builder: (column) => column); + $$GroupTableTableAnnotationComposer get groupId { + final $$GroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableAnnotationComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$PlayerGroupTableTableTableManager @@ -1006,16 +1600,9 @@ class $$PlayerGroupTableTableTableManager $$PlayerGroupTableTableAnnotationComposer, $$PlayerGroupTableTableCreateCompanionBuilder, $$PlayerGroupTableTableUpdateCompanionBuilder, - ( - PlayerGroupTableData, - BaseReferences< - _$AppDatabase, - $PlayerGroupTableTable, - PlayerGroupTableData - >, - ), + (PlayerGroupTableData, $$PlayerGroupTableTableReferences), PlayerGroupTableData, - PrefetchHooks Function() + PrefetchHooks Function({bool userId, bool groupId}) > { $$PlayerGroupTableTableTableManager( _$AppDatabase db, @@ -1051,9 +1638,71 @@ class $$PlayerGroupTableTableTableManager rowid: rowid, ), withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .map( + (e) => ( + e.readTable(table), + $$PlayerGroupTableTableReferences(db, table, e), + ), + ) .toList(), - prefetchHooksCallback: null, + prefetchHooksCallback: ({userId = false, groupId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (userId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: + $$PlayerGroupTableTableReferences + ._userIdTable(db), + referencedColumn: + $$PlayerGroupTableTableReferences + ._userIdTable(db) + .id, + ) + as T; + } + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: + $$PlayerGroupTableTableReferences + ._groupIdTable(db), + referencedColumn: + $$PlayerGroupTableTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, ), ); } @@ -1068,15 +1717,143 @@ typedef $$PlayerGroupTableTableProcessedTableManager = $$PlayerGroupTableTableAnnotationComposer, $$PlayerGroupTableTableCreateCompanionBuilder, $$PlayerGroupTableTableUpdateCompanionBuilder, - ( - PlayerGroupTableData, - BaseReferences< - _$AppDatabase, - $PlayerGroupTableTable, - PlayerGroupTableData - >, - ), + (PlayerGroupTableData, $$PlayerGroupTableTableReferences), PlayerGroupTableData, + PrefetchHooks Function({bool userId, bool groupId}) + >; +typedef $$GameTableTableCreateCompanionBuilder = + GameTableCompanion Function({ + required String id, + required String name, + Value rowid, + }); +typedef $$GameTableTableUpdateCompanionBuilder = + GameTableCompanion Function({ + Value id, + Value name, + Value rowid, + }); + +class $$GameTableTableFilterComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); +} + +class $$GameTableTableOrderingComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$GameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); +} + +class $$GameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + ( + GameTableData, + BaseReferences<_$AppDatabase, $GameTableTable, GameTableData>, + ), + GameTableData, + PrefetchHooks Function() + > { + $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GameTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rowid = const Value.absent(), + }) => GameTableCompanion(id: id, name: name, rowid: rowid), + createCompanionCallback: + ({ + required String id, + required String name, + Value rowid = const Value.absent(), + }) => GameTableCompanion.insert(id: id, name: name, rowid: rowid), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$GameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + ( + GameTableData, + BaseReferences<_$AppDatabase, $GameTableTable, GameTableData>, + ), + GameTableData, PrefetchHooks Function() >; @@ -1089,4 +1866,6 @@ class $AppDatabaseManager { $$GroupTableTableTableManager(_db, _db.groupTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db, _db.gameTable); } diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart new file mode 100644 index 0000000..45bfb20 --- /dev/null +++ b/lib/data/db/tables/game_table.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; + +class GameTable extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/db/tables/player_group_table.dart b/lib/data/db/tables/player_group_table.dart index a045789..096a981 100644 --- a/lib/data/db/tables/player_group_table.dart +++ b/lib/data/db/tables/player_group_table.dart @@ -1,9 +1,9 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/methods/db/tables/group_table.dart'; -import 'package:game_tracker/data/methods/db/tables/user_table.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGroupTable extends Table { - TextColumn get userId => text().references(UserTable, #id)(); + TextColumn get userId => text().references(PlayerTable, #id)(); TextColumn get groupId => text().references(GroupTable, #id)(); @override diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 316b712..6f12fe9 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -1,4 +1,4 @@ -import 'package:game_tracker/data/methods/dto/player.dart'; +import 'package:game_tracker/data/dto/player.dart'; class Group { final String id; diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 331645a..d5e84e8 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -1,6 +1,6 @@ class Player { final String id; - final String username; + final String name; - Player({required this.id, required this.username}); + Player({required this.id, required this.name}); } diff --git a/lib/main.dart b/lib/main.dart index dd19f34..22f94c7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/database.dart'; -import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bardb/database.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; void main() { -- 2.49.1 From 17d304eb5dc8a959215f4f66cfda7ce1423ef53d Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:19:00 +0100 Subject: [PATCH 052/563] add bigger hitboxes --- .../main_menu/custom_navigation_bar.dart | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index d1732a8..680a4dd 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -80,31 +80,37 @@ class _CustomNavigationBarState extends State } Widget _buildNavItem(IconData icon, String label, int index) { - final isSelected = currentIndex == index; + final isSelected = currentIndex == index; - return GestureDetector( + return Expanded( // makes each nav item occupy equal width = large horizontal hitbox + child: GestureDetector( onTap: () => onTabTapped(index), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon, - color: isSelected ? Colors.white : Colors.black, - ), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( + behavior: HitTestBehavior.opaque, // ensures the entire area is tappable + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), // adds comfortable tap height + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, color: isSelected ? Colors.white : Colors.black, - fontSize: 12, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), - ), - ], + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : Colors.black, + fontSize: 12, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ), ), - ); - } + ), + ); +} void onTabTapped(int index) { setState(() { -- 2.49.1 From 1698f61c2b0c583cc7a6d670f67afa3c007f103a Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:45:04 +0100 Subject: [PATCH 053/563] remove useless comments --- .../views/main_menu/custom_navigation_bar.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 680a4dd..bf7d90b 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -52,7 +52,7 @@ class _CustomNavigationBarState extends State ), backgroundColor: CustomTheme.backgroundColor, body: tabs[currentIndex], - extendBody: true, // Enables floating effect + extendBody: true, bottomNavigationBar: Padding( padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 8.0), child: Material( @@ -82,12 +82,12 @@ class _CustomNavigationBarState extends State Widget _buildNavItem(IconData icon, String label, int index) { final isSelected = currentIndex == index; - return Expanded( // makes each nav item occupy equal width = large horizontal hitbox + return Expanded( child: GestureDetector( onTap: () => onTabTapped(index), - behavior: HitTestBehavior.opaque, // ensures the entire area is tappable + behavior: HitTestBehavior.opaque, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), // adds comfortable tap height + padding: const EdgeInsets.symmetric(vertical: 5.0), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, -- 2.49.1 From 34df9f007b0b710662887037723cbec06479162e Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:49:02 +0100 Subject: [PATCH 054/563] add navbar_item.dart --- lib/presentation/widgets/navbar_item.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/presentation/widgets/navbar_item.dart diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart new file mode 100644 index 0000000..dd938b2 --- /dev/null +++ b/lib/presentation/widgets/navbar_item.dart @@ -0,0 +1,15 @@ +import 'package:flutter/cupertino.dart'; + +class NavbarItem extends StatefulWidget { + const NavbarItem({super.key}); + + @override + State createState() => _NavbarItemState(); +} + +class _NavbarItemState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} -- 2.49.1 From 92817bc4dbdc0b54a0b8a03d5a7b341241f2409b Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:05:19 +0100 Subject: [PATCH 055/563] move navbar widget to its own file --- .../main_menu/custom_navigation_bar.dart | 42 ++--------------- lib/presentation/widgets/navbar_item.dart | 47 +++++++++++++++++-- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index bf7d90b..10ee10f 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; +import 'package:game_tracker/presentation/widgets/navbar_item.dart'; class CustomNavigationBar extends StatefulWidget { const CustomNavigationBar({super.key}); @@ -66,10 +67,10 @@ class _CustomNavigationBarState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _buildNavItem(Icons.home, 'Home', 0), - _buildNavItem(Icons.history, 'Games', 1), - _buildNavItem(Icons.groups, 'Groups', 2), - _buildNavItem(Icons.bar_chart, 'Stats', 3), + NavbarItem(currentIndex: currentIndex, index: 0, icon: Icons.home_rounded, label: 'Home', onTabTapped: onTabTapped), + NavbarItem(currentIndex: currentIndex, index: 1, icon: Icons.gamepad_rounded, label: 'Games', onTabTapped: onTabTapped), + NavbarItem(currentIndex: currentIndex, index: 2, icon: Icons.group_rounded, label: 'Groups', onTabTapped: onTabTapped), + NavbarItem(currentIndex: currentIndex, index: 3, icon: Icons.bar_chart_rounded, label: 'Stats', onTabTapped: onTabTapped), ], ), ), @@ -79,39 +80,6 @@ class _CustomNavigationBarState extends State ); } - Widget _buildNavItem(IconData icon, String label, int index) { - final isSelected = currentIndex == index; - - return Expanded( - child: GestureDetector( - onTap: () => onTabTapped(index), - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon, - color: isSelected ? Colors.white : Colors.black, - ), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - color: isSelected ? Colors.white : Colors.black, - fontSize: 12, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - ), - ), - ], - ), - ), - ), - ); -} - void onTabTapped(int index) { setState(() { currentIndex = index; diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index dd938b2..f178ccf 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,7 +1,18 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; class NavbarItem extends StatefulWidget { - const NavbarItem({super.key}); + + final int currentIndex; + final int index; + final IconData icon; + final String label; + final Function(int) onTabTapped; + + const NavbarItem( + {super.key, required this.currentIndex, required this.index, + required this.icon, required this.label, required this.onTabTapped + } + ); @override State createState() => _NavbarItemState(); @@ -10,6 +21,36 @@ class NavbarItem extends StatefulWidget { class _NavbarItemState extends State { @override Widget build(BuildContext context) { - return const Placeholder(); + + bool isSelected = widget.currentIndex == widget.index; + + return Expanded( + child: GestureDetector( + onTap: () => widget.onTabTapped(widget.index), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + widget.icon, + color: isSelected ? Colors.white : Colors.black, + ), + const SizedBox(height: 4), + Text( + widget.label, + style: TextStyle( + color: isSelected ? Colors.white : Colors.black, + fontSize: 12, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ), + ), + ), + ); } } -- 2.49.1 From 3916cca2ca01b6f88fd28d6487ae4da1b3f1202f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 9 Nov 2025 19:10:11 +0100 Subject: [PATCH 056/563] Implemented QuickInfoTile --- ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile | 2 +- ios/Runner.xcodeproj/project.pbxproj | 118 +++++++++++++++++- .../contents.xcworkspacedata | 3 + lib/core/custom_theme.dart | 4 +- .../views/main_menu/home_view.dart | 19 ++- lib/presentation/widgets/quick_info_tile.dart | 56 +++++++++ 7 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 lib/presentation/widgets/quick_info_tile.dart diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 7c56964..1dc6cf7 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/ios/Podfile b/ios/Podfile index e549ee2..620e46e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e663485..8358b1c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,9 +11,11 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8AD879B4BA24BC1EB84E1092 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D622CF241440C10C19C0D397 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,14 +42,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 13301BC306FBFE16F253F2B9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 32DDFE3349B038E1CA758D7B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,13 +61,26 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B194217AD06D15D90AAF9056 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D622CF241440C10C19C0D397 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6F6FEDCE9772FEF7A6255134 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8AD879B4BA24BC1EB84E1092 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,6 +113,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + ABF0E17C36D6999806C09130 /* Pods */, + F14326E3F17437DD2E32AB7B /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +142,29 @@ path = Runner; sourceTree = ""; }; + ABF0E17C36D6999806C09130 /* Pods */ = { + isa = PBXGroup; + children = ( + B194217AD06D15D90AAF9056 /* Pods-Runner.debug.xcconfig */, + 32DDFE3349B038E1CA758D7B /* Pods-Runner.release.xcconfig */, + 13301BC306FBFE16F253F2B9 /* Pods-Runner.profile.xcconfig */, + 96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */, + B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */, + E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + F14326E3F17437DD2E32AB7B /* Frameworks */ = { + isa = PBXGroup; + children = ( + D622CF241440C10C19C0D397 /* Pods_Runner.framework */, + 8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + F7D5E29C2C77E2E8925BBB8A /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 6F6FEDCE9772FEF7A6255134 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 8947D8DE27F8CB7D5A5F265C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 0CC58B149CD3F41CF94E1C52 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +270,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0CC58B149CD3F41CF94E1C52 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +303,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 8947D8DE27F8CB7D5A5F265C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + F7D5E29C2C77E2E8925BBB8A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -346,7 +455,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -379,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -473,7 +585,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -524,7 +636,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 1a5741a..438aab5 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; class CustomTheme { static Color primaryColor = const Color(0xFF7505E4); static Color secondaryColor = const Color(0xFFAFA2FF); - static Color backgroundColor = const Color(0xFF1A1A1A); + static Color backgroundColor = const Color(0xFF0B0B0B); + static Color boxColor = const Color(0xFF101010); + static Color boxBorder = const Color(0xFF272727); static AppBarTheme appBarTheme = AppBarTheme( backgroundColor: backgroundColor, diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index fb23bc1..1a95b27 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,10 +1,25 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/quick_info_tile.dart'; class HomeView extends StatelessWidget { const HomeView({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('Home View')); + return const Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickInfoTile(title: 'Games', icon: Icons.casino, value: 42), + QuickInfoTile( + title: 'Groups', + icon: Icons.groups_rounded, + value: 5, + ), + ], + ), + ], + ); } } diff --git a/lib/presentation/widgets/quick_info_tile.dart b/lib/presentation/widgets/quick_info_tile.dart new file mode 100644 index 0000000..69ab5c6 --- /dev/null +++ b/lib/presentation/widgets/quick_info_tile.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class QuickInfoTile extends StatefulWidget { + final String title; + final IconData icon; + final int value; + const QuickInfoTile({ + super.key, + required this.title, + required this.icon, + required this.value, + }); + + @override + State createState() => _QuickInfoTileState(); +} + +class _QuickInfoTileState extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + height: 110, + width: 180, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.casino), + const SizedBox(width: 5), + Text( + widget.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const Spacer(), + Text( + widget.value.toString(), + style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} -- 2.49.1 From 1b090a43a0648db180f01ebc55debd606bc808f2 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 9 Nov 2025 20:49:14 +0100 Subject: [PATCH 057/563] a bit cleaner solution --- .../main_menu/custom_navigation_bar.dart | 32 ++++++++++++++++--- lib/presentation/widgets/navbar_item.dart | 27 ++++++++-------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 10ee10f..20ced7a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -67,10 +67,34 @@ class _CustomNavigationBarState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - NavbarItem(currentIndex: currentIndex, index: 0, icon: Icons.home_rounded, label: 'Home', onTabTapped: onTabTapped), - NavbarItem(currentIndex: currentIndex, index: 1, icon: Icons.gamepad_rounded, label: 'Games', onTabTapped: onTabTapped), - NavbarItem(currentIndex: currentIndex, index: 2, icon: Icons.group_rounded, label: 'Groups', onTabTapped: onTabTapped), - NavbarItem(currentIndex: currentIndex, index: 3, icon: Icons.bar_chart_rounded, label: 'Stats', onTabTapped: onTabTapped), + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: 'Home', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: 'Games', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: 'Groups', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: 'Stats', + onTabTapped: onTabTapped, + ), ], ), ), diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index f178ccf..b249571 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,18 +1,20 @@ import 'package:flutter/material.dart'; class NavbarItem extends StatefulWidget { - - final int currentIndex; final int index; + final bool isSelected; final IconData icon; final String label; final Function(int) onTabTapped; - const NavbarItem( - {super.key, required this.currentIndex, required this.index, - required this.icon, required this.label, required this.onTabTapped - } - ); + const NavbarItem({ + super.key, + required this.index, + required this.isSelected, + required this.icon, + required this.label, + required this.onTabTapped, + }); @override State createState() => _NavbarItemState(); @@ -21,9 +23,6 @@ class NavbarItem extends StatefulWidget { class _NavbarItemState extends State { @override Widget build(BuildContext context) { - - bool isSelected = widget.currentIndex == widget.index; - return Expanded( child: GestureDetector( onTap: () => widget.onTabTapped(widget.index), @@ -36,15 +35,17 @@ class _NavbarItemState extends State { children: [ Icon( widget.icon, - color: isSelected ? Colors.white : Colors.black, + color: widget.isSelected ? Colors.white : Colors.black, ), const SizedBox(height: 4), Text( widget.label, style: TextStyle( - color: isSelected ? Colors.white : Colors.black, + color: widget.isSelected ? Colors.white : Colors.black, fontSize: 12, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontWeight: widget.isSelected + ? FontWeight.bold + : FontWeight.normal, ), ), ], -- 2.49.1 From e88211245c8f3d0387b3c62d869372accab9155e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 9 Nov 2025 23:33:29 +0100 Subject: [PATCH 058/563] Implemented first version of home view --- .../views/main_menu/home_view.dart | 45 +++++++++- lib/presentation/widgets/game_tile.dart | 82 +++++++++++++++++++ lib/presentation/widgets/info_tile.dart | 61 ++++++++++++++ .../widgets/quick_create_button.dart | 34 ++++++++ lib/presentation/widgets/quick_info_tile.dart | 2 +- 5 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 lib/presentation/widgets/game_tile.dart create mode 100644 lib/presentation/widgets/info_tile.dart create mode 100644 lib/presentation/widgets/quick_create_button.dart diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1a95b27..fb03c5f 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/game_tile.dart'; +import 'package:game_tracker/presentation/widgets/info_tile.dart'; +import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/quick_info_tile.dart'; class HomeView extends StatelessWidget { @@ -6,9 +9,10 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { - return const Column( + return Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( + const Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ QuickInfoTile(title: 'Games', icon: Icons.casino, value: 42), @@ -19,6 +23,43 @@ class HomeView extends StatelessWidget { ), ], ), + const InfoTile( + title: 'Recent Games', + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + icon: Icons.timer, + content: GameTile(), + ), + InfoTile( + title: 'Quick Create', + height: 210, + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + icon: Icons.add_box_rounded, + content: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Cabo', onPressed: () {}), + QuickCreateButton(text: 'Uno', onPressed: () {}), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Phase 10', onPressed: () {}), + QuickCreateButton(text: 'Category 5', onPressed: () {}), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Category 6', onPressed: () {}), + QuickCreateButton(text: 'Category 7', onPressed: () {}), + ], + ), + ], + ), + ), ], ); } diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/game_tile.dart new file mode 100644 index 0000000..546531c --- /dev/null +++ b/lib/presentation/widgets/game_tile.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class GameTile extends StatefulWidget { + const GameTile({super.key}); + + @override + State createState() => _GameTileState(); +} + +class _GameTileState extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + height: 120, + width: 250, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Text( + 'Gametitle', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(width: 5), + Text( + 'Gametype', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + const SizedBox(height: 5), + Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: CustomTheme.primaryColor, + ), + child: const Text( + 'Ruleset', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + const Center( + child: Text( + '5 Player', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + const Spacer(), + Center( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + width: 220, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Colors.yellow.shade300, + ), + child: const Text( + 'In Progress', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/info_tile.dart b/lib/presentation/widgets/info_tile.dart new file mode 100644 index 0000000..a323556 --- /dev/null +++ b/lib/presentation/widgets/info_tile.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class InfoTile extends StatefulWidget { + final String title; + final IconData icon; + final Widget content; + final EdgeInsets? padding; + final double? height; + const InfoTile({ + super.key, + required this.title, + required this.icon, + required this.content, + this.padding, + this.height, + }); + + @override + State createState() => _InfoTileState(); +} + +class _InfoTileState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: widget.padding ?? const EdgeInsets.all(0), + child: Container( + padding: const EdgeInsets.all(12), + height: widget.height ?? 200, + width: 380, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Icon(widget.icon), + const SizedBox(width: 5), + Text( + widget.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + widget.content, + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/quick_create_button.dart b/lib/presentation/widgets/quick_create_button.dart new file mode 100644 index 0000000..bdf2945 --- /dev/null +++ b/lib/presentation/widgets/quick_create_button.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class QuickCreateButton extends StatefulWidget { + final String text; + final VoidCallback? onPressed; + const QuickCreateButton({ + super.key, + required this.text, + required this.onPressed, + }); + + @override + State createState() => _QuickCreateButtonState(); +} + +class _QuickCreateButtonState extends State { + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: widget.onPressed, + + style: ElevatedButton.styleFrom( + minimumSize: const Size(140, 40), + backgroundColor: CustomTheme.primaryColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + child: Text( + widget.text, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } +} diff --git a/lib/presentation/widgets/quick_info_tile.dart b/lib/presentation/widgets/quick_info_tile.dart index 69ab5c6..6831c65 100644 --- a/lib/presentation/widgets/quick_info_tile.dart +++ b/lib/presentation/widgets/quick_info_tile.dart @@ -33,7 +33,7 @@ class _QuickInfoTileState extends State { children: [ Row( children: [ - const Icon(Icons.casino), + Icon(widget.icon), const SizedBox(width: 5), Text( widget.title, -- 2.49.1 From 6cbc64c0421f4291e3b9252f3aebd8348f79b8c9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 11 Nov 2025 15:34:16 +0100 Subject: [PATCH 059/563] Simplified game tile --- lib/presentation/widgets/game_tile.dart | 126 +++++++++++++----------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/game_tile.dart index 546531c..c79a6b7 100644 --- a/lib/presentation/widgets/game_tile.dart +++ b/lib/presentation/widgets/game_tile.dart @@ -2,7 +2,20 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; class GameTile extends StatefulWidget { - const GameTile({super.key}); + final String gameTitle; + final String gameType; + final String ruleset; + final String players; + final String winner; + + const GameTile({ + super.key, + required this.gameTitle, + required this.gameType, + required this.ruleset, + required this.players, + required this.winner, + }); @override State createState() => _GameTileState(); @@ -11,72 +24,67 @@ class GameTile extends StatefulWidget { class _GameTileState extends State { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(12), - height: 120, - width: 250, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Row( - children: [ - Text( - 'Gametitle', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - SizedBox(width: 5), - Text( - 'Gametype', - style: TextStyle(fontSize: 14, color: Colors.grey), - ), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.gameTitle, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(width: 5), + Text( + widget.gameType, + style: const TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + const SizedBox(height: 5), + Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: CustomTheme.primaryColor, ), - const SizedBox(height: 5), - Container( + child: Text( + widget.ruleset, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Center( + heightFactor: 1.5, + child: Text( + widget.players, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Center( + child: Container( padding: const EdgeInsets.symmetric(horizontal: 4), - height: 20, + width: 220, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), - color: CustomTheme.primaryColor, + color: Colors.yellow.shade300, ), - child: const Text( - 'Ruleset', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - const Center( - child: Text( - '5 Player', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - const Spacer(), - Center( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 4), - width: 220, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Colors.yellow.shade300, - ), - child: const Text( - 'In Progress', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black87, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.emoji_events, color: Colors.black, size: 20), + Text( + widget.winner, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + ), ), - ), + ], ), ), - ], - ), + ), + ], ); } } -- 2.49.1 From 4f8ba002d3f1f2cb966aabe59eeaaeaa9a351153 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 11 Nov 2025 15:34:31 +0100 Subject: [PATCH 060/563] Overhauled layout building --- .../views/main_menu/home_view.dart | 135 ++++++++++++------ lib/presentation/widgets/info_tile.dart | 61 -------- .../widgets/quick_create_button.dart | 5 +- lib/presentation/widgets/tiles/info_tile.dart | 60 ++++++++ .../widgets/{ => tiles}/quick_info_tile.dart | 12 +- 5 files changed, 161 insertions(+), 112 deletions(-) delete mode 100644 lib/presentation/widgets/info_tile.dart create mode 100644 lib/presentation/widgets/tiles/info_tile.dart rename lib/presentation/widgets/{ => tiles}/quick_info_tile.dart (84%) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index fb03c5f..bbed244 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,66 +1,111 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/presentation/widgets/game_tile.dart'; -import 'package:game_tracker/presentation/widgets/info_tile.dart'; import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; -import 'package:game_tracker/presentation/widgets/quick_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; class HomeView extends StatelessWidget { const HomeView({super.key}); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickInfoTile(title: 'Games', icon: Icons.casino, value: 42), - QuickInfoTile( - title: 'Groups', - icon: Icons.groups_rounded, - value: 5, - ), - ], - ), - const InfoTile( - title: 'Recent Games', - padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), - icon: Icons.timer, - content: GameTile(), - ), - InfoTile( - title: 'Quick Create', - height: 210, - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), - icon: Icons.add_box_rounded, - content: Column( + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.center, children: [ - QuickCreateButton(text: 'Cabo', onPressed: () {}), - QuickCreateButton(text: 'Uno', onPressed: () {}), + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Games', + icon: Icons.casino, + value: 42, + ), + SizedBox(width: constraints.maxWidth * 0.05), + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: 5, + ), ], ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton(text: 'Phase 10', onPressed: () {}), - QuickCreateButton(text: 'Category 5', onPressed: () {}), - ], + Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Recent Games', + icon: Icons.timer, + content: const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GameTile( + gameTitle: 'Gamenight', + gameType: 'Cabo', + ruleset: 'Lowest Points', + players: '5 Players', + winner: 'Leonard', + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + GameTile( + gameTitle: 'Schoolbreak', + gameType: 'Uno', + ruleset: 'Highest Points', + players: 'The Gang', + winner: 'Lina', + ), + SizedBox(height: 8), + ], + ), + ), + ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton(text: 'Category 6', onPressed: () {}), - QuickCreateButton(text: 'Category 7', onPressed: () {}), - ], + InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Quick Create', + icon: Icons.add_box_rounded, + content: Column( + spacing: 8, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Category 1', onPressed: () {}), + QuickCreateButton(text: 'Category 2', onPressed: () {}), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Category 3', onPressed: () {}), + QuickCreateButton(text: 'Category 4', onPressed: () {}), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton(text: 'Category 5', onPressed: () {}), + QuickCreateButton(text: 'Category 6', onPressed: () {}), + ], + ), + ], + ), ), ], ), - ), - ], + ); + }, ); } } diff --git a/lib/presentation/widgets/info_tile.dart b/lib/presentation/widgets/info_tile.dart deleted file mode 100644 index a323556..0000000 --- a/lib/presentation/widgets/info_tile.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -class InfoTile extends StatefulWidget { - final String title; - final IconData icon; - final Widget content; - final EdgeInsets? padding; - final double? height; - const InfoTile({ - super.key, - required this.title, - required this.icon, - required this.content, - this.padding, - this.height, - }); - - @override - State createState() => _InfoTileState(); -} - -class _InfoTileState extends State { - @override - Widget build(BuildContext context) { - return Padding( - padding: widget.padding ?? const EdgeInsets.all(0), - child: Container( - padding: const EdgeInsets.all(12), - height: widget.height ?? 200, - width: 380, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - children: [ - Icon(widget.icon), - const SizedBox(width: 5), - Text( - widget.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 10), - widget.content, - ], - ), - ), - ); - } -} diff --git a/lib/presentation/widgets/quick_create_button.dart b/lib/presentation/widgets/quick_create_button.dart index bdf2945..3860f1c 100644 --- a/lib/presentation/widgets/quick_create_button.dart +++ b/lib/presentation/widgets/quick_create_button.dart @@ -19,15 +19,14 @@ class _QuickCreateButtonState extends State { Widget build(BuildContext context) { return ElevatedButton( onPressed: widget.onPressed, - style: ElevatedButton.styleFrom( - minimumSize: const Size(140, 40), + minimumSize: const Size(140, 45), backgroundColor: CustomTheme.primaryColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( widget.text, - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ); } diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart new file mode 100644 index 0000000..168262e --- /dev/null +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class InfoTile extends StatefulWidget { + final String title; + final IconData icon; + final Widget content; + final EdgeInsets? padding; + final double? height; + final double? width; + const InfoTile({ + super.key, + required this.title, + required this.icon, + required this.content, + this.padding, + this.height, + this.width, + }); + + @override + State createState() => _InfoTileState(); +} + +class _InfoTileState extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: widget.padding ?? const EdgeInsets.all(12), + height: widget.height, + width: widget.width ?? 380, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Icon(widget.icon), + const SizedBox(width: 5), + Text( + widget.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + widget.content, + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart similarity index 84% rename from lib/presentation/widgets/quick_info_tile.dart rename to lib/presentation/widgets/tiles/quick_info_tile.dart index 6831c65..423b8d3 100644 --- a/lib/presentation/widgets/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -5,11 +5,17 @@ class QuickInfoTile extends StatefulWidget { final String title; final IconData icon; final int value; + final double? height; + final double? width; + final EdgeInsets? padding; const QuickInfoTile({ super.key, required this.title, required this.icon, required this.value, + this.height, + this.width, + this.padding, }); @override @@ -20,9 +26,9 @@ class _QuickInfoTileState extends State { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(12), - height: 110, - width: 180, + padding: widget.padding ?? const EdgeInsets.all(12), + height: widget.height ?? 110, + width: widget.width ?? 180, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: CustomTheme.boxColor, -- 2.49.1 From dd754eb5696bfb02998b6463d97211bee82c058a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 11 Nov 2025 21:03:10 +0100 Subject: [PATCH 061/563] Implemented retrieving game and group count from the db --- lib/data/dao/game_dao.dart | 9 +++ lib/data/dao/group_dao.dart | 9 +++ .../views/main_menu/home_view.dart | 60 +++++++++++++++---- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index e59fae4..3b6529a 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -22,4 +22,13 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final result = await query.getSingle(); return Game(id: result.id, name: result.name); } + + /// Retrieves the number of games in the database. + Future getGameCount() async { + final count = + await (selectOnly(gameTable)..addColumns([gameTable.id.count()])) + .map((row) => row.read(gameTable.id.count())) + .getSingle(); + return count ?? 0; + } } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index c1932d3..7dc144d 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -56,4 +56,13 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ); return rowsAffected > 0; } + + /// Retrieves the number of groups in the database. + Future getGroupCount() async { + final count = + await (selectOnly(groupTable)..addColumns([groupTable.id.count()])) + .map((row) => row.read(groupTable.id.count())) + .getSingle(); + return count ?? 0; + } } diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index bbed244..42cb822 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,12 +1,30 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/presentation/widgets/game_tile.dart'; import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; +import 'package:provider/provider.dart'; -class HomeView extends StatelessWidget { +class HomeView extends StatefulWidget { const HomeView({super.key}); + @override + State createState() => _HomeViewState(); +} + +class _HomeViewState extends State { + late Future _gameCountFuture; + late Future _groupCountFuture; + + @override + initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _gameCountFuture = db.gameDao.getGameCount(); + _groupCountFuture = db.groupDao.getGroupCount(); + } + @override Widget build(BuildContext context) { return LayoutBuilder( @@ -18,20 +36,36 @@ class HomeView extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Games', - icon: Icons.casino, - value: 42, + FutureBuilder( + future: _gameCountFuture, + builder: (context, snapshot) { + final int count = (snapshot.hasData) ? snapshot.data! : 0; + return QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Games', + icon: Icons.groups_rounded, + value: count, + ); + }, ), SizedBox(width: constraints.maxWidth * 0.05), - QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Groups', - icon: Icons.groups_rounded, - value: 5, + FutureBuilder( + future: _groupCountFuture, + builder: (context, snapshot) { + final int count = + (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) + ? snapshot.data! + : 0; + return QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: count, + ); + }, ), ], ), -- 2.49.1 From 40f9ff6413bdd15f9cbd56cf7e910488f67ebe39 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 11 Nov 2025 21:16:35 +0100 Subject: [PATCH 062/563] Updated padding --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 42cb822..141af77 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -70,7 +70,7 @@ class _HomeViewState extends State { ], ), Padding( - padding: const EdgeInsets.symmetric(vertical: 24.0), + padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, title: 'Recent Games', -- 2.49.1 From b83fe03f35e87fd9ba2c6beda361bf434deaca2d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 10:54:05 +0100 Subject: [PATCH 063/563] Renamed app --- lib/main.dart | 6 +++--- test/widget_test.dart | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 22f94c7..98c40f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,14 +8,14 @@ void main() { runApp( Provider( create: (context) => AppDatabase(), - child: const MyApp(), + child: const GameTracker(), dispose: (context, db) => db.close(), ), ); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class GameTracker extends StatelessWidget { + const GameTracker({super.key}); @override Widget build(BuildContext context) { diff --git a/test/widget_test.dart b/test/widget_test.dart index 25cbc0e..cb44eec 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -7,13 +7,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:game_tracker/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const GameTracker()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); -- 2.49.1 From 25c7b37df3bc18d87bf964348765eaafed4bf57d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 10:57:46 +0100 Subject: [PATCH 064/563] updated userId to playerId --- lib/data/dao/player_group_dao.dart | 14 +-- lib/data/db/database.g.dart | 128 +++++++++++---------- lib/data/db/tables/player_group_table.dart | 4 +- 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index b68ed09..e51c325 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -19,26 +19,26 @@ class PlayerGroupDao extends DatabaseAccessor List groupMembers = []; for (var entry in result) { - final player = await db.playerDao.getPlayerById(entry.userId); + final player = await db.playerDao.getPlayerById(entry.playerId); groupMembers.add(player); } return groupMembers; } - /// Removes a player from a group based on [userId] and [groupId]. + /// Removes a player from a group based on [playerId] and [groupId]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromGroup(String userId, String groupId) async { + Future removePlayerFromGroup(String playerId, String groupId) async { final query = delete(playerGroupTable) - ..where((p) => p.userId.equals(userId) & p.groupId.equals(groupId)); + ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); final rowsAffected = await query.go(); return rowsAffected > 0; } - /// Adds a player to a group with the given [userId] and [groupId]. - Future addPlayerToGroup(String userId, String groupId) async { + /// Adds a player to a group with the given [playerId] and [groupId]. + Future addPlayerToGroup(String playerId, String groupId) async { await into(playerGroupTable).insert( - PlayerGroupTableCompanion.insert(userId: userId, groupId: groupId), + PlayerGroupTableCompanion.insert(playerId: playerId, groupId: groupId), ); } } diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 4075912..d4e9e94 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -419,10 +419,12 @@ class $PlayerGroupTableTable extends PlayerGroupTable final GeneratedDatabase attachedDatabase; final String? _alias; $PlayerGroupTableTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + static const VerificationMeta _playerIdMeta = const VerificationMeta( + 'playerId', + ); @override - late final GeneratedColumn userId = GeneratedColumn( - 'user_id', + late final GeneratedColumn playerId = GeneratedColumn( + 'player_id', aliasedName, false, type: DriftSqlType.string, @@ -446,7 +448,7 @@ class $PlayerGroupTableTable extends PlayerGroupTable ), ); @override - List get $columns => [userId, groupId]; + List get $columns => [playerId, groupId]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -459,13 +461,13 @@ class $PlayerGroupTableTable extends PlayerGroupTable }) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('user_id')) { + if (data.containsKey('player_id')) { context.handle( - _userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + _playerIdMeta, + playerId.isAcceptableOrUnknown(data['player_id']!, _playerIdMeta), ); } else if (isInserting) { - context.missing(_userIdMeta); + context.missing(_playerIdMeta); } if (data.containsKey('group_id')) { context.handle( @@ -479,14 +481,14 @@ class $PlayerGroupTableTable extends PlayerGroupTable } @override - Set get $primaryKey => {userId, groupId}; + Set get $primaryKey => {playerId, groupId}; @override PlayerGroupTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PlayerGroupTableData( - userId: attachedDatabase.typeMapping.read( + playerId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}user_id'], + data['${effectivePrefix}player_id'], )!, groupId: attachedDatabase.typeMapping.read( DriftSqlType.string, @@ -503,20 +505,20 @@ class $PlayerGroupTableTable extends PlayerGroupTable class PlayerGroupTableData extends DataClass implements Insertable { - final String userId; + final String playerId; final String groupId; - const PlayerGroupTableData({required this.userId, required this.groupId}); + const PlayerGroupTableData({required this.playerId, required this.groupId}); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['user_id'] = Variable(userId); + map['player_id'] = Variable(playerId); map['group_id'] = Variable(groupId); return map; } PlayerGroupTableCompanion toCompanion(bool nullToAbsent) { return PlayerGroupTableCompanion( - userId: Value(userId), + playerId: Value(playerId), groupId: Value(groupId), ); } @@ -527,7 +529,7 @@ class PlayerGroupTableData extends DataClass }) { serializer ??= driftRuntimeOptions.defaultSerializer; return PlayerGroupTableData( - userId: serializer.fromJson(json['userId']), + playerId: serializer.fromJson(json['playerId']), groupId: serializer.fromJson(json['groupId']), ); } @@ -535,19 +537,19 @@ class PlayerGroupTableData extends DataClass Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'userId': serializer.toJson(userId), + 'playerId': serializer.toJson(playerId), 'groupId': serializer.toJson(groupId), }; } - PlayerGroupTableData copyWith({String? userId, String? groupId}) => + PlayerGroupTableData copyWith({String? playerId, String? groupId}) => PlayerGroupTableData( - userId: userId ?? this.userId, + playerId: playerId ?? this.playerId, groupId: groupId ?? this.groupId, ); PlayerGroupTableData copyWithCompanion(PlayerGroupTableCompanion data) { return PlayerGroupTableData( - userId: data.userId.present ? data.userId.value : this.userId, + playerId: data.playerId.present ? data.playerId.value : this.playerId, groupId: data.groupId.present ? data.groupId.value : this.groupId, ); } @@ -555,56 +557,56 @@ class PlayerGroupTableData extends DataClass @override String toString() { return (StringBuffer('PlayerGroupTableData(') - ..write('userId: $userId, ') + ..write('playerId: $playerId, ') ..write('groupId: $groupId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(userId, groupId); + int get hashCode => Object.hash(playerId, groupId); @override bool operator ==(Object other) => identical(this, other) || (other is PlayerGroupTableData && - other.userId == this.userId && + other.playerId == this.playerId && other.groupId == this.groupId); } class PlayerGroupTableCompanion extends UpdateCompanion { - final Value userId; + final Value playerId; final Value groupId; final Value rowid; const PlayerGroupTableCompanion({ - this.userId = const Value.absent(), + this.playerId = const Value.absent(), this.groupId = const Value.absent(), this.rowid = const Value.absent(), }); PlayerGroupTableCompanion.insert({ - required String userId, + required String playerId, required String groupId, this.rowid = const Value.absent(), - }) : userId = Value(userId), + }) : playerId = Value(playerId), groupId = Value(groupId); static Insertable custom({ - Expression? userId, + Expression? playerId, Expression? groupId, Expression? rowid, }) { return RawValuesInsertable({ - if (userId != null) 'user_id': userId, + if (playerId != null) 'player_id': playerId, if (groupId != null) 'group_id': groupId, if (rowid != null) 'rowid': rowid, }); } PlayerGroupTableCompanion copyWith({ - Value? userId, + Value? playerId, Value? groupId, Value? rowid, }) { return PlayerGroupTableCompanion( - userId: userId ?? this.userId, + playerId: playerId ?? this.playerId, groupId: groupId ?? this.groupId, rowid: rowid ?? this.rowid, ); @@ -613,8 +615,8 @@ class PlayerGroupTableCompanion extends UpdateCompanion { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (userId.present) { - map['user_id'] = Variable(userId.value); + if (playerId.present) { + map['player_id'] = Variable(playerId.value); } if (groupId.present) { map['group_id'] = Variable(groupId.value); @@ -628,7 +630,7 @@ class PlayerGroupTableCompanion extends UpdateCompanion { @override String toString() { return (StringBuffer('PlayerGroupTableCompanion(') - ..write('userId: $userId, ') + ..write('playerId: $playerId, ') ..write('groupId: $groupId, ') ..write('rowid: $rowid') ..write(')')) @@ -890,7 +892,7 @@ final class $$PlayerTableTableReferences db.playerGroupTable, aliasName: $_aliasNameGenerator( db.playerTable.id, - db.playerGroupTable.userId, + db.playerGroupTable.playerId, ), ); @@ -898,7 +900,7 @@ final class $$PlayerTableTableReferences final manager = $$PlayerGroupTableTableTableManager( $_db, $_db.playerGroupTable, - ).filter((f) => f.userId.id.sqlEquals($_itemColumn('id')!)); + ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull( _playerGroupTableRefsTable($_db), @@ -935,7 +937,7 @@ class $$PlayerTableTableFilterComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.playerGroupTable, - getReferencedColumn: (t) => t.userId, + getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { @@ -996,7 +998,7 @@ class $$PlayerTableTableAnnotationComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.playerGroupTable, - getReferencedColumn: (t) => t.userId, + getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { @@ -1087,7 +1089,7 @@ class $$PlayerTableTableTableManager p0, ).playerGroupTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.userId == item.id), + referencedItems.where((e) => e.playerId == item.id), typedResults: items, ), ]; @@ -1358,13 +1360,13 @@ typedef $$GroupTableTableProcessedTableManager = >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = PlayerGroupTableCompanion Function({ - required String userId, + required String playerId, required String groupId, Value rowid, }); typedef $$PlayerGroupTableTableUpdateCompanionBuilder = PlayerGroupTableCompanion Function({ - Value userId, + Value playerId, Value groupId, Value rowid, }); @@ -1382,19 +1384,19 @@ final class $$PlayerGroupTableTableReferences super.$_typedResult, ); - static $PlayerTableTable _userIdTable(_$AppDatabase db) => + static $PlayerTableTable _playerIdTable(_$AppDatabase db) => db.playerTable.createAlias( - $_aliasNameGenerator(db.playerGroupTable.userId, db.playerTable.id), + $_aliasNameGenerator(db.playerGroupTable.playerId, db.playerTable.id), ); - $$PlayerTableTableProcessedTableManager get userId { - final $_column = $_itemColumn('user_id')!; + $$PlayerTableTableProcessedTableManager get playerId { + final $_column = $_itemColumn('player_id')!; final manager = $$PlayerTableTableTableManager( $_db, $_db.playerTable, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + final item = $_typedResult.readTableOrNull(_playerIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -1430,10 +1432,10 @@ class $$PlayerGroupTableTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableFilterComposer get userId { + $$PlayerTableTableFilterComposer get playerId { final $$PlayerTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.userId, + getCurrentColumn: (t) => t.playerId, referencedTable: $db.playerTable, getReferencedColumn: (t) => t.id, builder: @@ -1486,10 +1488,10 @@ class $$PlayerGroupTableTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableOrderingComposer get userId { + $$PlayerTableTableOrderingComposer get playerId { final $$PlayerTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.userId, + getCurrentColumn: (t) => t.playerId, referencedTable: $db.playerTable, getReferencedColumn: (t) => t.id, builder: @@ -1542,10 +1544,10 @@ class $$PlayerGroupTableTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableAnnotationComposer get userId { + $$PlayerTableTableAnnotationComposer get playerId { final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.userId, + getCurrentColumn: (t) => t.playerId, referencedTable: $db.playerTable, getReferencedColumn: (t) => t.id, builder: @@ -1602,7 +1604,7 @@ class $$PlayerGroupTableTableTableManager $$PlayerGroupTableTableUpdateCompanionBuilder, (PlayerGroupTableData, $$PlayerGroupTableTableReferences), PlayerGroupTableData, - PrefetchHooks Function({bool userId, bool groupId}) + PrefetchHooks Function({bool playerId, bool groupId}) > { $$PlayerGroupTableTableTableManager( _$AppDatabase db, @@ -1619,21 +1621,21 @@ class $$PlayerGroupTableTableTableManager $$PlayerGroupTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ - Value userId = const Value.absent(), + Value playerId = const Value.absent(), Value groupId = const Value.absent(), Value rowid = const Value.absent(), }) => PlayerGroupTableCompanion( - userId: userId, + playerId: playerId, groupId: groupId, rowid: rowid, ), createCompanionCallback: ({ - required String userId, + required String playerId, required String groupId, Value rowid = const Value.absent(), }) => PlayerGroupTableCompanion.insert( - userId: userId, + playerId: playerId, groupId: groupId, rowid: rowid, ), @@ -1645,7 +1647,7 @@ class $$PlayerGroupTableTableTableManager ), ) .toList(), - prefetchHooksCallback: ({userId = false, groupId = false}) { + prefetchHooksCallback: ({playerId = false, groupId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -1665,17 +1667,17 @@ class $$PlayerGroupTableTableTableManager dynamic > >(state) { - if (userId) { + if (playerId) { state = state.withJoin( currentTable: table, - currentColumn: table.userId, + currentColumn: table.playerId, referencedTable: $$PlayerGroupTableTableReferences - ._userIdTable(db), + ._playerIdTable(db), referencedColumn: $$PlayerGroupTableTableReferences - ._userIdTable(db) + ._playerIdTable(db) .id, ) as T; @@ -1719,7 +1721,7 @@ typedef $$PlayerGroupTableTableProcessedTableManager = $$PlayerGroupTableTableUpdateCompanionBuilder, (PlayerGroupTableData, $$PlayerGroupTableTableReferences), PlayerGroupTableData, - PrefetchHooks Function({bool userId, bool groupId}) + PrefetchHooks Function({bool playerId, bool groupId}) >; typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ diff --git a/lib/data/db/tables/player_group_table.dart b/lib/data/db/tables/player_group_table.dart index 096a981..61e2ed8 100644 --- a/lib/data/db/tables/player_group_table.dart +++ b/lib/data/db/tables/player_group_table.dart @@ -3,9 +3,9 @@ import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGroupTable extends Table { - TextColumn get userId => text().references(PlayerTable, #id)(); + TextColumn get playerId => text().references(PlayerTable, #id)(); TextColumn get groupId => text().references(GroupTable, #id)(); @override - Set> get primaryKey => {userId, groupId}; + Set> get primaryKey => {playerId, groupId}; } -- 2.49.1 From a922f24150318995b4bfa4b090cd5e1f6dca977f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 11:41:06 +0100 Subject: [PATCH 065/563] Added basic structure for game implementation --- lib/data/dao/game_dao.dart | 26 +- lib/data/dao/group_dao.dart | 12 +- lib/data/dao/group_game_dao.dart | 33 + lib/data/dao/group_game_dao.g.dart | 10 + lib/data/dao/player_game_dao.dart | 40 + lib/data/dao/player_game_dao.g.dart | 10 + lib/data/db/database.dart | 22 +- lib/data/db/database.g.dart | 1824 ++++++++++++++++++++- lib/data/db/tables/game_table.dart | 1 + lib/data/db/tables/group_game_table.dart | 11 + lib/data/db/tables/player_game_table.dart | 11 + lib/data/dto/game.dart | 14 +- 12 files changed, 1924 insertions(+), 90 deletions(-) create mode 100644 lib/data/dao/group_game_dao.dart create mode 100644 lib/data/dao/group_game_dao.g.dart create mode 100644 lib/data/dao/player_game_dao.dart create mode 100644 lib/data/dao/player_game_dao.g.dart create mode 100644 lib/data/db/tables/group_game_table.dart create mode 100644 lib/data/db/tables/player_game_table.dart diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 3b6529a..5894ed5 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -2,6 +2,8 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/game_table.dart'; import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; part 'game_dao.g.dart'; @@ -16,11 +18,27 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return result.map((row) => Game(id: row.id, name: row.name)).toList(); } - /// Retrieves a [Game] by its [id]. - Future getGameById(String id) async { - final query = select(gameTable)..where((g) => g.id.equals(id)); + /// Retrieves a [Game] by its [gameId]. + Future getGameById(String gameId) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); final result = await query.getSingle(); - return Game(id: result.id, name: result.name); + + List? players; + if (await db.playerGameDao.hasGamePlayers(gameId)) { + players = await db.playerGameDao.getPlayersByGameId(gameId); + } + Group? group; + if (await db.groupGameDao.hasGameGroup(gameId)) { + group = await db.groupGameDao.getGroupByGameId(gameId); + } + + return Game( + id: result.id, + name: result.name, + players: players, + group: group, + winner: result.winnerId, + ); } /// Retrieves the number of games in the database. diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 7dc144d..a92247a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -19,14 +19,14 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .toList(); } - /// Retrieves a [Group] by its [id], including its members. - Future getGroupById(String id) async { - final query = select(groupTable)..where((g) => g.id.equals(id)); + /// Retrieves a [Group] by its [groupId], including its members. + Future getGroupById(String groupId) async { + final query = select(groupTable)..where((g) => g.id.equals(groupId)); final result = await query.getSingle(); - List members = []; - - members = await db.playerGroupDao.getPlayersOfGroupById(id); + List members = await db.playerGroupDao.getPlayersOfGroupById( + groupId, + ); return Group(id: result.id, name: result.name, members: members); } diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart new file mode 100644 index 0000000..851e032 --- /dev/null +++ b/lib/data/dao/group_game_dao.dart @@ -0,0 +1,33 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/group_game_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; + +part 'group_game_dao.g.dart'; + +@DriftAccessor(tables: [GroupGameTable]) +class GroupGameDao extends DatabaseAccessor + with _$GroupGameDaoMixin { + GroupGameDao(super.db); + + /// Checks if there is a group associated with the given [gameId]. + /// Returns `true` if there is a group, otherwise `false`. + Future hasGameGroup(String gameId) async { + final count = + await (selectOnly(groupGameTable) + ..where(groupGameTable.gameId.equals(gameId)) + ..addColumns([groupGameTable.groupId.count()])) + .map((row) => row.read(groupGameTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + Future getGroupByGameId(String gameId) async { + final result = await (select( + groupGameTable, + )..where((g) => g.gameId.equals(gameId))).getSingle(); + + final group = await db.groupDao.getGroupById(result.groupId); + return group; + } +} diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart new file mode 100644 index 0000000..f1aa46f --- /dev/null +++ b/lib/data/dao/group_game_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'group_game_dao.dart'; + +// ignore_for_file: type=lint +mixin _$GroupGameDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; +} diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart new file mode 100644 index 0000000..c05edd1 --- /dev/null +++ b/lib/data/dao/player_game_dao.dart @@ -0,0 +1,40 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/player_game_table.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +part 'player_game_dao.g.dart'; + +@DriftAccessor(tables: [PlayerGameTable]) +class PlayerGameDao extends DatabaseAccessor + with _$PlayerGameDaoMixin { + PlayerGameDao(super.db); + + /// Checks if there are any players associated with the given [gameId]. + /// Returns `true` if there are players, otherwise `false`. + Future hasGamePlayers(String gameId) async { + final count = + await (selectOnly(playerGameTable) + ..where(playerGameTable.gameId.equals(gameId)) + ..addColumns([playerGameTable.playerId.count()])) + .map((row) => row.read(playerGameTable.playerId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Retrieves a list of [Player]s associated with the given [gameId]. + /// Returns an empty list if no players are found. + Future> getPlayersByGameId(String gameId) async { + final result = await (select( + playerGameTable, + )..where((p) => p.gameId.equals(gameId))).get(); + + if (result.isEmpty) return []; + + final futures = result.map( + (row) => db.playerDao.getPlayerById(row.playerId), + ); + final players = await Future.wait(futures); + return players.whereType().toList(); + } +} diff --git a/lib/data/dao/player_game_dao.g.dart b/lib/data/dao/player_game_dao.g.dart new file mode 100644 index 0000000..0d5f5e1 --- /dev/null +++ b/lib/data/dao/player_game_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_game_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PlayerGameDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable; +} diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index e35513e..b0662aa 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -2,10 +2,14 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; import 'package:game_tracker/data/dao/game_dao.dart'; import 'package:game_tracker/data/dao/group_dao.dart'; +import 'package:game_tracker/data/dao/group_game_dao.dart'; import 'package:game_tracker/data/dao/player_dao.dart'; +import 'package:game_tracker/data/dao/player_game_dao.dart'; import 'package:game_tracker/data/dao/player_group_dao.dart'; import 'package:game_tracker/data/db/tables/game_table.dart'; +import 'package:game_tracker/data/db/tables/group_game_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_game_table.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:path_provider/path_provider.dart'; @@ -13,8 +17,22 @@ import 'package:path_provider/path_provider.dart'; part 'database.g.dart'; @DriftDatabase( - tables: [PlayerTable, GroupTable, PlayerGroupTable, GameTable], - daos: [GroupDao, PlayerDao, PlayerGroupDao, GameDao], + tables: [ + PlayerTable, + GroupTable, + PlayerGroupTable, + PlayerGameTable, + GroupGameTable, + GameTable, + ], + daos: [ + GroupDao, + PlayerDao, + GameDao, + PlayerGroupDao, + PlayerGameDao, + GroupGameDao, + ], ) class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index d4e9e94..74efba1 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -638,6 +638,452 @@ class PlayerGroupTableCompanion extends UpdateCompanion { } } +class $PlayerGameTableTable extends PlayerGameTable + with TableInfo<$PlayerGameTableTable, PlayerGameTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PlayerGameTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _playerIdMeta = const VerificationMeta( + 'playerId', + ); + @override + late final GeneratedColumn playerId = GeneratedColumn( + 'player_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES player_table (id)', + ), + ); + static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + @override + late final GeneratedColumn gameId = GeneratedColumn( + 'game_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES group_table (id)', + ), + ); + @override + List get $columns => [playerId, gameId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'player_game_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('player_id')) { + context.handle( + _playerIdMeta, + playerId.isAcceptableOrUnknown(data['player_id']!, _playerIdMeta), + ); + } else if (isInserting) { + context.missing(_playerIdMeta); + } + if (data.containsKey('game_id')) { + context.handle( + _gameIdMeta, + gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + ); + } else if (isInserting) { + context.missing(_gameIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {playerId, gameId}; + @override + PlayerGameTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlayerGameTableData( + playerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}player_id'], + )!, + gameId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}game_id'], + )!, + ); + } + + @override + $PlayerGameTableTable createAlias(String alias) { + return $PlayerGameTableTable(attachedDatabase, alias); + } +} + +class PlayerGameTableData extends DataClass + implements Insertable { + final String playerId; + final String gameId; + const PlayerGameTableData({required this.playerId, required this.gameId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['player_id'] = Variable(playerId); + map['game_id'] = Variable(gameId); + return map; + } + + PlayerGameTableCompanion toCompanion(bool nullToAbsent) { + return PlayerGameTableCompanion( + playerId: Value(playerId), + gameId: Value(gameId), + ); + } + + factory PlayerGameTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlayerGameTableData( + playerId: serializer.fromJson(json['playerId']), + gameId: serializer.fromJson(json['gameId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'playerId': serializer.toJson(playerId), + 'gameId': serializer.toJson(gameId), + }; + } + + PlayerGameTableData copyWith({String? playerId, String? gameId}) => + PlayerGameTableData( + playerId: playerId ?? this.playerId, + gameId: gameId ?? this.gameId, + ); + PlayerGameTableData copyWithCompanion(PlayerGameTableCompanion data) { + return PlayerGameTableData( + playerId: data.playerId.present ? data.playerId.value : this.playerId, + gameId: data.gameId.present ? data.gameId.value : this.gameId, + ); + } + + @override + String toString() { + return (StringBuffer('PlayerGameTableData(') + ..write('playerId: $playerId, ') + ..write('gameId: $gameId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(playerId, gameId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlayerGameTableData && + other.playerId == this.playerId && + other.gameId == this.gameId); +} + +class PlayerGameTableCompanion extends UpdateCompanion { + final Value playerId; + final Value gameId; + final Value rowid; + const PlayerGameTableCompanion({ + this.playerId = const Value.absent(), + this.gameId = const Value.absent(), + this.rowid = const Value.absent(), + }); + PlayerGameTableCompanion.insert({ + required String playerId, + required String gameId, + this.rowid = const Value.absent(), + }) : playerId = Value(playerId), + gameId = Value(gameId); + static Insertable custom({ + Expression? playerId, + Expression? gameId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (playerId != null) 'player_id': playerId, + if (gameId != null) 'game_id': gameId, + if (rowid != null) 'rowid': rowid, + }); + } + + PlayerGameTableCompanion copyWith({ + Value? playerId, + Value? gameId, + Value? rowid, + }) { + return PlayerGameTableCompanion( + playerId: playerId ?? this.playerId, + gameId: gameId ?? this.gameId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (playerId.present) { + map['player_id'] = Variable(playerId.value); + } + if (gameId.present) { + map['game_id'] = Variable(gameId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlayerGameTableCompanion(') + ..write('playerId: $playerId, ') + ..write('gameId: $gameId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $GroupGameTableTable extends GroupGameTable + with TableInfo<$GroupGameTableTable, GroupGameTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GroupGameTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _groupIdMeta = const VerificationMeta( + 'groupId', + ); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES player_table (id)', + ), + ); + static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + @override + late final GeneratedColumn gameId = GeneratedColumn( + 'game_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES group_table (id)', + ), + ); + @override + List get $columns => [groupId, gameId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group_game_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('group_id')) { + context.handle( + _groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + ); + } else if (isInserting) { + context.missing(_groupIdMeta); + } + if (data.containsKey('game_id')) { + context.handle( + _gameIdMeta, + gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + ); + } else if (isInserting) { + context.missing(_gameIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {groupId, gameId}; + @override + GroupGameTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupGameTableData( + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + )!, + gameId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}game_id'], + )!, + ); + } + + @override + $GroupGameTableTable createAlias(String alias) { + return $GroupGameTableTable(attachedDatabase, alias); + } +} + +class GroupGameTableData extends DataClass + implements Insertable { + final String groupId; + final String gameId; + const GroupGameTableData({required this.groupId, required this.gameId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['group_id'] = Variable(groupId); + map['game_id'] = Variable(gameId); + return map; + } + + GroupGameTableCompanion toCompanion(bool nullToAbsent) { + return GroupGameTableCompanion( + groupId: Value(groupId), + gameId: Value(gameId), + ); + } + + factory GroupGameTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupGameTableData( + groupId: serializer.fromJson(json['groupId']), + gameId: serializer.fromJson(json['gameId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'groupId': serializer.toJson(groupId), + 'gameId': serializer.toJson(gameId), + }; + } + + GroupGameTableData copyWith({String? groupId, String? gameId}) => + GroupGameTableData( + groupId: groupId ?? this.groupId, + gameId: gameId ?? this.gameId, + ); + GroupGameTableData copyWithCompanion(GroupGameTableCompanion data) { + return GroupGameTableData( + groupId: data.groupId.present ? data.groupId.value : this.groupId, + gameId: data.gameId.present ? data.gameId.value : this.gameId, + ); + } + + @override + String toString() { + return (StringBuffer('GroupGameTableData(') + ..write('groupId: $groupId, ') + ..write('gameId: $gameId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(groupId, gameId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupGameTableData && + other.groupId == this.groupId && + other.gameId == this.gameId); +} + +class GroupGameTableCompanion extends UpdateCompanion { + final Value groupId; + final Value gameId; + final Value rowid; + const GroupGameTableCompanion({ + this.groupId = const Value.absent(), + this.gameId = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupGameTableCompanion.insert({ + required String groupId, + required String gameId, + this.rowid = const Value.absent(), + }) : groupId = Value(groupId), + gameId = Value(gameId); + static Insertable custom({ + Expression? groupId, + Expression? gameId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (groupId != null) 'group_id': groupId, + if (gameId != null) 'game_id': gameId, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupGameTableCompanion copyWith({ + Value? groupId, + Value? gameId, + Value? rowid, + }) { + return GroupGameTableCompanion( + groupId: groupId ?? this.groupId, + gameId: gameId ?? this.gameId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } + if (gameId.present) { + map['game_id'] = Variable(gameId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupGameTableCompanion(') + ..write('groupId: $groupId, ') + ..write('gameId: $gameId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + class $GameTableTable extends GameTable with TableInfo<$GameTableTable, GameTableData> { @override @@ -662,8 +1108,19 @@ class $GameTableTable extends GameTable type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _winnerIdMeta = const VerificationMeta( + 'winnerId', + ); @override - List get $columns => [id, name]; + late final GeneratedColumn winnerId = GeneratedColumn( + 'winner_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, name, winnerId]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -689,6 +1146,12 @@ class $GameTableTable extends GameTable } else if (isInserting) { context.missing(_nameMeta); } + if (data.containsKey('winner_id')) { + context.handle( + _winnerIdMeta, + winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), + ); + } return context; } @@ -706,6 +1169,10 @@ class $GameTableTable extends GameTable DriftSqlType.string, data['${effectivePrefix}name'], )!, + winnerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}winner_id'], + ), ); } @@ -718,17 +1185,27 @@ class $GameTableTable extends GameTable class GameTableData extends DataClass implements Insertable { final String id; final String name; - const GameTableData({required this.id, required this.name}); + final String? winnerId; + const GameTableData({required this.id, required this.name, this.winnerId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); + if (!nullToAbsent || winnerId != null) { + map['winner_id'] = Variable(winnerId); + } return map; } GameTableCompanion toCompanion(bool nullToAbsent) { - return GameTableCompanion(id: Value(id), name: Value(name)); + return GameTableCompanion( + id: Value(id), + name: Value(name), + winnerId: winnerId == null && nullToAbsent + ? const Value.absent() + : Value(winnerId), + ); } factory GameTableData.fromJson( @@ -739,6 +1216,7 @@ class GameTableData extends DataClass implements Insertable { return GameTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), + winnerId: serializer.fromJson(json['winnerId']), ); } @override @@ -747,15 +1225,24 @@ class GameTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), + 'winnerId': serializer.toJson(winnerId), }; } - GameTableData copyWith({String? id, String? name}) => - GameTableData(id: id ?? this.id, name: name ?? this.name); + GameTableData copyWith({ + String? id, + String? name, + Value winnerId = const Value.absent(), + }) => GameTableData( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId.present ? winnerId.value : this.winnerId, + ); GameTableData copyWithCompanion(GameTableCompanion data) { return GameTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, + winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, ); } @@ -763,44 +1250,51 @@ class GameTableData extends DataClass implements Insertable { String toString() { return (StringBuffer('GameTableData(') ..write('id: $id, ') - ..write('name: $name') + ..write('name: $name, ') + ..write('winnerId: $winnerId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name); + int get hashCode => Object.hash(id, name, winnerId); @override bool operator ==(Object other) => identical(this, other) || (other is GameTableData && other.id == this.id && - other.name == this.name); + other.name == this.name && + other.winnerId == this.winnerId); } class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; + final Value winnerId; final Value rowid; const GameTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), + this.winnerId = const Value.absent(), this.rowid = const Value.absent(), }); GameTableCompanion.insert({ required String id, required String name, + this.winnerId = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), name = Value(name); static Insertable custom({ Expression? id, Expression? name, + Expression? winnerId, Expression? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, + if (winnerId != null) 'winner_id': winnerId, if (rowid != null) 'rowid': rowid, }); } @@ -808,11 +1302,13 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion copyWith({ Value? id, Value? name, + Value? winnerId, Value? rowid, }) { return GameTableCompanion( id: id ?? this.id, name: name ?? this.name, + winnerId: winnerId ?? this.winnerId, rowid: rowid ?? this.rowid, ); } @@ -826,6 +1322,9 @@ class GameTableCompanion extends UpdateCompanion { if (name.present) { map['name'] = Variable(name.value); } + if (winnerId.present) { + map['winner_id'] = Variable(winnerId.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -837,6 +1336,7 @@ class GameTableCompanion extends UpdateCompanion { return (StringBuffer('GameTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') + ..write('winnerId: $winnerId, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -851,13 +1351,19 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); + late final $PlayerGameTableTable playerGameTable = $PlayerGameTableTable( + this, + ); + late final $GroupGameTableTable groupGameTable = $GroupGameTableTable(this); late final $GameTableTable gameTable = $GameTableTable(this); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); + late final GameDao gameDao = GameDao(this as AppDatabase); late final PlayerGroupDao playerGroupDao = PlayerGroupDao( this as AppDatabase, ); - late final GameDao gameDao = GameDao(this as AppDatabase); + late final PlayerGameDao playerGameDao = PlayerGameDao(this as AppDatabase); + late final GroupGameDao groupGameDao = GroupGameDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -866,6 +1372,8 @@ abstract class _$AppDatabase extends GeneratedDatabase { playerTable, groupTable, playerGroupTable, + playerGameTable, + groupGameTable, gameTable, ]; } @@ -909,6 +1417,50 @@ final class $$PlayerTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey<$PlayerGameTableTable, List> + _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerGameTable, + aliasName: $_aliasNameGenerator( + db.playerTable.id, + db.playerGameTable.playerId, + ), + ); + + $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { + final manager = $$PlayerGameTableTableTableManager( + $_db, + $_db.playerGameTable, + ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerGameTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey<$GroupGameTableTable, List> + _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupGameTable, + aliasName: $_aliasNameGenerator( + db.playerTable.id, + db.groupGameTable.groupId, + ), + ); + + $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { + final manager = $$GroupGameTableTableTableManager( + $_db, + $_db.groupGameTable, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$PlayerTableTableFilterComposer @@ -954,6 +1506,56 @@ class $$PlayerTableTableFilterComposer ); return f(composer); } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableFilterComposer f) f, + ) { + final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.playerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableFilterComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableFilterComposer f) f, + ) { + final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableFilterComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableOrderingComposer @@ -1015,6 +1617,56 @@ class $$PlayerTableTableAnnotationComposer ); return f(composer); } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + ) { + final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.playerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableAnnotationComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableAnnotationComposer a) f, + ) { + final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableAnnotationComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableTableManager @@ -1030,7 +1682,11 @@ class $$PlayerTableTableTableManager $$PlayerTableTableUpdateCompanionBuilder, (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, - PrefetchHooks Function({bool playerGroupTableRefs}) + PrefetchHooks Function({ + bool playerGroupTableRefs, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) : super( @@ -1064,38 +1720,89 @@ class $$PlayerTableTableTableManager ), ) .toList(), - prefetchHooksCallback: ({playerGroupTableRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (playerGroupTableRefs) db.playerGroupTable, - ], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (playerGroupTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - PlayerGroupTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._playerGroupTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).playerGroupTableRefs, - referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.playerId == item.id), - typedResults: items, - ), - ]; + prefetchHooksCallback: + ({ + playerGroupTableRefs = false, + playerGameTableRefs = false, + groupGameTableRefs = false, + }) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerGroupTableRefs) db.playerGroupTable, + if (playerGameTableRefs) db.playerGameTable, + if (groupGameTableRefs) db.groupGameTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (playerGroupTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + PlayerGroupTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._playerGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).playerGroupTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.playerId == item.id, + ), + typedResults: items, + ), + if (playerGameTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + PlayerGameTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._playerGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).playerGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.playerId == item.id, + ), + typedResults: items, + ), + if (groupGameTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + GroupGameTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._groupGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).groupGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.groupId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, ), ); } @@ -1112,7 +1819,11 @@ typedef $$PlayerTableTableProcessedTableManager = $$PlayerTableTableUpdateCompanionBuilder, (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, - PrefetchHooks Function({bool playerGroupTableRefs}) + PrefetchHooks Function({ + bool playerGroupTableRefs, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) >; typedef $$GroupTableTableCreateCompanionBuilder = GroupTableCompanion Function({ @@ -1153,6 +1864,47 @@ final class $$GroupTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey<$PlayerGameTableTable, List> + _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerGameTable, + aliasName: $_aliasNameGenerator( + db.groupTable.id, + db.playerGameTable.gameId, + ), + ); + + $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { + final manager = $$PlayerGameTableTableTableManager( + $_db, + $_db.playerGameTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerGameTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey<$GroupGameTableTable, List> + _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupGameTable, + aliasName: $_aliasNameGenerator(db.groupTable.id, db.groupGameTable.gameId), + ); + + $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { + final manager = $$GroupGameTableTableTableManager( + $_db, + $_db.groupGameTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$GroupTableTableFilterComposer @@ -1198,6 +1950,56 @@ class $$GroupTableTableFilterComposer ); return f(composer); } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableFilterComposer f) f, + ) { + final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableFilterComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableFilterComposer f) f, + ) { + final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableFilterComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableOrderingComposer @@ -1259,6 +2061,56 @@ class $$GroupTableTableAnnotationComposer ); return f(composer); } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + ) { + final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableAnnotationComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableAnnotationComposer a) f, + ) { + final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableAnnotationComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableTableManager @@ -1274,7 +2126,11 @@ class $$GroupTableTableTableManager $$GroupTableTableUpdateCompanionBuilder, (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function({bool playerGroupTableRefs}) + PrefetchHooks Function({ + bool playerGroupTableRefs, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) : super( @@ -1308,38 +2164,89 @@ class $$GroupTableTableTableManager ), ) .toList(), - prefetchHooksCallback: ({playerGroupTableRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (playerGroupTableRefs) db.playerGroupTable, - ], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (playerGroupTableRefs) - await $_getPrefetchedData< - GroupTableData, - $GroupTableTable, - PlayerGroupTableData - >( - currentTable: table, - referencedTable: $$GroupTableTableReferences - ._playerGroupTableRefsTable(db), - managerFromTypedResult: (p0) => - $$GroupTableTableReferences( - db, - table, - p0, - ).playerGroupTableRefs, - referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.groupId == item.id), - typedResults: items, - ), - ]; + prefetchHooksCallback: + ({ + playerGroupTableRefs = false, + playerGameTableRefs = false, + groupGameTableRefs = false, + }) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerGroupTableRefs) db.playerGroupTable, + if (playerGameTableRefs) db.playerGameTable, + if (groupGameTableRefs) db.groupGameTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (playerGroupTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + PlayerGroupTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._playerGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).playerGroupTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.groupId == item.id, + ), + typedResults: items, + ), + if (playerGameTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + PlayerGameTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._playerGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).playerGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), + if (groupGameTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + GroupGameTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._groupGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).groupGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, ), ); } @@ -1356,7 +2263,11 @@ typedef $$GroupTableTableProcessedTableManager = $$GroupTableTableUpdateCompanionBuilder, (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function({bool playerGroupTableRefs}) + PrefetchHooks Function({ + bool playerGroupTableRefs, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = PlayerGroupTableCompanion Function({ @@ -1723,16 +2634,746 @@ typedef $$PlayerGroupTableTableProcessedTableManager = PlayerGroupTableData, PrefetchHooks Function({bool playerId, bool groupId}) >; +typedef $$PlayerGameTableTableCreateCompanionBuilder = + PlayerGameTableCompanion Function({ + required String playerId, + required String gameId, + Value rowid, + }); +typedef $$PlayerGameTableTableUpdateCompanionBuilder = + PlayerGameTableCompanion Function({ + Value playerId, + Value gameId, + Value rowid, + }); + +final class $$PlayerGameTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $PlayerGameTableTable, + PlayerGameTableData + > { + $$PlayerGameTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $PlayerTableTable _playerIdTable(_$AppDatabase db) => + db.playerTable.createAlias( + $_aliasNameGenerator(db.playerGameTable.playerId, db.playerTable.id), + ); + + $$PlayerTableTableProcessedTableManager get playerId { + final $_column = $_itemColumn('player_id')!; + + final manager = $$PlayerTableTableTableManager( + $_db, + $_db.playerTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_playerIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTableTable _gameIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.playerGameTable.gameId, db.groupTable.id), + ); + + $$GroupTableTableProcessedTableManager get gameId { + final $_column = $_itemColumn('game_id')!; + + final manager = $$GroupTableTableTableManager( + $_db, + $_db.groupTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$PlayerGameTableTableFilterComposer + extends Composer<_$AppDatabase, $PlayerGameTableTable> { + $$PlayerGameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableFilterComposer get playerId { + final $$PlayerTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + 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; + } + + $$GroupTableTableFilterComposer get gameId { + final $$GroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableFilterComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerGameTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerGameTableTable> { + $$PlayerGameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableOrderingComposer get playerId { + final $$PlayerTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + 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; + } + + $$GroupTableTableOrderingComposer get gameId { + final $$GroupTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableOrderingComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerGameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerGameTableTable> { + $$PlayerGameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableAnnotationComposer get playerId { + final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + 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; + } + + $$GroupTableTableAnnotationComposer get gameId { + final $$GroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableAnnotationComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerGameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $PlayerGameTableTable, + PlayerGameTableData, + $$PlayerGameTableTableFilterComposer, + $$PlayerGameTableTableOrderingComposer, + $$PlayerGameTableTableAnnotationComposer, + $$PlayerGameTableTableCreateCompanionBuilder, + $$PlayerGameTableTableUpdateCompanionBuilder, + (PlayerGameTableData, $$PlayerGameTableTableReferences), + PlayerGameTableData, + PrefetchHooks Function({bool playerId, bool gameId}) + > { + $$PlayerGameTableTableTableManager( + _$AppDatabase db, + $PlayerGameTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$PlayerGameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$PlayerGameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$PlayerGameTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value playerId = const Value.absent(), + Value gameId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PlayerGameTableCompanion( + playerId: playerId, + gameId: gameId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String playerId, + required String gameId, + Value rowid = const Value.absent(), + }) => PlayerGameTableCompanion.insert( + playerId: playerId, + gameId: gameId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$PlayerGameTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({playerId = false, gameId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (playerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.playerId, + referencedTable: + $$PlayerGameTableTableReferences + ._playerIdTable(db), + referencedColumn: + $$PlayerGameTableTableReferences + ._playerIdTable(db) + .id, + ) + as T; + } + if (gameId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.gameId, + referencedTable: + $$PlayerGameTableTableReferences + ._gameIdTable(db), + referencedColumn: + $$PlayerGameTableTableReferences + ._gameIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$PlayerGameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $PlayerGameTableTable, + PlayerGameTableData, + $$PlayerGameTableTableFilterComposer, + $$PlayerGameTableTableOrderingComposer, + $$PlayerGameTableTableAnnotationComposer, + $$PlayerGameTableTableCreateCompanionBuilder, + $$PlayerGameTableTableUpdateCompanionBuilder, + (PlayerGameTableData, $$PlayerGameTableTableReferences), + PlayerGameTableData, + PrefetchHooks Function({bool playerId, bool gameId}) + >; +typedef $$GroupGameTableTableCreateCompanionBuilder = + GroupGameTableCompanion Function({ + required String groupId, + required String gameId, + Value rowid, + }); +typedef $$GroupGameTableTableUpdateCompanionBuilder = + GroupGameTableCompanion Function({ + Value groupId, + Value gameId, + Value rowid, + }); + +final class $$GroupGameTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $GroupGameTableTable, + GroupGameTableData + > { + $$GroupGameTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $PlayerTableTable _groupIdTable(_$AppDatabase db) => + db.playerTable.createAlias( + $_aliasNameGenerator(db.groupGameTable.groupId, db.playerTable.id), + ); + + $$PlayerTableTableProcessedTableManager get groupId { + final $_column = $_itemColumn('group_id')!; + + final manager = $$PlayerTableTableTableManager( + $_db, + $_db.playerTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTableTable _gameIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.groupGameTable.gameId, db.groupTable.id), + ); + + $$GroupTableTableProcessedTableManager get gameId { + final $_column = $_itemColumn('game_id')!; + + final manager = $$GroupTableTableTableManager( + $_db, + $_db.groupTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$GroupGameTableTableFilterComposer + extends Composer<_$AppDatabase, $GroupGameTableTable> { + $$GroupGameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableFilterComposer get groupId { + final $$PlayerTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + 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; + } + + $$GroupTableTableFilterComposer get gameId { + final $$GroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableFilterComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$GroupGameTableTableOrderingComposer + extends Composer<_$AppDatabase, $GroupGameTableTable> { + $$GroupGameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableOrderingComposer get groupId { + final $$PlayerTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + 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; + } + + $$GroupTableTableOrderingComposer get gameId { + final $$GroupTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableOrderingComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$GroupGameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupGameTableTable> { + $$GroupGameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$PlayerTableTableAnnotationComposer get groupId { + final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + 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; + } + + $$GroupTableTableAnnotationComposer get gameId { + final $$GroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableAnnotationComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$GroupGameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GroupGameTableTable, + GroupGameTableData, + $$GroupGameTableTableFilterComposer, + $$GroupGameTableTableOrderingComposer, + $$GroupGameTableTableAnnotationComposer, + $$GroupGameTableTableCreateCompanionBuilder, + $$GroupGameTableTableUpdateCompanionBuilder, + (GroupGameTableData, $$GroupGameTableTableReferences), + GroupGameTableData, + PrefetchHooks Function({bool groupId, bool gameId}) + > { + $$GroupGameTableTableTableManager( + _$AppDatabase db, + $GroupGameTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GroupGameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GroupGameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GroupGameTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value groupId = const Value.absent(), + Value gameId = const Value.absent(), + Value rowid = const Value.absent(), + }) => GroupGameTableCompanion( + groupId: groupId, + gameId: gameId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String groupId, + required String gameId, + Value rowid = const Value.absent(), + }) => GroupGameTableCompanion.insert( + groupId: groupId, + gameId: gameId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$GroupGameTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({groupId = false, gameId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: $$GroupGameTableTableReferences + ._groupIdTable(db), + referencedColumn: + $$GroupGameTableTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + if (gameId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.gameId, + referencedTable: $$GroupGameTableTableReferences + ._gameIdTable(db), + referencedColumn: + $$GroupGameTableTableReferences + ._gameIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$GroupGameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GroupGameTableTable, + GroupGameTableData, + $$GroupGameTableTableFilterComposer, + $$GroupGameTableTableOrderingComposer, + $$GroupGameTableTableAnnotationComposer, + $$GroupGameTableTableCreateCompanionBuilder, + $$GroupGameTableTableUpdateCompanionBuilder, + (GroupGameTableData, $$GroupGameTableTableReferences), + GroupGameTableData, + PrefetchHooks Function({bool groupId, bool gameId}) + >; typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ required String id, required String name, + Value winnerId, Value rowid, }); typedef $$GameTableTableUpdateCompanionBuilder = GameTableCompanion Function({ Value id, Value name, + Value winnerId, Value rowid, }); @@ -1754,6 +3395,11 @@ class $$GameTableTableFilterComposer column: $table.name, builder: (column) => ColumnFilters(column), ); + + ColumnFilters get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnFilters(column), + ); } class $$GameTableTableOrderingComposer @@ -1774,6 +3420,11 @@ class $$GameTableTableOrderingComposer column: $table.name, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnOrderings(column), + ); } class $$GameTableTableAnnotationComposer @@ -1790,6 +3441,9 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get winnerId => + $composableBuilder(column: $table.winnerId, builder: (column) => column); } class $$GameTableTableTableManager @@ -1825,14 +3479,26 @@ class $$GameTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), - }) => GameTableCompanion(id: id, name: name, rowid: rowid), + }) => GameTableCompanion( + id: id, + name: name, + winnerId: winnerId, + rowid: rowid, + ), createCompanionCallback: ({ required String id, required String name, + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), - }) => GameTableCompanion.insert(id: id, name: name, rowid: rowid), + }) => GameTableCompanion.insert( + id: id, + name: name, + winnerId: winnerId, + rowid: rowid, + ), withReferenceMapper: (p0) => p0 .map((e) => (e.readTable(table), BaseReferences(db, table, e))) .toList(), @@ -1868,6 +3534,10 @@ class $AppDatabaseManager { $$GroupTableTableTableManager(_db, _db.groupTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); + $$PlayerGameTableTableTableManager get playerGameTable => + $$PlayerGameTableTableTableManager(_db, _db.playerGameTable); + $$GroupGameTableTableTableManager get groupGameTable => + $$GroupGameTableTableTableManager(_db, _db.groupGameTable); $$GameTableTableTableManager get gameTable => $$GameTableTableTableManager(_db, _db.gameTable); } diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 45bfb20..0772647 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class GameTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + TextColumn get winnerId => text().nullable()(); @override Set> get primaryKey => {id}; diff --git a/lib/data/db/tables/group_game_table.dart b/lib/data/db/tables/group_game_table.dart new file mode 100644 index 0000000..f70206f --- /dev/null +++ b/lib/data/db/tables/group_game_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; + +class GroupGameTable extends Table { + TextColumn get groupId => text().references(PlayerTable, #id)(); + TextColumn get gameId => text().references(GroupTable, #id)(); + + @override + Set> get primaryKey => {groupId, gameId}; +} diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart new file mode 100644 index 0000000..95ef4db --- /dev/null +++ b/lib/data/db/tables/player_game_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; + +class PlayerGameTable extends Table { + TextColumn get playerId => text().references(PlayerTable, #id)(); + TextColumn get gameId => text().references(GroupTable, #id)(); + + @override + Set> get primaryKey => {playerId, gameId}; +} diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 7457b54..2c09851 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -1,6 +1,18 @@ +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + class Game { final String id; final String name; + final List? players; + final Group? group; + final String? winner; - Game({required this.id, required this.name}); + Game({ + this.players, + this.group, + this.winner, + required this.id, + required this.name, + }); } -- 2.49.1 From b6700bafd9c7441de21eae4c98ba17248b742dd0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 12:03:08 +0100 Subject: [PATCH 066/563] Added on delete cascade --- lib/data/db/tables/group_game_table.dart | 6 ++++-- lib/data/db/tables/player_game_table.dart | 6 ++++-- lib/data/db/tables/player_group_table.dart | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/data/db/tables/group_game_table.dart b/lib/data/db/tables/group_game_table.dart index f70206f..6be12bc 100644 --- a/lib/data/db/tables/group_game_table.dart +++ b/lib/data/db/tables/group_game_table.dart @@ -3,8 +3,10 @@ import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class GroupGameTable extends Table { - TextColumn get groupId => text().references(PlayerTable, #id)(); - TextColumn get gameId => text().references(GroupTable, #id)(); + TextColumn get groupId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get gameId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {groupId, gameId}; diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart index 95ef4db..79b6df2 100644 --- a/lib/data/db/tables/player_game_table.dart +++ b/lib/data/db/tables/player_game_table.dart @@ -3,8 +3,10 @@ import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGameTable extends Table { - TextColumn get playerId => text().references(PlayerTable, #id)(); - TextColumn get gameId => text().references(GroupTable, #id)(); + TextColumn get playerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get gameId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {playerId, gameId}; diff --git a/lib/data/db/tables/player_group_table.dart b/lib/data/db/tables/player_group_table.dart index 61e2ed8..da2521b 100644 --- a/lib/data/db/tables/player_group_table.dart +++ b/lib/data/db/tables/player_group_table.dart @@ -3,8 +3,10 @@ import 'package:game_tracker/data/db/tables/group_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGroupTable extends Table { - TextColumn get playerId => text().references(PlayerTable, #id)(); - TextColumn get groupId => text().references(GroupTable, #id)(); + TextColumn get playerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get groupId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {playerId, groupId}; -- 2.49.1 From d07943add9ed0146f6ff611c4026cbf114052ca0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 12:04:33 +0100 Subject: [PATCH 067/563] Added methods for inserting games and groups into the db --- lib/data/dao/game_dao.dart | 21 +++++++++++++++++++++ lib/data/dao/group_dao.dart | 25 ++++++++++++++++++++----- lib/data/dao/group_game_dao.dart | 8 ++++++++ lib/data/dao/player_game_dao.dart | 8 ++++++++ lib/data/db/tables/game_table.dart | 1 + 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 5894ed5..ba9c7df 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -41,6 +41,27 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ); } + Future addGame(Game game) async { + await db.transaction(() async { + for (final p in game.players ?? []) { + await db.playerDao.addPlayer(p); + await db.playerGameDao.addPlayerToGame(game.id, p.id); + } + if (game.group != null) { + await db.groupDao.addGroup(game.group!); + await db.groupGameDao.addGroupToGame(game.id, game.group!.id); + } + await into(gameTable).insert( + GameTableCompanion.insert( + id: game.id, + name: game.name, + winnerId: Value(game.winner), + ), + mode: InsertMode.insertOrReplace, + ); + }); + } + /// Retrieves the number of games in the database. Future getGameCount() async { final count = diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index a92247a..ace60c4 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -32,11 +32,26 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } /// Adds a new group with the given [id] and [name] to the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future addGroup(String id, String name) async { - await into( - groupTable, - ).insert(GroupTableCompanion.insert(id: id, name: name)); + /// This method also adds the group's members to the [PlayerGroupTable]. + Future addGroup(Group group) async { + await db.transaction(() async { + await into( + groupTable, + ).insert(GroupTableCompanion.insert(id: group.id, name: group.name)); + await db.batch( + (b) => b.insertAll( + db.playerGroupTable, + group.members + .map( + (member) => PlayerGroupTableCompanion.insert( + playerId: member.id, + groupId: group.id, + ), + ) + .toList(), + ), + ); + }); } /// Deletes the group with the given [id] from the database. diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 851e032..b29db1c 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -30,4 +30,12 @@ class GroupGameDao extends DatabaseAccessor final group = await db.groupDao.getGroupById(result.groupId); return group; } + + /// Associates a group with a game by inserting a record into the + /// [GroupGameTable]. + Future addGroupToGame(String gameId, String groupId) async { + await into( + groupGameTable, + ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId)); + } } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index c05edd1..e597462 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -37,4 +37,12 @@ class PlayerGameDao extends DatabaseAccessor final players = await Future.wait(futures); return players.whereType().toList(); } + + /// Associates a player with a game by inserting a record into the + /// [PlayerGameTable]. + Future addPlayerToGame(String gameId, String playerId) async { + await into(playerGameTable).insert( + PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + ); + } } diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 0772647..2c42213 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class GameTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + // todo: winner id needs to be deleted when corresponding player gets deleted TextColumn get winnerId => text().nullable()(); @override -- 2.49.1 From ca40ae668dc2f4e5a32d1c0b3e0d9c950d8280d3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 13:11:48 +0100 Subject: [PATCH 068/563] Updated methods with named parameters --- lib/data/dao/game_dao.dart | 20 +++++++++-------- lib/data/dao/group_dao.dart | 15 ++++++++----- lib/data/dao/group_game_dao.dart | 6 ++--- lib/data/dao/player_dao.dart | 35 +++++++++++++++++++----------- lib/data/dao/player_game_dao.dart | 11 ++++++---- lib/data/dao/player_group_dao.dart | 14 ++++++++---- 6 files changed, 62 insertions(+), 39 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index ba9c7df..775c509 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -19,17 +19,17 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } /// Retrieves a [Game] by its [gameId]. - Future getGameById(String gameId) async { + Future getGameById({required String gameId}) async { final query = select(gameTable)..where((g) => g.id.equals(gameId)); final result = await query.getSingle(); List? players; - if (await db.playerGameDao.hasGamePlayers(gameId)) { - players = await db.playerGameDao.getPlayersByGameId(gameId); + if (await db.playerGameDao.hasGamePlayers(gameId: gameId)) { + players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); } Group? group; - if (await db.groupGameDao.hasGameGroup(gameId)) { - group = await db.groupGameDao.getGroupByGameId(gameId); + if (await db.groupGameDao.hasGameGroup(gameId: gameId)) { + group = await db.groupGameDao.getGroupByGameId(gameId: gameId); } return Game( @@ -41,14 +41,16 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ); } - Future addGame(Game game) async { + /// Adds a new [Game] to the database. + /// Also adds associated players and group if they exist. + Future addGame({required Game game}) async { await db.transaction(() async { for (final p in game.players ?? []) { - await db.playerDao.addPlayer(p); - await db.playerGameDao.addPlayerToGame(game.id, p.id); + await db.playerDao.addPlayer(player: p); + await db.playerGameDao.addPlayerToGame(gameId: game.id, playerId: p.id); } if (game.group != null) { - await db.groupDao.addGroup(game.group!); + await db.groupDao.addGroup(group: game.group!); await db.groupGameDao.addGroupToGame(game.id, game.group!.id); } await into(gameTable).insert( diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index ace60c4..d449326 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -20,12 +20,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } /// Retrieves a [Group] by its [groupId], including its members. - Future getGroupById(String groupId) async { + Future getGroupById({required String groupId}) async { final query = select(groupTable)..where((g) => g.id.equals(groupId)); final result = await query.getSingle(); List members = await db.playerGroupDao.getPlayersOfGroupById( - groupId, + groupId: groupId, ); return Group(id: result.id, name: result.name, members: members); @@ -33,7 +33,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Adds a new group with the given [id] and [name] to the database. /// This method also adds the group's members to the [PlayerGroupTable]. - Future addGroup(Group group) async { + Future addGroup({required Group group}) async { await db.transaction(() async { await into( groupTable, @@ -56,15 +56,18 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Deletes the group with the given [id] from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteGroup(String id) async { - final query = (delete(groupTable)..where((g) => g.id.equals(id))); + Future deleteGroup({required String groupId}) async { + final query = (delete(groupTable)..where((g) => g.id.equals(groupId))); final rowsAffected = await query.go(); return rowsAffected > 0; } /// Updates the name of the group with the given [id] to [newName]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateGroupname(String id, String newName) async { + Future updateGroupname({ + required String id, + required String newName, + }) async { final rowsAffected = await (update(groupTable)..where((g) => g.id.equals(id))).write( GroupTableCompanion(name: Value(newName)), diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index b29db1c..88b8fad 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -12,7 +12,7 @@ class GroupGameDao extends DatabaseAccessor /// Checks if there is a group associated with the given [gameId]. /// Returns `true` if there is a group, otherwise `false`. - Future hasGameGroup(String gameId) async { + Future hasGameGroup({required String gameId}) async { final count = await (selectOnly(groupGameTable) ..where(groupGameTable.gameId.equals(gameId)) @@ -22,12 +22,12 @@ class GroupGameDao extends DatabaseAccessor return (count ?? 0) > 0; } - Future getGroupByGameId(String gameId) async { + Future getGroupByGameId({required String gameId}) async { final result = await (select( groupGameTable, )..where((g) => g.gameId.equals(gameId))).getSingle(); - final group = await db.groupDao.getGroupById(result.groupId); + final group = await db.groupDao.getGroupById(groupId: result.groupId); return group; } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 2d3c0ab..01e7163 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -17,38 +17,47 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Retrieves a [Player] by their [id]. - Future getPlayerById(String id) async { - final query = select(playerTable)..where((p) => p.id.equals(id)); + Future getPlayerById({required String playerId}) async { + final query = select(playerTable)..where((p) => p.id.equals(playerId)); final result = await query.getSingle(); return Player(id: result.id, name: result.name); } /// Adds a new [player] to the database. - Future addPlayer(Player player) async { - await into( - playerTable, - ).insert(PlayerTableCompanion.insert(id: player.id, name: player.name)); + /// If a player with the same ID already exists, updates their name to + /// the new one. + Future addPlayer({required Player player}) async { + if (!await playerExists(playerId: player.id)) { + await into( + playerTable, + ).insert(PlayerTableCompanion.insert(id: player.id, name: player.name)); + } else { + await updatePlayername(playerId: player.id, newName: player.name); + } } /// Deletes the player with the given [id] from the database. /// Returns `true` if the player was deleted, `false` if the player did not exist. - Future deletePlayer(String id) async { - final query = delete(playerTable)..where((p) => p.id.equals(id)); + Future deletePlayer({required String playerId}) async { + final query = delete(playerTable)..where((p) => p.id.equals(playerId)); final rowsAffected = await query.go(); return rowsAffected > 0; } /// Checks if a player with the given [id] exists in the database. /// Returns `true` if the player exists, `false` otherwise. - Future playerExists(String id) async { - final query = select(playerTable)..where((p) => p.id.equals(id)); + Future playerExists({required String playerId}) async { + final query = select(playerTable)..where((p) => p.id.equals(playerId)); final result = await query.getSingleOrNull(); return result != null; } - /// Updates the name of the player with the given [id] to [newName]. - Future updatePlayername(String id, String newName) async { - await (update(playerTable)..where((p) => p.id.equals(id))).write( + /// Updates the name of the player with the given [playerId] to [newName]. + Future updatePlayername({ + required String playerId, + required String newName, + }) async { + await (update(playerTable)..where((p) => p.id.equals(playerId))).write( PlayerTableCompanion(name: Value(newName)), ); } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index e597462..333dc28 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -12,7 +12,7 @@ class PlayerGameDao extends DatabaseAccessor /// Checks if there are any players associated with the given [gameId]. /// Returns `true` if there are players, otherwise `false`. - Future hasGamePlayers(String gameId) async { + Future hasGamePlayers({required String gameId}) async { final count = await (selectOnly(playerGameTable) ..where(playerGameTable.gameId.equals(gameId)) @@ -24,7 +24,7 @@ class PlayerGameDao extends DatabaseAccessor /// Retrieves a list of [Player]s associated with the given [gameId]. /// Returns an empty list if no players are found. - Future> getPlayersByGameId(String gameId) async { + Future> getPlayersByGameId({required String gameId}) async { final result = await (select( playerGameTable, )..where((p) => p.gameId.equals(gameId))).get(); @@ -32,7 +32,7 @@ class PlayerGameDao extends DatabaseAccessor if (result.isEmpty) return []; final futures = result.map( - (row) => db.playerDao.getPlayerById(row.playerId), + (row) => db.playerDao.getPlayerById(playerId: row.playerId), ); final players = await Future.wait(futures); return players.whereType().toList(); @@ -40,7 +40,10 @@ class PlayerGameDao extends DatabaseAccessor /// Associates a player with a game by inserting a record into the /// [PlayerGameTable]. - Future addPlayerToGame(String gameId, String playerId) async { + Future addPlayerToGame({ + required String gameId, + required String playerId, + }) async { await into(playerGameTable).insert( PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), ); diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index e51c325..5b2a095 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -11,7 +11,7 @@ class PlayerGroupDao extends DatabaseAccessor PlayerGroupDao(super.db); /// Retrieves all players belonging to a specific group by [groupId]. - Future> getPlayersOfGroupById(String groupId) async { + Future> getPlayersOfGroupById({required String groupId}) async { final query = select(playerGroupTable) ..where((pG) => pG.groupId.equals(groupId)); final result = await query.get(); @@ -19,7 +19,7 @@ class PlayerGroupDao extends DatabaseAccessor List groupMembers = []; for (var entry in result) { - final player = await db.playerDao.getPlayerById(entry.playerId); + final player = await db.playerDao.getPlayerById(playerId: entry.playerId); groupMembers.add(player); } @@ -28,7 +28,10 @@ class PlayerGroupDao extends DatabaseAccessor /// Removes a player from a group based on [playerId] and [groupId]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromGroup(String playerId, String groupId) async { + Future removePlayerFromGroup({ + required String playerId, + required String groupId, + }) async { final query = delete(playerGroupTable) ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); final rowsAffected = await query.go(); @@ -36,7 +39,10 @@ class PlayerGroupDao extends DatabaseAccessor } /// Adds a player to a group with the given [playerId] and [groupId]. - Future addPlayerToGroup(String playerId, String groupId) async { + Future addPlayerToGroup({ + required String playerId, + required String groupId, + }) async { await into(playerGroupTable).insert( PlayerGroupTableCompanion.insert(playerId: playerId, groupId: groupId), ); -- 2.49.1 From ac99217b72f8c7cba43bf68bf58f6a7b9c52f776 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 13:12:26 +0100 Subject: [PATCH 069/563] Added reference on winner --- lib/data/db/tables/game_table.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 2c42213..9651a79 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -1,10 +1,11 @@ import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; class GameTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); - // todo: winner id needs to be deleted when corresponding player gets deleted - TextColumn get winnerId => text().nullable()(); + TextColumn get winnerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {id}; -- 2.49.1 From 67d5f7e8919cf1c60c5c770005300166dac89bc8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:01:31 +0100 Subject: [PATCH 070/563] Updated order --- lib/data/db/database.dart | 4 +- lib/data/db/database.g.dart | 1369 +++++++++++++++++++++-------------- 2 files changed, 817 insertions(+), 556 deletions(-) diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index b0662aa..73ad73e 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -20,14 +20,14 @@ part 'database.g.dart'; tables: [ PlayerTable, GroupTable, + GameTable, PlayerGroupTable, PlayerGameTable, GroupGameTable, - GameTable, ], daos: [ - GroupDao, PlayerDao, + GroupDao, GameDao, PlayerGroupDao, PlayerGameDao, diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 74efba1..453fcb0 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -413,6 +413,268 @@ class GroupTableCompanion extends UpdateCompanion { } } +class $GameTableTable extends GameTable + with TableInfo<$GameTableTable, GameTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GameTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _winnerIdMeta = const VerificationMeta( + 'winnerId', + ); + @override + late final GeneratedColumn winnerId = GeneratedColumn( + 'winner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES player_table (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [id, name, winnerId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'game_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('winner_id')) { + context.handle( + _winnerIdMeta, + winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), + ); + } else if (isInserting) { + context.missing(_winnerIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GameTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GameTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + winnerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}winner_id'], + )!, + ); + } + + @override + $GameTableTable createAlias(String alias) { + return $GameTableTable(attachedDatabase, alias); + } +} + +class GameTableData extends DataClass implements Insertable { + final String id; + final String name; + final String winnerId; + const GameTableData({ + required this.id, + required this.name, + required this.winnerId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['winner_id'] = Variable(winnerId); + return map; + } + + GameTableCompanion toCompanion(bool nullToAbsent) { + return GameTableCompanion( + id: Value(id), + name: Value(name), + winnerId: Value(winnerId), + ); + } + + factory GameTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GameTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + winnerId: serializer.fromJson(json['winnerId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'winnerId': serializer.toJson(winnerId), + }; + } + + GameTableData copyWith({String? id, String? name, String? winnerId}) => + GameTableData( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId ?? this.winnerId, + ); + GameTableData copyWithCompanion(GameTableCompanion data) { + return GameTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, + ); + } + + @override + String toString() { + return (StringBuffer('GameTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('winnerId: $winnerId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, winnerId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GameTableData && + other.id == this.id && + other.name == this.name && + other.winnerId == this.winnerId); +} + +class GameTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value winnerId; + final Value rowid; + const GameTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.winnerId = const Value.absent(), + this.rowid = const Value.absent(), + }); + GameTableCompanion.insert({ + required String id, + required String name, + required String winnerId, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + winnerId = Value(winnerId); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? winnerId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (winnerId != null) 'winner_id': winnerId, + if (rowid != null) 'rowid': rowid, + }); + } + + GameTableCompanion copyWith({ + Value? id, + Value? name, + Value? winnerId, + Value? rowid, + }) { + return GameTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId ?? this.winnerId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (winnerId.present) { + map['winner_id'] = Variable(winnerId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GameTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('winnerId: $winnerId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + class $PlayerGroupTableTable extends PlayerGroupTable with TableInfo<$PlayerGroupTableTable, PlayerGroupTableData> { @override @@ -430,7 +692,7 @@ class $PlayerGroupTableTable extends PlayerGroupTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id)', + 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); static const VerificationMeta _groupIdMeta = const VerificationMeta( @@ -444,7 +706,7 @@ class $PlayerGroupTableTable extends PlayerGroupTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES group_table (id)', + 'REFERENCES group_table (id) ON DELETE CASCADE', ), ); @override @@ -655,7 +917,7 @@ class $PlayerGameTableTable extends PlayerGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id)', + 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); @@ -667,7 +929,7 @@ class $PlayerGameTableTable extends PlayerGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES group_table (id)', + 'REFERENCES game_table (id) ON DELETE CASCADE', ), ); @override @@ -878,7 +1140,7 @@ class $GroupGameTableTable extends GroupGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id)', + 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); @@ -890,7 +1152,7 @@ class $GroupGameTableTable extends GroupGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES group_table (id)', + 'REFERENCES group_table (id) ON DELETE CASCADE', ), ); @override @@ -1084,270 +1346,12 @@ class GroupGameTableCompanion extends UpdateCompanion { } } -class $GameTableTable extends GameTable - with TableInfo<$GameTableTable, GameTableData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $GameTableTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _nameMeta = const VerificationMeta('name'); - @override - late final GeneratedColumn name = GeneratedColumn( - 'name', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _winnerIdMeta = const VerificationMeta( - 'winnerId', - ); - @override - late final GeneratedColumn winnerId = GeneratedColumn( - 'winner_id', - aliasedName, - true, - type: DriftSqlType.string, - requiredDuringInsert: false, - ); - @override - List get $columns => [id, name, winnerId]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'game_table'; - @override - VerificationContext validateIntegrity( - Insertable instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } else if (isInserting) { - context.missing(_idMeta); - } - if (data.containsKey('name')) { - context.handle( - _nameMeta, - name.isAcceptableOrUnknown(data['name']!, _nameMeta), - ); - } else if (isInserting) { - context.missing(_nameMeta); - } - if (data.containsKey('winner_id')) { - context.handle( - _winnerIdMeta, - winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), - ); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - GameTableData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GameTableData( - id: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}id'], - )!, - name: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}name'], - )!, - winnerId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}winner_id'], - ), - ); - } - - @override - $GameTableTable createAlias(String alias) { - return $GameTableTable(attachedDatabase, alias); - } -} - -class GameTableData extends DataClass implements Insertable { - final String id; - final String name; - final String? winnerId; - const GameTableData({required this.id, required this.name, this.winnerId}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['name'] = Variable(name); - if (!nullToAbsent || winnerId != null) { - map['winner_id'] = Variable(winnerId); - } - return map; - } - - GameTableCompanion toCompanion(bool nullToAbsent) { - return GameTableCompanion( - id: Value(id), - name: Value(name), - winnerId: winnerId == null && nullToAbsent - ? const Value.absent() - : Value(winnerId), - ); - } - - factory GameTableData.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return GameTableData( - id: serializer.fromJson(json['id']), - name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), - }; - } - - GameTableData copyWith({ - String? id, - String? name, - Value winnerId = const Value.absent(), - }) => GameTableData( - id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId.present ? winnerId.value : this.winnerId, - ); - GameTableData copyWithCompanion(GameTableCompanion data) { - return GameTableData( - id: data.id.present ? data.id.value : this.id, - name: data.name.present ? data.name.value : this.name, - winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, - ); - } - - @override - String toString() { - return (StringBuffer('GameTableData(') - ..write('id: $id, ') - ..write('name: $name, ') - ..write('winnerId: $winnerId') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, name, winnerId); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is GameTableData && - other.id == this.id && - other.name == this.name && - other.winnerId == this.winnerId); -} - -class GameTableCompanion extends UpdateCompanion { - final Value id; - final Value name; - final Value winnerId; - final Value rowid; - const GameTableCompanion({ - this.id = const Value.absent(), - this.name = const Value.absent(), - this.winnerId = const Value.absent(), - this.rowid = const Value.absent(), - }); - GameTableCompanion.insert({ - required String id, - required String name, - this.winnerId = const Value.absent(), - this.rowid = const Value.absent(), - }) : id = Value(id), - name = Value(name); - static Insertable custom({ - Expression? id, - Expression? name, - Expression? winnerId, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (name != null) 'name': name, - if (winnerId != null) 'winner_id': winnerId, - if (rowid != null) 'rowid': rowid, - }); - } - - GameTableCompanion copyWith({ - Value? id, - Value? name, - Value? winnerId, - Value? rowid, - }) { - return GameTableCompanion( - id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (name.present) { - map['name'] = Variable(name.value); - } - if (winnerId.present) { - map['winner_id'] = Variable(winnerId.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('GameTableCompanion(') - ..write('id: $id, ') - ..write('name: $name, ') - ..write('winnerId: $winnerId, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $PlayerTableTable playerTable = $PlayerTableTable(this); late final $GroupTableTable groupTable = $GroupTableTable(this); + late final $GameTableTable gameTable = $GameTableTable(this); late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); @@ -1355,7 +1359,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { this, ); late final $GroupGameTableTable groupGameTable = $GroupGameTableTable(this); - late final $GameTableTable gameTable = $GameTableTable(this); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GameDao gameDao = GameDao(this as AppDatabase); @@ -1371,11 +1374,63 @@ abstract class _$AppDatabase extends GeneratedDatabase { List get allSchemaEntities => [ playerTable, groupTable, + gameTable, playerGroupTable, playerGameTable, groupGameTable, - gameTable, ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'player_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'player_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('player_group_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'group_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('player_group_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'player_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'game_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'player_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'group_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + ), + ]); } typedef $$PlayerTableTableCreateCompanionBuilder = @@ -1395,6 +1450,24 @@ final class $$PlayerTableTableReferences extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + static MultiTypedResultKey<$GameTableTable, List> + _gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.gameTable, + aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId), + ); + + $$GameTableTableProcessedTableManager get gameTableRefs { + final manager = $$GameTableTableTableManager( + $_db, + $_db.gameTable, + ).filter((f) => f.winnerId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -1482,6 +1555,31 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + Expression gameTableRefs( + Expression Function($$GameTableTableFilterComposer f) f, + ) { + final $$GameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.winnerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableFilterComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -1593,6 +1691,31 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + Expression gameTableRefs( + Expression Function($$GameTableTableAnnotationComposer a) f, + ) { + final $$GameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.winnerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableAnnotationComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -1683,6 +1806,7 @@ class $$PlayerTableTableTableManager (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ + bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, bool groupGameTableRefs, @@ -1722,6 +1846,7 @@ class $$PlayerTableTableTableManager .toList(), prefetchHooksCallback: ({ + gameTableRefs = false, playerGroupTableRefs = false, playerGameTableRefs = false, groupGameTableRefs = false, @@ -1729,6 +1854,7 @@ class $$PlayerTableTableTableManager return PrefetchHooks( db: db, explicitlyWatchedTables: [ + if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, @@ -1736,6 +1862,27 @@ class $$PlayerTableTableTableManager addJoins: null, getPrefetchedDataCallback: (items) async { return [ + if (gameTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + GameTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._gameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).gameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.winnerId == item.id, + ), + typedResults: items, + ), if (playerGroupTableRefs) await $_getPrefetchedData< PlayerTableData, @@ -1820,6 +1967,7 @@ typedef $$PlayerTableTableProcessedTableManager = (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ + bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, bool groupGameTableRefs, @@ -1865,29 +2013,6 @@ final class $$GroupTableTableReferences ); } - static MultiTypedResultKey<$PlayerGameTableTable, List> - _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.playerGameTable, - aliasName: $_aliasNameGenerator( - db.groupTable.id, - db.playerGameTable.gameId, - ), - ); - - $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { - final manager = $$PlayerGameTableTableTableManager( - $_db, - $_db.playerGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull( - _playerGameTableRefsTable($_db), - ); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } - static MultiTypedResultKey<$GroupGameTableTable, List> _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.groupGameTable, @@ -1951,31 +2076,6 @@ class $$GroupTableTableFilterComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableFilterComposer f) f, - ) { - final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableFilterComposer( - $db: $db, - $table: $db.playerGameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression groupGameTableRefs( Expression Function($$GroupGameTableTableFilterComposer f) f, ) { @@ -2062,31 +2162,6 @@ class $$GroupTableTableAnnotationComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableAnnotationComposer a) f, - ) { - final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableAnnotationComposer( - $db: $db, - $table: $db.playerGameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression groupGameTableRefs( Expression Function($$GroupGameTableTableAnnotationComposer a) f, ) { @@ -2128,7 +2203,6 @@ class $$GroupTableTableTableManager GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, bool groupGameTableRefs, }) > { @@ -2165,16 +2239,11 @@ class $$GroupTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - playerGroupTableRefs = false, - playerGameTableRefs = false, - groupGameTableRefs = false, - }) { + ({playerGroupTableRefs = false, groupGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, - if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, ], addJoins: null, @@ -2201,27 +2270,6 @@ class $$GroupTableTableTableManager ), typedResults: items, ), - if (playerGameTableRefs) - await $_getPrefetchedData< - GroupTableData, - $GroupTableTable, - PlayerGameTableData - >( - currentTable: table, - referencedTable: $$GroupTableTableReferences - ._playerGameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$GroupTableTableReferences( - db, - table, - p0, - ).playerGameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, - ), - typedResults: items, - ), if (groupGameTableRefs) await $_getPrefetchedData< GroupTableData, @@ -2265,10 +2313,385 @@ typedef $$GroupTableTableProcessedTableManager = GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, bool groupGameTableRefs, }) >; +typedef $$GameTableTableCreateCompanionBuilder = + GameTableCompanion Function({ + required String id, + required String name, + required String winnerId, + Value rowid, + }); +typedef $$GameTableTableUpdateCompanionBuilder = + GameTableCompanion Function({ + Value id, + Value name, + Value winnerId, + Value rowid, + }); + +final class $$GameTableTableReferences + extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { + $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $PlayerTableTable _winnerIdTable(_$AppDatabase db) => + db.playerTable.createAlias( + $_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id), + ); + + $$PlayerTableTableProcessedTableManager get winnerId { + final $_column = $_itemColumn('winner_id')!; + + final manager = $$PlayerTableTableTableManager( + $_db, + $_db.playerTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_winnerIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static MultiTypedResultKey<$PlayerGameTableTable, List> + _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerGameTable, + aliasName: $_aliasNameGenerator(db.gameTable.id, db.playerGameTable.gameId), + ); + + $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { + final manager = $$PlayerGameTableTableTableManager( + $_db, + $_db.playerGameTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerGameTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$GameTableTableFilterComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + $$PlayerTableTableFilterComposer get winnerId { + final $$PlayerTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.winnerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableFilterComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableFilterComposer f) f, + ) { + final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableFilterComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GameTableTableOrderingComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + $$PlayerTableTableOrderingComposer get winnerId { + final $$PlayerTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.winnerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableOrderingComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$GameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + $$PlayerTableTableAnnotationComposer get winnerId { + final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.winnerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableAnnotationComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + Expression playerGameTableRefs( + Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + ) { + final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerGameTableTableAnnotationComposer( + $db: $db, + $table: $db.playerGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + (GameTableData, $$GameTableTableReferences), + GameTableData, + PrefetchHooks Function({bool winnerId, bool playerGameTableRefs}) + > { + $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GameTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value winnerId = const Value.absent(), + Value rowid = const Value.absent(), + }) => GameTableCompanion( + id: id, + name: name, + winnerId: winnerId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String winnerId, + Value rowid = const Value.absent(), + }) => GameTableCompanion.insert( + id: id, + name: name, + winnerId: winnerId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$GameTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({winnerId = false, playerGameTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerGameTableRefs) db.playerGameTable, + ], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (winnerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.winnerId, + referencedTable: $$GameTableTableReferences + ._winnerIdTable(db), + referencedColumn: $$GameTableTableReferences + ._winnerIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return [ + if (playerGameTableRefs) + await $_getPrefetchedData< + GameTableData, + $GameTableTable, + PlayerGameTableData + >( + currentTable: table, + referencedTable: $$GameTableTableReferences + ._playerGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GameTableTableReferences( + db, + table, + p0, + ).playerGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$GameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + (GameTableData, $$GameTableTableReferences), + GameTableData, + PrefetchHooks Function({bool winnerId, bool playerGameTableRefs}) + >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = PlayerGroupTableCompanion Function({ required String playerId, @@ -2679,17 +3102,17 @@ final class $$PlayerGameTableTableReferences ); } - static $GroupTableTable _gameIdTable(_$AppDatabase db) => - db.groupTable.createAlias( - $_aliasNameGenerator(db.playerGameTable.gameId, db.groupTable.id), + static $GameTableTable _gameIdTable(_$AppDatabase db) => + db.gameTable.createAlias( + $_aliasNameGenerator(db.playerGameTable.gameId, db.gameTable.id), ); - $$GroupTableTableProcessedTableManager get gameId { + $$GameTableTableProcessedTableManager get gameId { final $_column = $_itemColumn('game_id')!; - final manager = $$GroupTableTableTableManager( + final manager = $$GameTableTableTableManager( $_db, - $_db.groupTable, + $_db.gameTable, ).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); if (item == null) return manager; @@ -2731,20 +3154,20 @@ class $$PlayerGameTableTableFilterComposer return composer; } - $$GroupTableTableFilterComposer get gameId { - final $$GroupTableTableFilterComposer composer = $composerBuilder( + $$GameTableTableFilterComposer get gameId { + final $$GameTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.gameId, - referencedTable: $db.groupTable, + referencedTable: $db.gameTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableFilterComposer( + }) => $$GameTableTableFilterComposer( $db: $db, - $table: $db.groupTable, + $table: $db.gameTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2787,20 +3210,20 @@ class $$PlayerGameTableTableOrderingComposer return composer; } - $$GroupTableTableOrderingComposer get gameId { - final $$GroupTableTableOrderingComposer composer = $composerBuilder( + $$GameTableTableOrderingComposer get gameId { + final $$GameTableTableOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.gameId, - referencedTable: $db.groupTable, + referencedTable: $db.gameTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableOrderingComposer( + }) => $$GameTableTableOrderingComposer( $db: $db, - $table: $db.groupTable, + $table: $db.gameTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2843,20 +3266,20 @@ class $$PlayerGameTableTableAnnotationComposer return composer; } - $$GroupTableTableAnnotationComposer get gameId { - final $$GroupTableTableAnnotationComposer composer = $composerBuilder( + $$GameTableTableAnnotationComposer get gameId { + final $$GameTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.gameId, - referencedTable: $db.groupTable, + referencedTable: $db.gameTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableAnnotationComposer( + }) => $$GameTableTableAnnotationComposer( $db: $db, - $table: $db.groupTable, + $table: $db.gameTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3362,168 +3785,6 @@ typedef $$GroupGameTableTableProcessedTableManager = GroupGameTableData, PrefetchHooks Function({bool groupId, bool gameId}) >; -typedef $$GameTableTableCreateCompanionBuilder = - GameTableCompanion Function({ - required String id, - required String name, - Value winnerId, - Value rowid, - }); -typedef $$GameTableTableUpdateCompanionBuilder = - GameTableCompanion Function({ - Value id, - Value name, - Value winnerId, - Value rowid, - }); - -class $$GameTableTableFilterComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get winnerId => $composableBuilder( - column: $table.winnerId, - builder: (column) => ColumnFilters(column), - ); -} - -class $$GameTableTableOrderingComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get name => $composableBuilder( - column: $table.name, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get winnerId => $composableBuilder( - column: $table.winnerId, - builder: (column) => ColumnOrderings(column), - ); -} - -class $$GameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); - - GeneratedColumn get winnerId => - $composableBuilder(column: $table.winnerId, builder: (column) => column); -} - -class $$GameTableTableTableManager - extends - RootTableManager< - _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - ( - GameTableData, - BaseReferences<_$AppDatabase, $GameTableTable, GameTableData>, - ), - GameTableData, - PrefetchHooks Function() - > { - $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$GameTableTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$GameTableTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$GameTableTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value id = const Value.absent(), - Value name = const Value.absent(), - Value winnerId = const Value.absent(), - Value rowid = const Value.absent(), - }) => GameTableCompanion( - id: id, - name: name, - winnerId: winnerId, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String id, - required String name, - Value winnerId = const Value.absent(), - Value rowid = const Value.absent(), - }) => GameTableCompanion.insert( - id: id, - name: name, - winnerId: winnerId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), - prefetchHooksCallback: null, - ), - ); -} - -typedef $$GameTableTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - ( - GameTableData, - BaseReferences<_$AppDatabase, $GameTableTable, GameTableData>, - ), - GameTableData, - PrefetchHooks Function() - >; class $AppDatabaseManager { final _$AppDatabase _db; @@ -3532,12 +3793,12 @@ class $AppDatabaseManager { $$PlayerTableTableTableManager(_db, _db.playerTable); $$GroupTableTableTableManager get groupTable => $$GroupTableTableTableManager(_db, _db.groupTable); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db, _db.gameTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); $$PlayerGameTableTableTableManager get playerGameTable => $$PlayerGameTableTableTableManager(_db, _db.playerGameTable); $$GroupGameTableTableTableManager get groupGameTable => $$GroupGameTableTableTableManager(_db, _db.groupGameTable); - $$GameTableTableTableManager get gameTable => - $$GameTableTableTableManager(_db, _db.gameTable); } -- 2.49.1 From 3f5a8406753867a8564fefbb7b771ecbbeb53b85 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:01:44 +0100 Subject: [PATCH 071/563] Added toString methods --- lib/data/dto/game.dart | 9 +++++++-- lib/data/dto/group.dart | 5 +++++ lib/data/dto/player.dart | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 2c09851..125af48 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -6,13 +6,18 @@ class Game { final String name; final List? players; final Group? group; - final String? winner; + final String winner; Game({ this.players, this.group, - this.winner, + this.winner = '', required this.id, required this.name, }); + + @override + String toString() { + return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; + } } diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 6f12fe9..427a52b 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -6,4 +6,9 @@ class Group { final List members; Group({required this.id, required this.name, required this.members}); + + @override + String toString() { + return 'Group{id: $id, name: $name,members: $members}'; + } } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index d5e84e8..631a51f 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -3,4 +3,9 @@ class Player { final String name; Player({required this.id, required this.name}); + + @override + String toString() { + return 'Player{id: $id,name: $name}'; + } } -- 2.49.1 From 93a4ccaee0d924d3c933715f0229d46440805505 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:02:01 +0100 Subject: [PATCH 072/563] Added missing methods and implemented tests --- lib/data/dao/game_dao.dart | 18 ++- lib/data/dao/game_dao.g.dart | 1 + lib/data/dao/group_dao.dart | 15 ++- lib/data/dao/player_game_dao.g.dart | 2 +- lib/data/dao/player_group_dao.dart | 37 +++++- lib/data/db/tables/player_game_table.dart | 4 +- lib/main.dart | 30 +++++ test/db_tests/game_test.dart | 111 ++++++++++++++++++ test/db_tests/group_test.dart | 130 ++++++++++++++++++++++ test/db_tests/player_test.dart | 66 +++++++++++ test/widget_test.dart | 29 ----- 11 files changed, 402 insertions(+), 41 deletions(-) create mode 100644 test/db_tests/game_test.dart create mode 100644 test/db_tests/group_test.dart create mode 100644 test/db_tests/player_test.dart delete mode 100644 test/widget_test.dart diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 775c509..9379623 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -57,13 +57,21 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: Value(game.winner), + winnerId: game.winner, ), mode: InsertMode.insertOrReplace, ); }); } + /// Deletes the game with the given [gameId] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteGame({required String gameId}) async { + final query = delete(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + /// Retrieves the number of games in the database. Future getGameCount() async { final count = @@ -72,4 +80,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { .getSingle(); return count ?? 0; } + + /// Checks if a game with the given [gameId] exists in the database. + /// Returns `true` if the game exists, otherwise `false`. + Future gameExists({required String gameId}) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); + final result = await query.getSingleOrNull(); + return result != null; + } } diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart index b5a29fe..ebf5524 100644 --- a/lib/data/dao/game_dao.g.dart +++ b/lib/data/dao/game_dao.g.dart @@ -4,5 +4,6 @@ part of 'game_dao.dart'; // ignore_for_file: type=lint mixin _$GameDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index d449326..1f0e2c8 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -51,6 +51,9 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .toList(), ), ); + await Future.wait( + group.members.map((player) => db.playerDao.addPlayer(player: player)), + ); }); } @@ -65,11 +68,11 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Updates the name of the group with the given [id] to [newName]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateGroupname({ - required String id, + required String groupId, required String newName, }) async { final rowsAffected = - await (update(groupTable)..where((g) => g.id.equals(id))).write( + await (update(groupTable)..where((g) => g.id.equals(groupId))).write( GroupTableCompanion(name: Value(newName)), ); return rowsAffected > 0; @@ -83,4 +86,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .getSingle(); return count ?? 0; } + + /// Checks if a group with the given [groupId] exists in the database. + /// Returns `true` if the group exists, `false` otherwise. + Future groupExists({required String groupId}) async { + final query = select(groupTable)..where((g) => g.id.equals(groupId)); + final result = await query.getSingleOrNull(); + return result != null; + } } diff --git a/lib/data/dao/player_game_dao.g.dart b/lib/data/dao/player_game_dao.g.dart index 0d5f5e1..4d0a192 100644 --- a/lib/data/dao/player_game_dao.g.dart +++ b/lib/data/dao/player_game_dao.g.dart @@ -5,6 +5,6 @@ part of 'player_game_dao.dart'; // ignore_for_file: type=lint mixin _$PlayerGameDaoMixin on DatabaseAccessor { $PlayerTableTable get playerTable => attachedDatabase.playerTable; - $GroupTableTable get groupTable => attachedDatabase.groupTable; + $GameTableTable get gameTable => attachedDatabase.gameTable; $PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable; } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 5b2a095..fe067ae 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -16,7 +16,7 @@ class PlayerGroupDao extends DatabaseAccessor ..where((pG) => pG.groupId.equals(groupId)); final result = await query.get(); - List groupMembers = []; + List groupMembers = List.empty(growable: true); for (var entry in result) { final player = await db.playerDao.getPlayerById(playerId: entry.playerId); @@ -38,13 +38,38 @@ class PlayerGroupDao extends DatabaseAccessor return rowsAffected > 0; } - /// Adds a player to a group with the given [playerId] and [groupId]. - Future addPlayerToGroup({ + /// Adds a [player] to a group with the given [groupId]. + /// If the player is already in the group, no action is taken. + /// If the player does not exist in the player table, they are added. + /// Returns `true` if the player was added, otherwise `false`. + Future addPlayerToGroup({ + required Player player, + required String groupId, + }) async { + if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { + return false; + } + + if (await db.playerDao.playerExists(playerId: player.id) == false) { + db.playerDao.addPlayer(player: player); + } + + await into(playerGroupTable).insert( + PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + ); + + return true; + } + + /// Checks if a player with [playerId] is in the group with [groupId]. + /// Returns `true` if the player is in the group, otherwise `false`. + Future isPlayerInGroup({ required String playerId, required String groupId, }) async { - await into(playerGroupTable).insert( - PlayerGroupTableCompanion.insert(playerId: playerId, groupId: groupId), - ); + final query = select(playerGroupTable) + ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); + final result = await query.getSingleOrNull(); + return result != null; } } diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart index 79b6df2..74c36fe 100644 --- a/lib/data/db/tables/player_game_table.dart +++ b/lib/data/db/tables/player_game_table.dart @@ -1,12 +1,12 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/game_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGameTable extends Table { TextColumn get playerId => text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get gameId => - text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + text().references(GameTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {playerId, gameId}; diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..5aa9cfc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -19,6 +22,8 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + dbCheck(context); + return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', @@ -39,4 +44,29 @@ class GameTracker extends StatelessWidget { home: const CustomNavigationBar(), ); } + + Future dbCheck(BuildContext context) async { + Player player1 = Player(id: 'p1', name: 'Alice'); + Player player2 = Player(id: 'p2', name: 'Bob'); + Player player3 = Player(id: 'p3', name: 'Charlie'); + Player player4 = Player(id: 'p4', name: 'Diana'); + Group testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + Game testgame = Game( + id: 'ga1', + name: 'Test Game', + winner: player1.id, + players: [player4], + group: testgroup, + ); + + final db = Provider.of(context, listen: false); + //await db.gameDao.addGame(game: testgame); + print('Game added: ${testgame.name}'); + final game = await db.gameDao.getGameById(gameId: testgame.id); + print(game.toString()); + } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart new file mode 100644 index 0000000..fae8fc5 --- /dev/null +++ b/test/db_tests/game_test.dart @@ -0,0 +1,111 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + late Game testgame; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + testPlayer = Player(id: 'test_id', name: 'Test Player'); + player1 = Player(id: 'p1', name: 'Alice'); + player2 = Player(id: 'p2', name: 'Bob'); + player3 = Player(id: 'p3', name: 'Charlie'); + player4 = Player(id: 'p4', name: 'Diana'); + testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + testgame = Game( + id: 'ga1', + name: 'Test Game', + group: testgroup, + players: [player4], + ); + }); + tearDown(() async { + await database.close(); + }); + + group('game tests', () { + test('game is added correctly', () async { + await database.gameDao.addGame(game: testgame); + + final result = await database.gameDao.getGameById(gameId: testgame.id); + + expect(result.id, testgame.id); + expect(result.name, testgame.name); + expect(result.winner, testgame.winner); + + if (result.group != null) { + expect(result.group!.members.length, testgroup.members.length); + + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.group!.members[i].id, testgroup.members[i].id); + expect(result.group!.members[i].name, testgroup.members[i].name); + } + } else { + fail('Group is null'); + } + if (result.players != null) { + expect(result.players!.length, testgame.players!.length); + + for (int i = 0; i < testgame.players!.length; i++) { + expect(result.players![i].id, testgame.players![i].id); + expect(result.players![i].name, testgame.players![i].name); + } + } else { + fail('Players is null'); + } + }); + + test('game is deleted correctly', () async { + await database.gameDao.addGame(game: testgame); + + final gameDeleted = await database.gameDao.deleteGame( + gameId: testgame.id, + ); + expect(gameDeleted, true); + + final gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, false); + }); + + test('get game count works correctly', () async { + final initialCount = await database.gameDao.getGameCount(); + expect(initialCount, 0); + + await database.gameDao.addGame(game: testgame); + + final gameAdded = await database.gameDao.getGameCount(); + expect(gameAdded, 1); + + final gameRemoved = await database.gameDao.deleteGame( + gameId: testgame.id, + ); + expect(gameRemoved, true); + + final finalCount = await database.gameDao.getGameCount(); + expect(finalCount, 0); + }); + }); +} diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart new file mode 100644 index 0000000..cdf514f --- /dev/null +++ b/test/db_tests/group_test.dart @@ -0,0 +1,130 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + player1 = Player(id: 'p1', name: 'Alice'); + player2 = Player(id: 'p2', name: 'Bob'); + player3 = Player(id: 'p3', name: 'Charlie'); + player4 = Player(id: 'p4', name: 'Diana'); + testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + }); + tearDown(() async { + await database.close(); + }); + + test('group and group members gets added correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + + expect(result.id, testgroup.id); + expect(result.name, testgroup.name); + + expect(result.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.members[i].id, testgroup.members[i].id); + expect(result.members[i].name, testgroup.members[i].name); + } + }); + + test('group gets deleted correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final groupDeleted = await database.groupDao.deleteGroup( + groupId: testgroup.id, + ); + expect(groupDeleted, true); + + final groupExists = await database.groupDao.groupExists( + groupId: testgroup.id, + ); + expect(groupExists, false); + }); + + test('group name gets updated correcly ', () async { + await database.groupDao.addGroup(group: testgroup); + + const newGroupName = 'new group name'; + + await database.groupDao.updateGroupname( + groupId: testgroup.id, + newName: newGroupName, + ); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.name, newGroupName); + }); + + test('Adding player to group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + await database.playerGroupDao.addPlayerToGroup( + player: player4, + groupId: testgroup.id, + ); + + final playerAdded = await database.playerGroupDao.isPlayerInGroup( + playerId: player4.id, + groupId: testgroup.id, + ); + + expect(playerAdded, true); + + final playerAdded2 = await database.playerGroupDao.isPlayerInGroup( + playerId: 'a', + groupId: testgroup.id, + ); + + expect(playerAdded2, false); + + expect(playerAdded, true); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.members.length, testgroup.members.length + 1); + + final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); + expect(addedPlayer.name, player4.name); + }); + + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final playerToRemove = testgroup.members[0]; + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testgroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.members.length, testgroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); +} diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart new file mode 100644 index 0000000..a53f2cc --- /dev/null +++ b/test/db_tests/player_test.dart @@ -0,0 +1,66 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + testPlayer = Player(id: 'test_id', name: 'Test Player'); + }); + tearDown(() async { + await database.close(); + }); + + group('player tests', () { + test('players get inserted correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); + + expect(result.id, testPlayer.id); + expect(result.name, testPlayer.name); + }); + + test('players get deleted correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + final playerDeleted = await database.playerDao.deletePlayer( + playerId: testPlayer.id, + ); + expect(playerDeleted, true); + + final playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, false); + }); + + test('player name gets updated correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + + const newPlayerName = 'new player name'; + + await database.playerDao.updatePlayername( + playerId: testPlayer.id, + newName: newPlayerName, + ); + + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); + expect(result.name, newPlayerName); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index cb44eec..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const GameTracker()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} -- 2.49.1 From 39b20681216ac3224795395c35cabce0f91d0040 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:02:18 +0100 Subject: [PATCH 073/563] Removed unused var --- test/db_tests/game_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index fae8fc5..bee3ff8 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -8,7 +8,6 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player testPlayer; late Player player1; late Player player2; late Player player3; @@ -25,7 +24,6 @@ void main() { ), ); - testPlayer = Player(id: 'test_id', name: 'Test Player'); player1 = Player(id: 'p1', name: 'Alice'); player2 = Player(id: 'p2', name: 'Bob'); player3 = Player(id: 'p3', name: 'Charlie'); -- 2.49.1 From 9229f1f0a5b2a86772c4a8d1734c1695b1a23601 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:09:17 +0100 Subject: [PATCH 074/563] Added counting methods + testing those --- lib/data/dao/player_dao.dart | 9 +++++++++ test/db_tests/group_test.dart | 18 ++++++++++++++++++ test/db_tests/player_test.dart | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 01e7163..976d4b0 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -61,4 +61,13 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { PlayerTableCompanion(name: Value(newName)), ); } + + /// Retrieves the total count of players in the database. + Future getPlayerCount() async { + final count = + await (selectOnly(playerTable)..addColumns([playerTable.id.count()])) + .map((row) => row.read(playerTable.id.count())) + .getSingle(); + return count ?? 0; + } } diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index cdf514f..a8fbcd5 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -127,4 +127,22 @@ void main() { final playerExists = result.members.any((p) => p.id == playerToRemove.id); expect(playerExists, false); }); + + test('get group count works correctly', () async { + final initialCount = await database.groupDao.getGroupCount(); + expect(initialCount, 0); + + await database.groupDao.addGroup(group: testgroup); + + final groupAdded = await database.groupDao.getGroupCount(); + expect(groupAdded, 1); + + final groupRemoved = await database.groupDao.deleteGroup( + groupId: testgroup.id, + ); + expect(groupRemoved, true); + + final finalCount = await database.groupDao.getGroupCount(); + expect(finalCount, 0); + }); } diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index a53f2cc..7fb9152 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -62,5 +62,23 @@ void main() { ); expect(result.name, newPlayerName); }); + + test('get player count works correctly', () async { + final initialCount = await database.playerDao.getPlayerCount(); + expect(initialCount, 0); + + await database.playerDao.addPlayer(player: testPlayer); + + final playerAdded = await database.playerDao.getPlayerCount(); + expect(playerAdded, 1); + + final playerRemoved = await database.playerDao.deletePlayer( + playerId: testPlayer.id, + ); + expect(playerRemoved, true); + + final finalCount = await database.playerDao.getPlayerCount(); + expect(finalCount, 0); + }); }); } -- 2.49.1 From 2179331455c2b08b4b451d016cac61fec06bba68 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 14 Nov 2025 09:01:19 +0100 Subject: [PATCH 075/563] Removed test code --- lib/main.dart | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5aa9cfc..98c40f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -22,8 +19,6 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { - dbCheck(context); - return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', @@ -44,29 +39,4 @@ class GameTracker extends StatelessWidget { home: const CustomNavigationBar(), ); } - - Future dbCheck(BuildContext context) async { - Player player1 = Player(id: 'p1', name: 'Alice'); - Player player2 = Player(id: 'p2', name: 'Bob'); - Player player3 = Player(id: 'p3', name: 'Charlie'); - Player player4 = Player(id: 'p4', name: 'Diana'); - Group testgroup = Group( - id: 'gr1', - name: 'Test Group', - members: [player1, player2, player3], - ); - Game testgame = Game( - id: 'ga1', - name: 'Test Game', - winner: player1.id, - players: [player4], - group: testgroup, - ); - - final db = Provider.of(context, listen: false); - //await db.gameDao.addGame(game: testgame); - print('Game added: ${testgame.name}'); - final game = await db.gameDao.getGameById(gameId: testgame.id); - print(game.toString()); - } } -- 2.49.1 From 3f0adb4c05a5a5ca4bdc90a836125a2d8b501ffa Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:20:27 +0100 Subject: [PATCH 076/563] use GameTile to display game history --- .../views/main_menu/game_history_view.dart | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index de75ae6..b840d70 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/game_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -182,16 +182,21 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { } else if (suggestedGameData.isEmpty) { return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); } - return ListView.builder( + return ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), itemCount: suggestedGameData.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; - return doubleRowInfoTile( - currentGame['game'] + ": ", - currentGame['title'], - currentGame['players'].toString() + " Spieler", - currentGame['group'], - currentGame['date'], + return GameTile( + gameTitle: currentGame['title'], + gameType: currentGame['game'], + ruleset: currentGame['date'], + players: '${currentGame['players']} Spieler', + winner: currentGame['group'], ); }, ); -- 2.49.1 From 1536e2b2af72bd80a40cd31009ff4431429c0cc6 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:31:50 +0100 Subject: [PATCH 077/563] move navbar up --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 20ced7a..3f6a82a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -55,7 +55,7 @@ class _CustomNavigationBarState extends State body: tabs[currentIndex], extendBody: true, bottomNavigationBar: Padding( - padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 8.0), + padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 18.0), child: Material( elevation: 10, borderRadius: BorderRadius.circular(24), -- 2.49.1 From 2ee4d8e6fa3dd36de8810703c6d09c44d0fdf427 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 14 Nov 2025 20:58:01 +0100 Subject: [PATCH 078/563] implemented basic layout & functionality for add group button & group tiles --- .../views/main_menu/groups_view.dart | 82 ++++++++++++++++++- .../widgets/full_width_button.dart | 29 +++++++ lib/presentation/widgets/group_tile.dart | 56 +++++++++++++ .../widgets/top_centered_message.dart | 29 ++++--- 4 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 lib/presentation/widgets/full_width_button.dart create mode 100644 lib/presentation/widgets/group_tile.dart diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 485d516..a3e66d3 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -1,10 +1,86 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +//import 'package:game_tracker/data/dto/group.dart'; +//import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/group_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -class GroupsView extends StatelessWidget { +class Group { + final String id; + final String name; + final List members; + + Group({required this.id, required this.name, required this.members}); +} + +class Player { + final String id; + final String name; + + Player({required this.id, required this.name}); +} + +class GroupsView extends StatefulWidget { const GroupsView({super.key}); + @override + State createState() => _GroupsViewState(); +} + +class _GroupsViewState extends State { + Future> _getMockGroups() async { + await Future.delayed(const Duration(seconds: 1)); + final player1 = Player(id: 'p1', name: 'Felix'); + final player2 = Player(id: 'p2', name: 'Yannick'); + final player3 = Player(id: 'p3', name: 'Mathis'); + final player4 = Player(id: 'p4', name: 'Petrus'); + + return [ + Group( + id: 'g1', + name: 'Weekend Warriors', + members: [player1, player2, player4], + ), + Group(id: 'g2', name: 'Strategy Masters', members: [player3, player4]), + Group( + id: 'g3', + name: 'The Cardboard Crew', + members: [player1, player2, player3, player4], + ), + Group(id: 'g4', name: 'The Group', members: [player1, player3, player4]), + ]; + } + @override Widget build(BuildContext context) { - return const Center(child: Text('Groups View')); + return Column( + children: [ + FutureBuilder( + future: _getMockGroups(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center( + child: TopCenteredMessage( + message: "Data not yet available, show sceleton", + ), + ); + } else if (snapshot.hasError) { + return Center( + child: TopCenteredMessage( + message: "Error while loading group data.", + ), + ); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return Center( + child: TopCenteredMessage(message: "No groups created yet."), + ); + } + return GroupTile(group: snapshot.data![0]); + //return ListView.builder() + }, + ), + FullWidthButton(text: "Create Group", onPressed: () {}), + ], + ); } } diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart new file mode 100644 index 0000000..6df700b --- /dev/null +++ b/lib/presentation/widgets/full_width_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class FullWidthButton extends StatelessWidget { + const FullWidthButton({super.key, required this.text, this.onPressed}); + + final String text; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + minimumSize: Size(MediaQuery.of(context).size.width * 0.8, 60), + backgroundColor: CustomTheme.primaryColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + child: Text( + text, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 22, + color: Colors.white, + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart new file mode 100644 index 0000000..5959f04 --- /dev/null +++ b/lib/presentation/widgets/group_tile.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +//import 'package:game_tracker/data/dto/group.dart'; + +class Group { + final String id; + final String name; + final List members; + + Group({required this.id, required this.name, required this.members}); +} + +class Player { + final String id; + final String name; + + Player({required this.id, required this.name}); +} + +class GroupTile extends StatelessWidget { + const GroupTile({super.key, required this.group}); + + final Group group; + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("${group.name}", overflow: TextOverflow.ellipsis), + Text("${group.members.length}"), + Icon(Icons.group), + ], + ), + Wrap( + spacing: 8.0, + runSpacing: -4.0, + children: [ + for (var member in group.members) + Container( + color: Colors.grey, + padding: const EdgeInsets.all(4.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Text(member.name), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index 6fe34ff..df8dcb1 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; -Widget TopCenteredMessage(String message) { - return Container( - padding: EdgeInsets.only(top: 100), - margin: EdgeInsets.only(left: 10, right: 10), - alignment: Alignment.topCenter, - child: Text( - "$message", - style: TextStyle(fontSize: 20), - textAlign: TextAlign.center, - ), - ); +class TopCenteredMessage extends StatelessWidget { + const TopCenteredMessage({super.key, required this.message}); + + final String message; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(top: 100), + margin: const EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.topCenter, + child: Text( + message, + style: const TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ); + } } -- 2.49.1 From cbd5e1d0ba0a7a3b9fce2691e558ba974f706b53 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 14 Nov 2025 22:30:56 +0100 Subject: [PATCH 079/563] Added documentation --- lib/data/dao/group_game_dao.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 88b8fad..d3b30ca 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -22,6 +22,7 @@ class GroupGameDao extends DatabaseAccessor return (count ?? 0) > 0; } + /// Retrieves the [Group] associated with the given [gameId]. Future getGroupByGameId({required String gameId}) async { final result = await (select( groupGameTable, -- 2.49.1 From 7be86b3d9e77b1b3f1cbfb570ff0db33d99553e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 14 Nov 2025 22:31:30 +0100 Subject: [PATCH 080/563] Changed method name --- lib/data/dao/game_dao.dart | 2 +- lib/data/dao/player_game_dao.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 9379623..fc931ad 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -24,7 +24,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final result = await query.getSingle(); List? players; - if (await db.playerGameDao.hasGamePlayers(gameId: gameId)) { + if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) { players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); } Group? group; diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 333dc28..8f367f8 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -12,7 +12,7 @@ class PlayerGameDao extends DatabaseAccessor /// Checks if there are any players associated with the given [gameId]. /// Returns `true` if there are players, otherwise `false`. - Future hasGamePlayers({required String gameId}) async { + Future gameHasPlayers({required String gameId}) async { final count = await (selectOnly(playerGameTable) ..where(playerGameTable.gameId.equals(gameId)) -- 2.49.1 From 34bf6740adce09c4b8f7019193471f26555f3251 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 14 Nov 2025 22:33:22 +0100 Subject: [PATCH 081/563] Corrected GroupGameTable attributes --- lib/data/dao/group_game_dao.g.dart | 3 +- lib/data/db/database.g.dart | 401 ++++++++++++----------- lib/data/db/tables/group_game_table.dart | 6 +- 3 files changed, 210 insertions(+), 200 deletions(-) diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart index f1aa46f..426f192 100644 --- a/lib/data/dao/group_game_dao.g.dart +++ b/lib/data/dao/group_game_dao.g.dart @@ -4,7 +4,8 @@ part of 'group_game_dao.dart'; // ignore_for_file: type=lint mixin _$GroupGameDaoMixin on DatabaseAccessor { - $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GroupTableTable get groupTable => attachedDatabase.groupTable; + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $GameTableTable get gameTable => attachedDatabase.gameTable; $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; } diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 453fcb0..03b7a10 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -1140,7 +1140,7 @@ class $GroupGameTableTable extends GroupGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id) ON DELETE CASCADE', + 'REFERENCES group_table (id) ON DELETE CASCADE', ), ); static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); @@ -1152,7 +1152,7 @@ class $GroupGameTableTable extends GroupGameTable type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES group_table (id) ON DELETE CASCADE', + 'REFERENCES game_table (id) ON DELETE CASCADE', ), ); @override @@ -1359,8 +1359,8 @@ abstract class _$AppDatabase extends GeneratedDatabase { this, ); late final $GroupGameTableTable groupGameTable = $GroupGameTableTable(this); - late final GroupDao groupDao = GroupDao(this as AppDatabase); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); + late final GroupDao groupDao = GroupDao(this as AppDatabase); late final GameDao gameDao = GameDao(this as AppDatabase); late final PlayerGroupDao playerGroupDao = PlayerGroupDao( this as AppDatabase, @@ -1418,14 +1418,14 @@ abstract class _$AppDatabase extends GeneratedDatabase { ), WritePropagation( on: TableUpdateQuery.onTableName( - 'player_table', + 'group_table', limitUpdateKind: UpdateKind.delete, ), result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( - 'group_table', + 'game_table', limitUpdateKind: UpdateKind.delete, ), result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], @@ -1513,27 +1513,6 @@ final class $$PlayerTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } - - static MultiTypedResultKey<$GroupGameTableTable, List> - _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupGameTable, - aliasName: $_aliasNameGenerator( - db.playerTable.id, - db.groupGameTable.groupId, - ), - ); - - $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { - final manager = $$GroupGameTableTableTableManager( - $_db, - $_db.groupGameTable, - ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } } class $$PlayerTableTableFilterComposer @@ -1629,31 +1608,6 @@ class $$PlayerTableTableFilterComposer ); return f(composer); } - - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableFilterComposer f) f, - ) { - final $$GroupGameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableFilterComposer( - $db: $db, - $table: $db.groupGameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } } class $$PlayerTableTableOrderingComposer @@ -1765,31 +1719,6 @@ class $$PlayerTableTableAnnotationComposer ); return f(composer); } - - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableAnnotationComposer a) f, - ) { - final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableAnnotationComposer( - $db: $db, - $table: $db.groupGameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } } class $$PlayerTableTableTableManager @@ -1809,7 +1738,6 @@ class $$PlayerTableTableTableManager bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, - bool groupGameTableRefs, }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) @@ -1849,7 +1777,6 @@ class $$PlayerTableTableTableManager gameTableRefs = false, playerGroupTableRefs = false, playerGameTableRefs = false, - groupGameTableRefs = false, }) { return PrefetchHooks( db: db, @@ -1857,7 +1784,6 @@ class $$PlayerTableTableTableManager if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, - if (groupGameTableRefs) db.groupGameTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -1925,27 +1851,6 @@ class $$PlayerTableTableTableManager ), typedResults: items, ), - if (groupGameTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - GroupGameTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._groupGameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).groupGameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.groupId == item.id, - ), - typedResults: items, - ), ]; }, ); @@ -1970,7 +1875,6 @@ typedef $$PlayerTableTableProcessedTableManager = bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, - bool groupGameTableRefs, }) >; typedef $$GroupTableTableCreateCompanionBuilder = @@ -2016,14 +1920,17 @@ final class $$GroupTableTableReferences static MultiTypedResultKey<$GroupGameTableTable, List> _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.groupGameTable, - aliasName: $_aliasNameGenerator(db.groupTable.id, db.groupGameTable.gameId), + aliasName: $_aliasNameGenerator( + db.groupTable.id, + db.groupGameTable.groupId, + ), ); $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { final manager = $$GroupGameTableTableTableManager( $_db, $_db.groupGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); return ProcessedTableManager( @@ -2083,7 +1990,7 @@ class $$GroupTableTableFilterComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { @@ -2169,7 +2076,7 @@ class $$GroupTableTableAnnotationComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { @@ -2287,7 +2194,7 @@ class $$GroupTableTableTableManager ).groupGameTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, + (e) => e.groupId == item.id, ), typedResults: items, ), @@ -2373,6 +2280,24 @@ final class $$GameTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey<$GroupGameTableTable, List> + _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupGameTable, + aliasName: $_aliasNameGenerator(db.gameTable.id, db.groupGameTable.gameId), + ); + + $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { + final manager = $$GroupGameTableTableTableManager( + $_db, + $_db.groupGameTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$GameTableTableFilterComposer @@ -2441,6 +2366,31 @@ class $$GameTableTableFilterComposer ); return f(composer); } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableFilterComposer f) f, + ) { + final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableFilterComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GameTableTableOrderingComposer @@ -2548,6 +2498,31 @@ class $$GameTableTableAnnotationComposer ); return f(composer); } + + Expression groupGameTableRefs( + Expression Function($$GroupGameTableTableAnnotationComposer a) f, + ) { + final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.groupGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupGameTableTableAnnotationComposer( + $db: $db, + $table: $db.groupGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GameTableTableTableManager @@ -2563,7 +2538,11 @@ class $$GameTableTableTableManager $$GameTableTableUpdateCompanionBuilder, (GameTableData, $$GameTableTableReferences), GameTableData, - PrefetchHooks Function({bool winnerId, bool playerGameTableRefs}) + PrefetchHooks Function({ + bool winnerId, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) > { $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) : super( @@ -2609,11 +2588,16 @@ class $$GameTableTableTableManager ) .toList(), prefetchHooksCallback: - ({winnerId = false, playerGameTableRefs = false}) { + ({ + winnerId = false, + playerGameTableRefs = false, + groupGameTableRefs = false, + }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGameTableRefs) db.playerGameTable, + if (groupGameTableRefs) db.groupGameTable, ], addJoins: < @@ -2670,6 +2654,27 @@ class $$GameTableTableTableManager ), typedResults: items, ), + if (groupGameTableRefs) + await $_getPrefetchedData< + GameTableData, + $GameTableTable, + GroupGameTableData + >( + currentTable: table, + referencedTable: $$GameTableTableReferences + ._groupGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GameTableTableReferences( + db, + table, + p0, + ).groupGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), ]; }, ); @@ -2690,7 +2695,11 @@ typedef $$GameTableTableProcessedTableManager = $$GameTableTableUpdateCompanionBuilder, (GameTableData, $$GameTableTableReferences), GameTableData, - PrefetchHooks Function({bool winnerId, bool playerGameTableRefs}) + PrefetchHooks Function({ + bool winnerId, + bool playerGameTableRefs, + bool groupGameTableRefs, + }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = PlayerGroupTableCompanion Function({ @@ -3448,17 +3457,17 @@ final class $$GroupGameTableTableReferences super.$_typedResult, ); - static $PlayerTableTable _groupIdTable(_$AppDatabase db) => - db.playerTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.groupId, db.playerTable.id), + static $GroupTableTable _groupIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.groupGameTable.groupId, db.groupTable.id), ); - $$PlayerTableTableProcessedTableManager get groupId { + $$GroupTableTableProcessedTableManager get groupId { final $_column = $_itemColumn('group_id')!; - final manager = $$PlayerTableTableTableManager( + final manager = $$GroupTableTableTableManager( $_db, - $_db.playerTable, + $_db.groupTable, ).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); if (item == null) return manager; @@ -3467,17 +3476,17 @@ final class $$GroupGameTableTableReferences ); } - static $GroupTableTable _gameIdTable(_$AppDatabase db) => - db.groupTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.gameId, db.groupTable.id), + static $GameTableTable _gameIdTable(_$AppDatabase db) => + db.gameTable.createAlias( + $_aliasNameGenerator(db.groupGameTable.gameId, db.gameTable.id), ); - $$GroupTableTableProcessedTableManager get gameId { + $$GameTableTableProcessedTableManager get gameId { final $_column = $_itemColumn('game_id')!; - final manager = $$GroupTableTableTableManager( + final manager = $$GameTableTableTableManager( $_db, - $_db.groupTable, + $_db.gameTable, ).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); if (item == null) return manager; @@ -3496,33 +3505,10 @@ class $$GroupGameTableTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableFilterComposer get groupId { - final $$PlayerTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - 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; - } - - $$GroupTableTableFilterComposer get gameId { + $$GroupTableTableFilterComposer get groupId { final $$GroupTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, + getCurrentColumn: (t) => t.groupId, referencedTable: $db.groupTable, getReferencedColumn: (t) => t.id, builder: @@ -3541,6 +3527,29 @@ class $$GroupGameTableTableFilterComposer ); return composer; } + + $$GameTableTableFilterComposer get gameId { + final $$GameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableFilterComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$GroupGameTableTableOrderingComposer @@ -3552,33 +3561,10 @@ class $$GroupGameTableTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableOrderingComposer get groupId { - final $$PlayerTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - 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; - } - - $$GroupTableTableOrderingComposer get gameId { + $$GroupTableTableOrderingComposer get groupId { final $$GroupTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, + getCurrentColumn: (t) => t.groupId, referencedTable: $db.groupTable, getReferencedColumn: (t) => t.id, builder: @@ -3597,6 +3583,29 @@ class $$GroupGameTableTableOrderingComposer ); return composer; } + + $$GameTableTableOrderingComposer get gameId { + final $$GameTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableOrderingComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$GroupGameTableTableAnnotationComposer @@ -3608,33 +3617,10 @@ class $$GroupGameTableTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - $$PlayerTableTableAnnotationComposer get groupId { - final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - 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; - } - - $$GroupTableTableAnnotationComposer get gameId { + $$GroupTableTableAnnotationComposer get groupId { final $$GroupTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, + getCurrentColumn: (t) => t.groupId, referencedTable: $db.groupTable, getReferencedColumn: (t) => t.id, builder: @@ -3653,6 +3639,29 @@ class $$GroupGameTableTableAnnotationComposer ); return composer; } + + $$GameTableTableAnnotationComposer get gameId { + final $$GameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableAnnotationComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$GroupGameTableTableTableManager diff --git a/lib/data/db/tables/group_game_table.dart b/lib/data/db/tables/group_game_table.dart index 6be12bc..a16672e 100644 --- a/lib/data/db/tables/group_game_table.dart +++ b/lib/data/db/tables/group_game_table.dart @@ -1,12 +1,12 @@ import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/game_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; class GroupGameTable extends Table { TextColumn get groupId => - text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get gameId => text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get gameId => + text().references(GameTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {groupId, gameId}; -- 2.49.1 From 1081fb8be7d61395bf0b1fab8addff48612dcb72 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 16:08:47 +0100 Subject: [PATCH 082/563] rearrange imports and fix wrong syntax in widget call --- lib/presentation/views/main_menu/game_history_view.dart | 8 +++++--- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index de75ae6..f620cd7 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -178,9 +178,11 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return TopCenteredMessage("Keine Spiele erstellt"); + return TopCenteredMessage(message: "Keine Spiele erstellt"); } else if (suggestedGameData.isEmpty) { - return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); + return TopCenteredMessage( + message: "Kein Spiel mit den Suchparametern gefunden.", + ); } return ListView.builder( itemCount: suggestedGameData.length, diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index a3e66d3..70f70dc 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; //import 'package:game_tracker/data/dto/group.dart'; //import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; -import 'package:game_tracker/presentation/widgets/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class Group { @@ -75,7 +74,8 @@ class _GroupsViewState extends State { child: TopCenteredMessage(message: "No groups created yet."), ); } - return GroupTile(group: snapshot.data![0]); + return Center(child: Text("whatever")); + //return GroupTile(group: snapshot.data![0]); //return ListView.builder() }, ), -- 2.49.1 From 36aa4722a329bd051c40c29df0129264c0c9ae19 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 17:11:48 +0100 Subject: [PATCH 083/563] wrapped custom_navigation_bar in safearea --- .../main_menu/custom_navigation_bar.dart | 126 +++++++++--------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 3f6a82a..28331b8 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -31,71 +31,73 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - _currentTabTitle(), - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + _currentTabTitle(), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), + ), + icon: const Icon(Icons.settings), + ), + ], + elevation: 0, ), backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - actions: [ - IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsView()), - ), - icon: const Icon(Icons.settings), - ), - ], - elevation: 0, - ), - backgroundColor: CustomTheme.backgroundColor, - body: tabs[currentIndex], - extendBody: true, - bottomNavigationBar: Padding( - padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 18.0), - child: Material( - elevation: 10, - borderRadius: BorderRadius.circular(24), - color: CustomTheme.primaryColor, - child: ClipRRect( + body: tabs[currentIndex], + extendBody: true, + bottomNavigationBar: Padding( + padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 18.0), + child: Material( + elevation: 10, borderRadius: BorderRadius.circular(24), - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - NavbarItem( - index: 0, - isSelected: currentIndex == 0, - icon: Icons.home_rounded, - label: 'Home', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 1, - isSelected: currentIndex == 1, - icon: Icons.gamepad_rounded, - label: 'Games', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 2, - isSelected: currentIndex == 2, - icon: Icons.group_rounded, - label: 'Groups', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 3, - isSelected: currentIndex == 3, - icon: Icons.bar_chart_rounded, - label: 'Stats', - onTabTapped: onTabTapped, - ), - ], + color: CustomTheme.primaryColor, + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: 'Home', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: 'Games', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: 'Groups', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: 'Stats', + onTabTapped: onTabTapped, + ), + ], + ), ), ), ), -- 2.49.1 From d08c79fc262b9298442962285072813a8d145a89 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 20:22:09 +0100 Subject: [PATCH 084/563] implemented groups view with create group button --- .../views/main_menu/groups_view.dart | 115 +++++++++++++----- .../widgets/full_width_button.dart | 2 +- lib/presentation/widgets/group_tile.dart | 73 +++++++---- 3 files changed, 134 insertions(+), 56 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 70f70dc..76034a6 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; //import 'package:game_tracker/data/dto/group.dart'; //import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class Group { @@ -38,7 +39,15 @@ class _GroupsViewState extends State { Group( id: 'g1', name: 'Weekend Warriors', - members: [player1, player2, player4], + members: [ + player1, + player2, + player4, + player3, + player1, + player4, + player2, + ], ), Group(id: 'g2', name: 'Strategy Masters', members: [player3, player4]), Group( @@ -46,41 +55,85 @@ class _GroupsViewState extends State { name: 'The Cardboard Crew', members: [player1, player2, player3, player4], ), - Group(id: 'g4', name: 'The Group', members: [player1, player3, player4]), + Group( + id: 'g4', + name: 'Gamers', + members: [player1, player3, player1, player4], + ), + Group( + id: 'g4', + name: 'The Group', + members: [player4, player1, player3, player4, player3], + ), + Group( + id: 'g4', + name: 'Friends', + members: [player4, player1, player3, player4], + ), + Group( + id: 'g4', + name: 'Sample Group', + members: [player1, player1, player4, player3], + ), + Group( + id: 'g4', + name: 'The Group', + members: [player1, player1, player3, player4], + ), + Group( + id: 'g4', + name: 'The Best', + members: [player1, player3, player1, player4, player1], + ), ]; } @override Widget build(BuildContext context) { - return Column( - children: [ - FutureBuilder( - future: _getMockGroups(), - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: TopCenteredMessage( - message: "Data not yet available, show sceleton", - ), - ); - } else if (snapshot.hasError) { - return Center( - child: TopCenteredMessage( - message: "Error while loading group data.", - ), - ); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return Center( - child: TopCenteredMessage(message: "No groups created yet."), - ); - } - return Center(child: Text("whatever")); - //return GroupTile(group: snapshot.data![0]); - //return ListView.builder() - }, - ), - FullWidthButton(text: "Create Group", onPressed: () {}), - ], + return SafeArea( + child: Stack( + children: [ + FutureBuilder( + future: _getMockGroups(), + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: TopCenteredMessage( + message: 'Data not yet available, show sceleton', + ), + ); + } else if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + message: 'Error while loading group data.', + ), + ); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center( + child: TopCenteredMessage( + message: 'No groups created yet.', + ), + ); + } + //return Center(child: Text('whatever')); + //return GroupTile(group: snapshot.data![0]); + return ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: snapshot.data!.length, + itemBuilder: (BuildContext context, int index) { + return GroupTile(group: snapshot.data![index]); + }, + ); + }, + ), + Positioned( + bottom: 16, + right: 16, + child: FullWidthButton(text: 'Create Group', onPressed: () {}), + ), + ], + ), ); } } diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index 6df700b..2af6abe 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -12,7 +12,7 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - minimumSize: Size(MediaQuery.of(context).size.width * 0.8, 60), + minimumSize: Size(MediaQuery.of(context).size.width * 0.90, 60), backgroundColor: CustomTheme.primaryColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index 5959f04..783abcd 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -1,20 +1,6 @@ import 'package:flutter/material.dart'; -//import 'package:game_tracker/data/dto/group.dart'; - -class Group { - final String id; - final String name; - final List members; - - Group({required this.id, required this.name, required this.members}); -} - -class Player { - final String id; - final String name; - - Player({required this.id, required this.name}); -} +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); @@ -24,31 +10,70 @@ class GroupTile extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + width: MediaQuery.of(context).size.width * 0.90, + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.secondaryColor, + borderRadius: BorderRadius.circular(12), + ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("${group.name}", overflow: TextOverflow.ellipsis), - Text("${group.members.length}"), - Icon(Icons.group), + Text( + group.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.white, + ), + ), + const Spacer(), + Text( + '${group.members.length}', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 20, + color: Colors.white, + ), + ), + SizedBox(width: 3), + const Icon(Icons.group), ], ), + SizedBox(height: 5), Wrap( - spacing: 8.0, - runSpacing: -4.0, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 12.0, + runSpacing: 8.0, children: [ for (var member in group.members) Container( - color: Colors.grey, - padding: const EdgeInsets.all(4.0), + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), decoration: BoxDecoration( + color: Colors.black26, borderRadius: BorderRadius.circular(12), ), - child: Text(member.name), + child: Text( + member.name, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), ), ], ), + SizedBox(height: 2.5), ], ), ); -- 2.49.1 From d36348c59c1b87afbdf4f1e7f065dff86aa0a301 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 21:24:52 +0100 Subject: [PATCH 085/563] added skeleton loading --- .../views/main_menu/groups_view.dart | 65 ++++++++++++++----- lib/presentation/widgets/group_tile.dart | 2 +- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 76034a6..e52cf58 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class Group { final String id; @@ -29,7 +30,7 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { Future> _getMockGroups() async { - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 4)); final player1 = Player(id: 'p1', name: 'Felix'); final player2 = Player(id: 'p2', name: 'Yannick'); final player3 = Player(id: 'p3', name: 'Mathis'); @@ -88,45 +89,73 @@ class _GroupsViewState extends State { ]; } + final player = Player(id: 'p1', name: 'Felix'); + late final List skeletonData = List.filled( + 7, + Group( + id: 'g1', + name: 'Weekend Warriors', + members: [player, player, player, player], + ), + ); + @override Widget build(BuildContext context) { return SafeArea( child: Stack( children: [ - FutureBuilder( + FutureBuilder>( future: _getMockGroups(), builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: TopCenteredMessage( - message: 'Data not yet available, show sceleton', - ), - ); - } else if (snapshot.hasError) { + if (snapshot.hasError) { return const Center( child: TopCenteredMessage( message: 'Error while loading group data.', ), ); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( child: TopCenteredMessage( message: 'No groups created yet.', ), ); } - //return Center(child: Text('whatever')); - //return GroupTile(group: snapshot.data![0]); - return ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: snapshot.data!.length, - itemBuilder: (BuildContext context, int index) { - return GroupTile(group: snapshot.data![index]); - }, + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; + final List groups = isLoading + ? skeletonData + : (snapshot.data ?? []); + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[100]!, + to: Colors.grey[400]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: + AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: groups.length, + itemBuilder: (BuildContext context, int index) { + return GroupTile(group: groups[index]); + }, + ), ); }, ), + + // Dein Button bleibt wie gehabt Positioned( bottom: 16, right: 16, diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index 783abcd..87d997b 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -59,7 +59,7 @@ class GroupTile extends StatelessWidget { horizontal: 10, ), decoration: BoxDecoration( - color: Colors.black26, + color: Colors.black38, borderRadius: BorderRadius.circular(12), ), child: Text( -- 2.49.1 From 352fdc13f1698eb26d5f37f5b001b1a6dca80c1b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 21:25:12 +0100 Subject: [PATCH 086/563] added skeletonizer package requirement --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 5fa8019..ab6e30b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: drift_flutter: ^0.2.4 path_provider: ^2.1.5 provider: ^6.1.5 + skeletonizer: ^2.1.0+1 dev_dependencies: flutter_test: -- 2.49.1 From d07bc6e8bbbb5eb3494e7359daa237933eaa8a6e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 21:43:03 +0100 Subject: [PATCH 087/563] increased skeleton item count & added daos as import --- .../views/main_menu/groups_view.dart | 22 +++---------------- lib/presentation/widgets/group_tile.dart | 2 +- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index e52cf58..623f7df 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -1,26 +1,11 @@ import 'package:flutter/material.dart'; -//import 'package:game_tracker/data/dto/group.dart'; -//import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class Group { - final String id; - final String name; - final List members; - - Group({required this.id, required this.name, required this.members}); -} - -class Player { - final String id; - final String name; - - Player({required this.id, required this.name}); -} - class GroupsView extends StatefulWidget { const GroupsView({super.key}); @@ -91,7 +76,7 @@ class _GroupsViewState extends State { final player = Player(id: 'p1', name: 'Felix'); late final List skeletonData = List.filled( - 7, + 8, Group( id: 'g1', name: 'Weekend Warriors', @@ -155,7 +140,6 @@ class _GroupsViewState extends State { }, ), - // Dein Button bleibt wie gehabt Positioned( bottom: 16, right: 16, diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index 87d997b..bb96493 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; +import 'package:game_tracker/data/dto/group.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); -- 2.49.1 From 511124e323b48d9d52469289acdca6f1bfa82d13 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 21:57:31 +0100 Subject: [PATCH 088/563] reduced mock data loading time --- lib/presentation/views/main_menu/groups_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 623f7df..4865d0a 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -15,7 +15,7 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { Future> _getMockGroups() async { - await Future.delayed(const Duration(seconds: 4)); + await Future.delayed(const Duration(seconds: 2)); final player1 = Player(id: 'p1', name: 'Felix'); final player2 = Player(id: 'p2', name: 'Yannick'); final player3 = Player(id: 'p3', name: 'Mathis'); -- 2.49.1 From ac6f3d0f929a0f34afaa23bc737ca4feec0ec791 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 22:10:51 +0100 Subject: [PATCH 089/563] added missing const's --- lib/presentation/widgets/group_tile.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index bb96493..a874bc7 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -41,11 +41,11 @@ class GroupTile extends StatelessWidget { color: Colors.white, ), ), - SizedBox(width: 3), + const SizedBox(width: 3), const Icon(Icons.group), ], ), - SizedBox(height: 5), + const SizedBox(height: 5), Wrap( alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, @@ -64,7 +64,7 @@ class GroupTile extends StatelessWidget { ), child: Text( member.name, - style: TextStyle( + style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: Colors.white, @@ -73,7 +73,7 @@ class GroupTile extends StatelessWidget { ), ], ), - SizedBox(height: 2.5), + const SizedBox(height: 2.5), ], ), ); -- 2.49.1 From 7c34e517794c3679dc166a2381ef1757e6516684 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 15 Nov 2025 22:33:00 +0100 Subject: [PATCH 090/563] fixed black bar behind nav bar by removing safearea --- lib/presentation/views/main_menu/groups_view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 4865d0a..0d5b993 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; @@ -86,8 +87,10 @@ class _GroupsViewState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: Stack( + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + body: Stack( + alignment: Alignment.center, children: [ FutureBuilder>( future: _getMockGroups(), @@ -141,8 +144,7 @@ class _GroupsViewState extends State { ), Positioned( - bottom: 16, - right: 16, + bottom: 80, child: FullWidthButton(text: 'Create Group', onPressed: () {}), ), ], -- 2.49.1 From b475237b9ea59a38f0ce906d53e7e5a47632b82e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 16:25:50 +0100 Subject: [PATCH 091/563] ignored text for players in skeleton loading --- lib/presentation/widgets/group_tile.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index a874bc7..a317248 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); @@ -62,12 +63,14 @@ class GroupTile extends StatelessWidget { color: Colors.black38, borderRadius: BorderRadius.circular(12), ), - child: Text( - member.name, - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.bold, - color: Colors.white, + child: Skeleton.ignore( + child: Text( + member.name, + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), ), -- 2.49.1 From 168d7748a98e2864b1b518a4d2617b867638bc19 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 16:26:08 +0100 Subject: [PATCH 092/563] added todo for getAllGroups tests --- test/db_tests/group_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index a8fbcd5..d9e2c32 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -36,6 +36,7 @@ void main() { await database.close(); }); + //TODO: test getAllGroups method test('group and group members gets added correctly', () async { await database.groupDao.addGroup(group: testgroup); -- 2.49.1 From 640830d8ab39e07339bf6a966d620f67f205fde9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 16:26:28 +0100 Subject: [PATCH 093/563] fixed getAllGroups not returning members --- lib/data/dao/group_dao.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 1f0e2c8..ffcb84a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -14,9 +14,14 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { Future> getAllGroups() async { final query = select(groupTable); final result = await query.get(); - return result - .map((row) => Group(id: row.id, name: row.name, members: [])) - .toList(); + return Future.wait( + result.map((groupData) async { + final members = await db.playerGroupDao.getPlayersOfGroupById( + groupId: groupData.id, + ); + return Group(id: groupData.id, name: groupData.name, members: members); + }), + ); } /// Retrieves a [Group] by its [groupId], including its members. -- 2.49.1 From 2dad822d79582686d82548a3af0e77d12ec0e2ad Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 16:34:51 +0100 Subject: [PATCH 094/563] add sample groups with sample members at app startup --- lib/main.dart | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..b19eb45 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -19,6 +21,7 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + addSampleGroupData(context); return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', @@ -39,4 +42,74 @@ class GameTracker extends StatelessWidget { home: const CustomNavigationBar(), ); } + + Future addSampleGroupData(BuildContext context) async { + final db = Provider.of(context, listen: false); + if (await db.groupDao.getGroupCount() == 0) { + final List allPlayers = [ + Player(id: '1', name: 'Alex'), + Player(id: '2', name: 'Ben'), + Player(id: '3', name: 'Chris'), + Player(id: '4', name: 'Daniel'), + Player(id: '5', name: 'Max Mustermann'), + Player(id: '6', name: 'Sebastian'), + Player(id: '7', name: 'Jonathan'), + Player(id: '8', name: 'Alexander'), + ]; + + // 2. Erstelle und füge 8 Gruppen mit unterschiedlicher Spieleranzahl hinzu + await db.groupDao.addGroup( + group: Group( + id: '1', + name: 'Anfänger', + members: allPlayers.sublist(0, 3), + ), + ); // 3 Spieler + await db.groupDao.addGroup( + group: Group( + id: '2', + name: 'Die glorreichen Sieben', + members: allPlayers.sublist(0, 7), + ), + ); // 7 Spieler + await db.groupDao.addGroup( + group: Group( + id: '3', + name: 'Profis', + members: allPlayers.sublist(4, 8), + ), + ); // 4 Spieler + await db.groupDao.addGroup( + group: Group( + id: '4', + name: 'Duo Infernale', + members: [allPlayers[0], allPlayers[7]], + ), + ); // 2 Spieler + await db.groupDao.addGroup( + group: Group( + id: '5', + name: 'Die fantastischen Fünf', + members: allPlayers.sublist(1, 6), + ), + ); // 5 Spieler + await db.groupDao.addGroup( + group: Group( + id: '6', + name: 'Feierabend-Zocker', + members: allPlayers.sublist(0, 6), + ), + ); // 6 Spieler + await db.groupDao.addGroup( + group: Group(id: '7', name: 'Alle Mann an Bord!', members: allPlayers), + ); // 8 Spieler + await db.groupDao.addGroup( + group: Group( + id: '8', + name: 'Testgruppe Alpha', + members: [allPlayers[1], allPlayers[3]], + ), + ); + } + } } -- 2.49.1 From 2dd4f52336dfab9db96fda0de03bb80ad107fa28 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 16:42:18 +0100 Subject: [PATCH 095/563] removed sample groups from groups view --- .../views/main_menu/groups_view.dart | 82 ++++--------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 0d5b993..c5f8def 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class GroupsView extends StatefulWidget { @@ -15,76 +17,25 @@ class GroupsView extends StatefulWidget { } class _GroupsViewState extends State { - Future> _getMockGroups() async { - await Future.delayed(const Duration(seconds: 2)); - final player1 = Player(id: 'p1', name: 'Felix'); - final player2 = Player(id: 'p2', name: 'Yannick'); - final player3 = Player(id: 'p3', name: 'Mathis'); - final player4 = Player(id: 'p4', name: 'Petrus'); + late Future> _allGroupsFuture; - return [ - Group( - id: 'g1', - name: 'Weekend Warriors', - members: [ - player1, - player2, - player4, - player3, - player1, - player4, - player2, - ], - ), - Group(id: 'g2', name: 'Strategy Masters', members: [player3, player4]), - Group( - id: 'g3', - name: 'The Cardboard Crew', - members: [player1, player2, player3, player4], - ), - Group( - id: 'g4', - name: 'Gamers', - members: [player1, player3, player1, player4], - ), - Group( - id: 'g4', - name: 'The Group', - members: [player4, player1, player3, player4, player3], - ), - Group( - id: 'g4', - name: 'Friends', - members: [player4, player1, player3, player4], - ), - Group( - id: 'g4', - name: 'Sample Group', - members: [player1, player1, player4, player3], - ), - Group( - id: 'g4', - name: 'The Group', - members: [player1, player1, player3, player4], - ), - Group( - id: 'g4', - name: 'The Best', - members: [player1, player3, player1, player4, player1], - ), - ]; - } - - final player = Player(id: 'p1', name: 'Felix'); + final player = Player(id: 'p1', name: 'Sample'); late final List skeletonData = List.filled( 8, Group( - id: 'g1', - name: 'Weekend Warriors', + id: '0', + name: 'Sample Game', members: [player, player, player, player], ), ); + @override + void initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _allGroupsFuture = db.groupDao.getAllGroups(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -93,7 +44,7 @@ class _GroupsViewState extends State { alignment: Alignment.center, children: [ FutureBuilder>( - future: _getMockGroups(), + future: _allGroupsFuture, builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { @@ -134,8 +85,11 @@ class _GroupsViewState extends State { ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: groups.length, + itemCount: groups.length + 1, itemBuilder: (BuildContext context, int index) { + if (index == groups.length) { + return const SizedBox(height: 60); + } return GroupTile(group: groups[index]); }, ), -- 2.49.1 From 3bd522df6e3b355f81af29e5b9464fef68e13afa Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 18:29:27 +0100 Subject: [PATCH 096/563] change colors to fit home page --- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- lib/presentation/widgets/group_tile.dart | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index c5f8def..6a3f404 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -69,8 +69,8 @@ class _GroupsViewState extends State { : (snapshot.data ?? []); return Skeletonizer( effect: PulseEffect( - from: Colors.grey[100]!, - to: Colors.grey[400]!, + from: Colors.grey[800]!, + to: Colors.grey[600]!, duration: const Duration(milliseconds: 800), ), enabled: isLoading, diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index a317248..f61d3ce 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -15,7 +15,8 @@ class GroupTile extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: BoxDecoration( - color: CustomTheme.secondaryColor, + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), borderRadius: BorderRadius.circular(12), ), child: Column( @@ -30,7 +31,6 @@ class GroupTile extends StatelessWidget { style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20, - color: Colors.white, ), ), const Spacer(), @@ -39,11 +39,10 @@ class GroupTile extends StatelessWidget { style: const TextStyle( fontWeight: FontWeight.w900, fontSize: 20, - color: Colors.white, ), ), const SizedBox(width: 3), - const Icon(Icons.group), + Icon(Icons.group), ], ), const SizedBox(height: 5), @@ -69,7 +68,6 @@ class GroupTile extends StatelessWidget { style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold, - color: Colors.white, ), ), ), -- 2.49.1 From a707990356ef3d9292b6ce43e3a95b4f381c9061 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 18:36:38 +0100 Subject: [PATCH 097/563] added new color: onBoxColor --- lib/core/custom_theme.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 438aab5..16e9585 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -5,6 +5,7 @@ class CustomTheme { static Color secondaryColor = const Color(0xFFAFA2FF); static Color backgroundColor = const Color(0xFF0B0B0B); static Color boxColor = const Color(0xFF101010); + static Color onBoxColor = const Color(0xFF181818); static Color boxBorder = const Color(0xFF272727); static AppBarTheme appBarTheme = AppBarTheme( -- 2.49.1 From d931e85e9f1eca94a9f64f97863520b8a88142b6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 18:36:54 +0100 Subject: [PATCH 098/563] changed member tile color --- lib/presentation/widgets/group_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index f61d3ce..11d3e47 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -59,7 +59,7 @@ class GroupTile extends StatelessWidget { horizontal: 10, ), decoration: BoxDecoration( - color: Colors.black38, + color: CustomTheme.onBoxColor, borderRadius: BorderRadius.circular(12), ), child: Skeleton.ignore( -- 2.49.1 From a6f40456d8fe0297bdd7ef697a0715d0dc2bd851 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 18:44:56 +0100 Subject: [PATCH 099/563] added missing const --- lib/presentation/widgets/group_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/group_tile.dart index 11d3e47..ce6ba34 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/group_tile.dart @@ -42,7 +42,7 @@ class GroupTile extends StatelessWidget { ), ), const SizedBox(width: 3), - Icon(Icons.group), + const Icon(Icons.group), ], ), const SizedBox(height: 5), -- 2.49.1 From 02735b5b1dfd27daa9d349244dbd437a41b27751 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 18:58:01 +0100 Subject: [PATCH 100/563] Added missing test --- test/db_tests/group_test.dart | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index d9e2c32..59ba686 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -36,7 +36,29 @@ void main() { await database.close(); }); - //TODO: test getAllGroups method + test('all groups get fetched correclty', () async { + final testgroup2 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player3, player4], + ); + await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup2); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); + + final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); + expect(fetchedGroup1.name, testgroup.name); + expect(fetchedGroup1.members.length, testgroup.members.length); + expect(fetchedGroup1.members.elementAt(0).id, player1.id); + + final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); + expect(fetchedGroup2.name, testgroup2.name); + expect(fetchedGroup2.members.length, testgroup2.members.length); + expect(fetchedGroup2.members.elementAt(0).id, player2.id); + }); + test('group and group members gets added correctly', () async { await database.groupDao.addGroup(group: testgroup); -- 2.49.1 From 5c09dfb47dae81f736311908631e6cdff3f23e73 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 19:01:58 +0100 Subject: [PATCH 101/563] Added fallbacks for groups when same group already exists --- lib/data/dao/group_dao.dart | 48 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index ffcb84a..8eb3a1a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -38,28 +38,32 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Adds a new group with the given [id] and [name] to the database. /// This method also adds the group's members to the [PlayerGroupTable]. - Future addGroup({required Group group}) async { - await db.transaction(() async { - await into( - groupTable, - ).insert(GroupTableCompanion.insert(id: group.id, name: group.name)); - await db.batch( - (b) => b.insertAll( - db.playerGroupTable, - group.members - .map( - (member) => PlayerGroupTableCompanion.insert( - playerId: member.id, - groupId: group.id, - ), - ) - .toList(), - ), - ); - await Future.wait( - group.members.map((player) => db.playerDao.addPlayer(player: player)), - ); - }); + Future addGroup({required Group group}) async { + if (!await groupExists(groupId: group.id)) { + await db.transaction(() async { + await into( + groupTable, + ).insert(GroupTableCompanion.insert(id: group.id, name: group.name)); + await db.batch( + (b) => b.insertAll( + db.playerGroupTable, + group.members + .map( + (member) => PlayerGroupTableCompanion.insert( + playerId: member.id, + groupId: group.id, + ), + ) + .toList(), + ), + ); + await Future.wait( + group.members.map((player) => db.playerDao.addPlayer(player: player)), + ); + return true; + }); + } + return false; } /// Deletes the group with the given [id] from the database. -- 2.49.1 From 74402f2e04de52ef7e32d8ca6740b9cbefd2cd10 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 19:02:20 +0100 Subject: [PATCH 102/563] Added fallbacks for players when same player already exists --- lib/data/dao/player_dao.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 976d4b0..591634c 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -26,14 +26,14 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// Adds a new [player] to the database. /// If a player with the same ID already exists, updates their name to /// the new one. - Future addPlayer({required Player player}) async { + Future addPlayer({required Player player}) async { if (!await playerExists(playerId: player.id)) { await into( playerTable, ).insert(PlayerTableCompanion.insert(id: player.id, name: player.name)); - } else { - await updatePlayername(playerId: player.id, newName: player.name); + return true; } + return false; } /// Deletes the player with the given [id] from the database. -- 2.49.1 From e709edbf7a7406165ec8c72dc86f11e6538c77ae Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 19:12:38 +0100 Subject: [PATCH 103/563] Added getAllPlayers test --- test/db_tests/player_test.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 7fb9152..9f12faa 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -24,6 +24,25 @@ void main() { }); group('player tests', () { + test('all players get fetched correclty', () async { + final testPlayer2 = Player(id: 'gr2', name: 'Second Group'); + await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer2); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer.id, + ); + expect(fetchedPlayer1.name, testPlayer.name); + + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + }); + test('players get inserted correcly ', () async { await database.playerDao.addPlayer(player: testPlayer); final result = await database.playerDao.getPlayerById( -- 2.49.1 From d3de0fda49c385eac8bb65a78e1d657c117daf4e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 19:12:49 +0100 Subject: [PATCH 104/563] Updates getAllGroups test --- test/db_tests/group_test.dart | 207 ++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 99 deletions(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 59ba686..e8e6316 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -35,137 +35,146 @@ void main() { tearDown(() async { await database.close(); }); + group('group tests', () { + test('all groups get fetched correclty', () async { + final testgroup2 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player3, player4], + ); + await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup2); - test('all groups get fetched correclty', () async { - final testgroup2 = Group( - id: 'gr2', - name: 'Second Group', - members: [player2, player3, player4], - ); - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 2); + final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); + expect(fetchedGroup1.name, testgroup.name); + expect(fetchedGroup1.members.length, testgroup.members.length); + expect(fetchedGroup1.members.elementAt(0).id, player1.id); - final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); - expect(fetchedGroup1.name, testgroup.name); - expect(fetchedGroup1.members.length, testgroup.members.length); - expect(fetchedGroup1.members.elementAt(0).id, player1.id); + final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); + expect(fetchedGroup2.name, testgroup2.name); + expect(fetchedGroup2.members.length, testgroup2.members.length); + expect(fetchedGroup2.members.elementAt(0).id, player2.id); + }); - final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); - expect(fetchedGroup2.name, testgroup2.name); - expect(fetchedGroup2.members.length, testgroup2.members.length); - expect(fetchedGroup2.members.elementAt(0).id, player2.id); - }); + test('group and group members gets added correctly', () async { + await database.groupDao.addGroup(group: testgroup); - test('group and group members gets added correctly', () async { - await database.groupDao.addGroup(group: testgroup); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); - final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.id, testgroup.id); + expect(result.name, testgroup.name); - expect(result.id, testgroup.id); - expect(result.name, testgroup.name); + expect(result.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.members[i].id, testgroup.members[i].id); + expect(result.members[i].name, testgroup.members[i].name); + } + }); - expect(result.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.members[i].id, testgroup.members[i].id); - expect(result.members[i].name, testgroup.members[i].name); - } - }); + test('group gets deleted correctly', () async { + await database.groupDao.addGroup(group: testgroup); - test('group gets deleted correctly', () async { - await database.groupDao.addGroup(group: testgroup); + final groupDeleted = await database.groupDao.deleteGroup( + groupId: testgroup.id, + ); + expect(groupDeleted, true); - final groupDeleted = await database.groupDao.deleteGroup( - groupId: testgroup.id, - ); - expect(groupDeleted, true); + final groupExists = await database.groupDao.groupExists( + groupId: testgroup.id, + ); + expect(groupExists, false); + }); - final groupExists = await database.groupDao.groupExists( - groupId: testgroup.id, - ); - expect(groupExists, false); - }); + test('group name gets updated correcly ', () async { + await database.groupDao.addGroup(group: testgroup); - test('group name gets updated correcly ', () async { - await database.groupDao.addGroup(group: testgroup); + const newGroupName = 'new group name'; - const newGroupName = 'new group name'; + await database.groupDao.updateGroupname( + groupId: testgroup.id, + newName: newGroupName, + ); - await database.groupDao.updateGroupname( - groupId: testgroup.id, - newName: newGroupName, - ); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.name, newGroupName); + }); - final result = await database.groupDao.getGroupById(groupId: testgroup.id); - expect(result.name, newGroupName); - }); + test('Adding player to group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); - test('Adding player to group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); + await database.playerGroupDao.addPlayerToGroup( + player: player4, + groupId: testgroup.id, + ); - await database.playerGroupDao.addPlayerToGroup( - player: player4, - groupId: testgroup.id, - ); + final playerAdded = await database.playerGroupDao.isPlayerInGroup( + playerId: player4.id, + groupId: testgroup.id, + ); - final playerAdded = await database.playerGroupDao.isPlayerInGroup( - playerId: player4.id, - groupId: testgroup.id, - ); + expect(playerAdded, true); - expect(playerAdded, true); + final playerAdded2 = await database.playerGroupDao.isPlayerInGroup( + playerId: 'a', + groupId: testgroup.id, + ); - final playerAdded2 = await database.playerGroupDao.isPlayerInGroup( - playerId: 'a', - groupId: testgroup.id, - ); + expect(playerAdded2, false); - expect(playerAdded2, false); + expect(playerAdded, true); - expect(playerAdded, true); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length + 1); - final result = await database.groupDao.getGroupById(groupId: testgroup.id); - expect(result.members.length, testgroup.members.length + 1); + final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); + expect(addedPlayer.name, player4.name); + }); - final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); - expect(addedPlayer.name, player4.name); - }); + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); - test('Removing player from group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); + final playerToRemove = testgroup.members[0]; - final playerToRemove = testgroup.members[0]; + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testgroup.id, + ); + expect(removed, true); - final removed = await database.playerGroupDao.removePlayerFromGroup( - playerId: playerToRemove.id, - groupId: testgroup.id, - ); - expect(removed, true); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length - 1); - final result = await database.groupDao.getGroupById(groupId: testgroup.id); - expect(result.members.length, testgroup.members.length - 1); + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); - final playerExists = result.members.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); + test('get group count works correctly', () async { + final initialCount = await database.groupDao.getGroupCount(); + expect(initialCount, 0); - test('get group count works correctly', () async { - final initialCount = await database.groupDao.getGroupCount(); - expect(initialCount, 0); + await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup); + final groupAdded = await database.groupDao.getGroupCount(); + expect(groupAdded, 1); - final groupAdded = await database.groupDao.getGroupCount(); - expect(groupAdded, 1); + final groupRemoved = await database.groupDao.deleteGroup( + groupId: testgroup.id, + ); + expect(groupRemoved, true); - final groupRemoved = await database.groupDao.deleteGroup( - groupId: testgroup.id, - ); - expect(groupRemoved, true); - - final finalCount = await database.groupDao.getGroupCount(); - expect(finalCount, 0); + final finalCount = await database.groupDao.getGroupCount(); + expect(finalCount, 0); + }); }); } -- 2.49.1 From 8d8d4319d248ec6a7934bdc126df419ed4923e3a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:37:51 +0100 Subject: [PATCH 105/563] change Mediaquery.of to .sizeOf --- lib/presentation/widgets/full_width_button.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index 2af6abe..bd18c64 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -12,7 +12,7 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - minimumSize: Size(MediaQuery.of(context).size.width * 0.90, 60), + minimumSize: Size(MediaQuery.sizeOf(context).width * 0.9, 60), backgroundColor: CustomTheme.primaryColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), -- 2.49.1 From 38466c6056a8e05d6d8eded63dcc37ac68cdd691 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:38:08 +0100 Subject: [PATCH 106/563] added title and icon to gamehistoryview --- lib/presentation/views/main_menu/game_history_view.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index f620cd7..3642a88 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -178,9 +178,15 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return TopCenteredMessage(message: "Keine Spiele erstellt"); + return TopCenteredMessage( + icon: Icons.info, + title: "Info", + message: "Keine Spiele erstellt", + ); } else if (suggestedGameData.isEmpty) { return TopCenteredMessage( + icon: Icons.search, + title: "Info", message: "Kein Spiel mit den Suchparametern gefunden.", ); } -- 2.49.1 From deab074885001a0f4ef6451ebdaeaa1fdfff6294 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:38:26 +0100 Subject: [PATCH 107/563] changed font sizes & tile width --- lib/presentation/widgets/{ => tiles}/group_tile.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) rename lib/presentation/widgets/{ => tiles}/group_tile.dart (90%) diff --git a/lib/presentation/widgets/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart similarity index 90% rename from lib/presentation/widgets/group_tile.dart rename to lib/presentation/widgets/tiles/group_tile.dart index ce6ba34..448c68c 100644 --- a/lib/presentation/widgets/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -11,8 +11,7 @@ class GroupTile extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - width: MediaQuery.of(context).size.width * 0.90, - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: BoxDecoration( color: CustomTheme.boxColor, @@ -30,7 +29,7 @@ class GroupTile extends StatelessWidget { overflow: TextOverflow.ellipsis, style: const TextStyle( fontWeight: FontWeight.bold, - fontSize: 20, + fontSize: 18, ), ), const Spacer(), @@ -38,11 +37,11 @@ class GroupTile extends StatelessWidget { '${group.members.length}', style: const TextStyle( fontWeight: FontWeight.w900, - fontSize: 20, + fontSize: 18, ), ), const SizedBox(width: 3), - const Icon(Icons.group), + const Icon(Icons.group, size: 22), ], ), const SizedBox(height: 5), @@ -66,7 +65,7 @@ class GroupTile extends StatelessWidget { child: Text( member.name, style: const TextStyle( - fontSize: 17, + fontSize: 14, fontWeight: FontWeight.bold, ), ), -- 2.49.1 From 258f610e289b0c7bae92babbac77111d4bc11b85 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:39:34 +0100 Subject: [PATCH 108/563] added icons & titles to top contered messages --- lib/presentation/views/main_menu/groups_view.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 6a3f404..7b79a53 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; -import 'package:game_tracker/presentation/widgets/group_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -50,7 +50,9 @@ class _GroupsViewState extends State { if (snapshot.hasError) { return const Center( child: TopCenteredMessage( - message: 'Error while loading group data.', + icon: Icons.report, + title: "Error", + message: 'Group data couldn\'t\nbe loaded.', ), ); } @@ -58,6 +60,8 @@ class _GroupsViewState extends State { (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( child: TopCenteredMessage( + icon: Icons.info, + title: "Info", message: 'No groups created yet.', ), ); -- 2.49.1 From c1e032208cd79d6d28d01b2963ef8f70b89e786d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:39:48 +0100 Subject: [PATCH 109/563] added icons, titles and changed font size --- .../widgets/top_centered_message.dart | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index df8dcb1..af317d8 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,9 +1,16 @@ import 'package:flutter/material.dart'; class TopCenteredMessage extends StatelessWidget { - const TopCenteredMessage({super.key, required this.message}); + const TopCenteredMessage({ + super.key, + required this.icon, + required this.title, + required this.message, + }); + final String title; final String message; + final IconData icon; @override Widget build(BuildContext context) { @@ -11,10 +18,21 @@ class TopCenteredMessage extends StatelessWidget { padding: const EdgeInsets.only(top: 100), margin: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.topCenter, - child: Text( - message, - style: const TextStyle(fontSize: 20), - textAlign: TextAlign.center, + child: Column( + children: [ + Icon(icon, size: 45), + SizedBox(height: 10), + Text( + title, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + Text( + message, + style: const TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), + ], ), ); } -- 2.49.1 From accc1899493beddb472312248e4c15227dc9f60f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:50:54 +0100 Subject: [PATCH 110/563] decreased number of tiles in skeleton --- lib/presentation/views/main_menu/groups_view.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7b79a53..11e2544 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -21,11 +21,11 @@ class _GroupsViewState extends State { final player = Player(id: 'p1', name: 'Sample'); late final List skeletonData = List.filled( - 8, + 7, Group( id: '0', name: 'Sample Game', - members: [player, player, player, player], + members: [player, player, player, player, player, player], ), ); @@ -68,16 +68,17 @@ class _GroupsViewState extends State { } final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List groups = isLoading - ? skeletonData - : (snapshot.data ?? []); + final List groups = skeletonData; + //final List groups = isLoading + // ? skeletonData + // : (snapshot.data ?? []); return Skeletonizer( effect: PulseEffect( from: Colors.grey[800]!, to: Colors.grey[600]!, duration: const Duration(milliseconds: 800), ), - enabled: isLoading, + enabled: true, enableSwitchAnimation: true, switchAnimationConfig: const SwitchAnimationConfig( duration: Duration(milliseconds: 200), -- 2.49.1 From 4befc85c9f9492b6bb3ce0eaa412aba9cf7a1354 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:52:31 +0100 Subject: [PATCH 111/563] added missing const --- lib/presentation/widgets/top_centered_message.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index af317d8..a5deea2 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -21,7 +21,7 @@ class TopCenteredMessage extends StatelessWidget { child: Column( children: [ Icon(icon, size: 45), - SizedBox(height: 10), + const SizedBox(height: 10), Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), -- 2.49.1 From 9ffb7d6ca3c684379eeb34d1e6893fe3a9cceac4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 19:53:32 +0100 Subject: [PATCH 112/563] changed skeleton to only show when loading --- lib/presentation/views/main_menu/groups_view.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 11e2544..8b3bffe 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -68,17 +68,16 @@ class _GroupsViewState extends State { } final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List groups = skeletonData; - //final List groups = isLoading - // ? skeletonData - // : (snapshot.data ?? []); + final List groups = isLoading + ? skeletonData + : (snapshot.data ?? []); return Skeletonizer( effect: PulseEffect( from: Colors.grey[800]!, to: Colors.grey[600]!, duration: const Duration(milliseconds: 800), ), - enabled: true, + enabled: isLoading, enableSwitchAnimation: true, switchAnimationConfig: const SwitchAnimationConfig( duration: Duration(milliseconds: 200), -- 2.49.1 From a1d57fc42424dd625e7f79b83de54c49c156a20f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 20:04:59 +0100 Subject: [PATCH 113/563] removed sample data for group view --- lib/main.dart | 73 --------------------------------------------------- 1 file changed, 73 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b19eb45..98c40f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -21,7 +19,6 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { - addSampleGroupData(context); return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', @@ -42,74 +39,4 @@ class GameTracker extends StatelessWidget { home: const CustomNavigationBar(), ); } - - Future addSampleGroupData(BuildContext context) async { - final db = Provider.of(context, listen: false); - if (await db.groupDao.getGroupCount() == 0) { - final List allPlayers = [ - Player(id: '1', name: 'Alex'), - Player(id: '2', name: 'Ben'), - Player(id: '3', name: 'Chris'), - Player(id: '4', name: 'Daniel'), - Player(id: '5', name: 'Max Mustermann'), - Player(id: '6', name: 'Sebastian'), - Player(id: '7', name: 'Jonathan'), - Player(id: '8', name: 'Alexander'), - ]; - - // 2. Erstelle und füge 8 Gruppen mit unterschiedlicher Spieleranzahl hinzu - await db.groupDao.addGroup( - group: Group( - id: '1', - name: 'Anfänger', - members: allPlayers.sublist(0, 3), - ), - ); // 3 Spieler - await db.groupDao.addGroup( - group: Group( - id: '2', - name: 'Die glorreichen Sieben', - members: allPlayers.sublist(0, 7), - ), - ); // 7 Spieler - await db.groupDao.addGroup( - group: Group( - id: '3', - name: 'Profis', - members: allPlayers.sublist(4, 8), - ), - ); // 4 Spieler - await db.groupDao.addGroup( - group: Group( - id: '4', - name: 'Duo Infernale', - members: [allPlayers[0], allPlayers[7]], - ), - ); // 2 Spieler - await db.groupDao.addGroup( - group: Group( - id: '5', - name: 'Die fantastischen Fünf', - members: allPlayers.sublist(1, 6), - ), - ); // 5 Spieler - await db.groupDao.addGroup( - group: Group( - id: '6', - name: 'Feierabend-Zocker', - members: allPlayers.sublist(0, 6), - ), - ); // 6 Spieler - await db.groupDao.addGroup( - group: Group(id: '7', name: 'Alle Mann an Bord!', members: allPlayers), - ); // 8 Spieler - await db.groupDao.addGroup( - group: Group( - id: '8', - name: 'Testgruppe Alpha', - members: [allPlayers[1], allPlayers[3]], - ), - ); - } - } } -- 2.49.1 From 10a45ac1f0cae5c0b00a8476a0a9e21fd4831df4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 16 Nov 2025 20:11:10 +0100 Subject: [PATCH 114/563] removed unneccessary double quotes --- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 8b3bffe..7f1f32d 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -51,7 +51,7 @@ class _GroupsViewState extends State { return const Center( child: TopCenteredMessage( icon: Icons.report, - title: "Error", + title: 'Error', message: 'Group data couldn\'t\nbe loaded.', ), ); @@ -61,7 +61,7 @@ class _GroupsViewState extends State { return const Center( child: TopCenteredMessage( icon: Icons.info, - title: "Info", + title: 'Info', message: 'No groups created yet.', ), ); -- 2.49.1 From a9dd3216cc18ecc8d7d842776c1cd3081f5c410b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 20:51:28 +0100 Subject: [PATCH 115/563] Implemented skeleton --- .../views/main_menu/home_view.dart | 260 +++++++++++------- pubspec.yaml | 1 + 2 files changed, 158 insertions(+), 103 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 141af77..57c4f61 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @@ -16,6 +17,7 @@ class HomeView extends StatefulWidget { class _HomeViewState extends State { late Future _gameCountFuture; late Future _groupCountFuture; + bool isLoading = true; @override initState() { @@ -23,120 +25,172 @@ class _HomeViewState extends State { final db = Provider.of(context, listen: false); _gameCountFuture = db.gameDao.getGameCount(); _groupCountFuture = db.groupDao.getGroupCount(); + + Future.wait([_gameCountFuture, _groupCountFuture]).then((_) { + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FutureBuilder( - future: _gameCountFuture, - builder: (context, snapshot) { - final int count = (snapshot.hasData) ? snapshot.data! : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Games', - icon: Icons.groups_rounded, - value: count, - ); - }, - ), - SizedBox(width: constraints.maxWidth * 0.05), - FutureBuilder( - future: _groupCountFuture, - builder: (context, snapshot) { - final int count = - (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Groups', - icon: Icons.groups_rounded, - value: count, - ); - }, - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: InfoTile( - width: constraints.maxWidth * 0.95, - title: 'Recent Games', - icon: Icons.timer, - content: const Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GameTile( - gameTitle: 'Gamenight', - gameType: 'Cabo', - ruleset: 'Lowest Points', - players: '5 Players', - winner: 'Leonard', - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - GameTile( - gameTitle: 'Schoolbreak', - gameType: 'Uno', - ruleset: 'Highest Points', - players: 'The Gang', - winner: 'Lina', - ), - SizedBox(height: 8), - ], - ), - ), - ), - ), - InfoTile( - width: constraints.maxWidth * 0.95, - title: 'Quick Create', - icon: Icons.add_box_rounded, - content: Column( - spacing: 8, + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton(text: 'Category 1', onPressed: () {}), - QuickCreateButton(text: 'Category 2', onPressed: () {}), - ], + FutureBuilder( + future: _gameCountFuture, + builder: (context, snapshot) { + final int count = (snapshot.hasData) + ? snapshot.data! + : 0; + return QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Games', + icon: Icons.groups_rounded, + value: count, + ); + }, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton(text: 'Category 3', onPressed: () {}), - QuickCreateButton(text: 'Category 4', onPressed: () {}), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton(text: 'Category 5', onPressed: () {}), - QuickCreateButton(text: 'Category 6', onPressed: () {}), - ], + SizedBox(width: constraints.maxWidth * 0.05), + FutureBuilder( + future: _groupCountFuture, + builder: (context, snapshot) { + final int count = + (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) + ? snapshot.data! + : 0; + return QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: count, + ); + }, ), ], ), - ), - ], + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Recent Games', + icon: Icons.timer, + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Skeleton.unite( + unite: true, + borderRadius: BorderRadius.circular(8), + child: const GameTile( + gameTitle: 'Gamenight', + gameType: 'Cabo', + ruleset: 'Lowest Points', + players: '5 Players', + winner: 'Leonard', + ), + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + Skeleton.unite( + unite: true, + borderRadius: BorderRadius.circular(8), + child: const GameTile( + gameTitle: 'Schoolbreak', + gameType: 'Uno', + ruleset: 'Highest Points', + players: 'The Gang', + winner: 'Lina', + ), + ), + SizedBox(height: 8), + ], + ), + ), + ), + ), + InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Quick Create', + icon: Icons.add_box_rounded, + content: Column( + spacing: 8, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 1', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 2', + onPressed: () {}, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 3', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 4', + onPressed: () {}, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 5', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 6', + onPressed: () {}, + ), + ], + ), + ], + ), + ), + ], + ), ), ); }, diff --git a/pubspec.yaml b/pubspec.yaml index 5fa8019..ab6e30b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: drift_flutter: ^0.2.4 path_provider: ^2.1.5 provider: ^6.1.5 + skeletonizer: ^2.1.0+1 dev_dependencies: flutter_test: -- 2.49.1 From 630836d40cd3d50104f54c0a786015028289e3ca Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 16 Nov 2025 21:53:04 +0100 Subject: [PATCH 116/563] typo --- test/db_tests/group_test.dart | 2 +- test/db_tests/player_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index e8e6316..d8d6ce2 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -36,7 +36,7 @@ void main() { await database.close(); }); group('group tests', () { - test('all groups get fetched correclty', () async { + test('all groups get fetched correctly', () async { final testgroup2 = Group( id: 'gr2', name: 'Second Group', diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 9f12faa..5258c66 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -24,7 +24,7 @@ void main() { }); group('player tests', () { - test('all players get fetched correclty', () async { + test('all players get fetched correctly', () async { final testPlayer2 = Player(id: 'gr2', name: 'Second Group'); await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); -- 2.49.1 From b5e7fe23ab335388eb8ee28fe781dc77fab3b899 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:10:08 +0100 Subject: [PATCH 117/563] changed fullwidthbutton to include size, borderColor and infillColor attributes --- .../widgets/full_width_button.dart | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index bd18c64..fc2ca78 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -1,10 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; class FullWidthButton extends StatelessWidget { - const FullWidthButton({super.key, required this.text, this.onPressed}); + const FullWidthButton({ + super.key, + required this.text, + required this.borderColor, + required this.infillColor, + required this.sizeRelativeToWidth, + required this.onPressed, + }); final String text; + final Color borderColor; + final Color infillColor; + final double sizeRelativeToWidth; final VoidCallback? onPressed; @override @@ -12,8 +21,12 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - minimumSize: Size(MediaQuery.sizeOf(context).width * 0.9, 60), - backgroundColor: CustomTheme.primaryColor, + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + backgroundColor: infillColor, + side: BorderSide(color: borderColor, width: 2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( -- 2.49.1 From a54495f915447d0b0b42cfa959b751dc77eb87da Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:10:33 +0100 Subject: [PATCH 118/563] implemented basic CreateGroupView without functionality --- .../create_group/create_group_view.dart | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 lib/presentation/views/main_menu/create_group/create_group_view.dart diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart new file mode 100644 index 0000000..63daf62 --- /dev/null +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class CreateGroupView extends StatefulWidget { + const CreateGroupView({super.key}); + + @override + State createState() => _CreateGroupViewState(); +} + +class _CreateGroupViewState extends State { + List selectedPlayers = [ + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + ]; + late Future> _allPlayersFuture; + late final List skeletonData = List.filled( + 7, + Player(id: '0', name: 'Player 0'), + ); + + @override + @override + void initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _allPlayersFuture = db.playerDao.getAllPlayers(); + } + + @override + Widget build(BuildContext context) { + addSamplePlayers(context); + return SafeArea( + child: Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + title: const Text( + "Create new group", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextField( + decoration: InputDecoration( + filled: true, + fillColor: CustomTheme.boxColor, + hint: Text("Group name", style: TextStyle(fontSize: 18)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ), + ), + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SearchBar( + constraints: BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: "Search for players", + hintStyle: WidgetStateProperty.all( + TextStyle(fontSize: 16), + ), + leading: Icon(Icons.search), + backgroundColor: WidgetStateProperty.all( + CustomTheme.boxColor, + ), + side: WidgetStateProperty.all( + BorderSide(color: CustomTheme.boxBorder), + ), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + SizedBox(height: 10), + Text( + "Ausgewählte Spieler: (X)", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var player in selectedPlayers) + Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 12), + Text( + player.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 3), + GestureDetector( + child: const Icon(Icons.close, size: 20), + onTap: () { + setState(() { + selectedPlayers.remove(player); + }); + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Text( + "Alle Spieler:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10), + FutureBuilder( + future: _allPlayersFuture, + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == + ConnectionState.waiting; + final List players = isLoading + ? skeletonData + : (snapshot.data ?? []); + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: + const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher + .defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: ListView.builder( + itemCount: players.length, + itemBuilder: + (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + players[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + IconButton( + icon: Icon(Icons.add, size: 20), + onPressed: () {}, + ), + ], + ), + ); //GroupTile(group: groups[index]); + }, + ), + ), + ); + }, + ), + ], + ), + ), + ), + FullWidthButton( + text: "Create group", + infillColor: CustomTheme.primaryColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.95, + onPressed: () {}, + ), + SizedBox(height: 10), + FullWidthButton( + text: "Cancel", + infillColor: CustomTheme.boxColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.95, + onPressed: () { + Navigator.pop(context); + }, + ), + SizedBox(height: 20), + ], + ), + ), + ); + } + + Future addSamplePlayers(BuildContext context) async { + final db = Provider.of(context, listen: false); + final playerCount = await db.playerDao.getPlayerCount(); + if (playerCount == 0) { + for (int i = 1; i <= 10; i++) { + final player = Player(id: '$i', name: 'Spieler $i'); + await db.playerDao.addPlayer(player: player); + } + print("10 Beispiel-Spieler wurden zur Datenbank hinzugefügt."); + final players = await db.playerDao.getAllPlayers(); + for (int i = 0; i < players.length; i++) { + print(players[i]); + } + } + } +} -- 2.49.1 From e4de8fdb253906e14723e955ec01aa9fd7df358c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:11:47 +0100 Subject: [PATCH 119/563] added missing attributes for FullWidthButton in groups view --- .../views/main_menu/groups_view.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7f1f32d..7c5e3d5 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -103,7 +104,22 @@ class _GroupsViewState extends State { Positioned( bottom: 80, - child: FullWidthButton(text: 'Create Group', onPressed: () {}), + child: FullWidthButton( + text: 'Create Group', + infillColor: CustomTheme.primaryColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.90, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + }, + ), ), ], ), -- 2.49.1 From 2fc7eab1ac465f53ff9e3f2e417f6a9609d8a8ed Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 13:19:05 +0100 Subject: [PATCH 120/563] Corrected game tile skeleton --- .../views/main_menu/home_view.dart | 41 ++++++++----------- lib/presentation/widgets/game_tile.dart | 35 +++++++++------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 57c4f61..cf6288a 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -26,7 +26,8 @@ class _HomeViewState extends State { _gameCountFuture = db.gameDao.getGameCount(); _groupCountFuture = db.groupDao.getGroupCount(); - Future.wait([_gameCountFuture, _groupCountFuture]).then((_) { + Future.wait([_gameCountFuture, _groupCountFuture]).then((_) async { + await Future.delayed(const Duration(milliseconds: 50)); if (mounted) { setState(() { isLoading = false; @@ -102,37 +103,29 @@ class _HomeViewState extends State { width: constraints.maxWidth * 0.95, title: 'Recent Games', icon: Icons.timer, - content: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40.0), + content: const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Skeleton.unite( - unite: true, - borderRadius: BorderRadius.circular(8), - child: const GameTile( - gameTitle: 'Gamenight', - gameType: 'Cabo', - ruleset: 'Lowest Points', - players: '5 Players', - winner: 'Leonard', - ), + GameTile( + gameTitle: 'Gamenight', + gameType: 'Cabo', + ruleset: 'Lowest Points', + players: '5 Players', + winner: 'Leonard', ), - const Padding( + Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - Skeleton.unite( - unite: true, - borderRadius: BorderRadius.circular(8), - child: const GameTile( - gameTitle: 'Schoolbreak', - gameType: 'Uno', - ruleset: 'Highest Points', - players: 'The Gang', - winner: 'Lina', - ), + GameTile( + gameTitle: 'Schoolbreak', + gameType: 'Uno', + ruleset: 'Highest Points', + players: 'The Gang', + winner: 'Lina', ), SizedBox(height: 8), ], diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/game_tile.dart index c79a6b7..fcd2f65 100644 --- a/lib/presentation/widgets/game_tile.dart +++ b/lib/presentation/widgets/game_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class GameTile extends StatefulWidget { final String gameTitle; @@ -48,9 +49,11 @@ class _GameTileState extends State { borderRadius: BorderRadius.circular(4), color: CustomTheme.primaryColor, ), - child: Text( - widget.ruleset, - style: const TextStyle(fontWeight: FontWeight.bold), + child: Skeleton.ignore( + child: Text( + widget.ruleset, + style: const TextStyle(fontWeight: FontWeight.bold), + ), ), ), Center( @@ -68,19 +71,21 @@ class _GameTileState extends State { borderRadius: BorderRadius.circular(4), color: Colors.yellow.shade300, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.emoji_events, color: Colors.black, size: 20), - Text( - widget.winner, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black87, + child: Skeleton.ignore( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.emoji_events, color: Colors.black, size: 20), + Text( + widget.winner, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + ), ), - ), - ], + ], + ), ), ), ), -- 2.49.1 From 47bb090e725be017e0a8e252c35e25b26b6e9ef2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 18:23:07 +0100 Subject: [PATCH 121/563] added option to choose disabledBackgroundColor in FullWidthButton --- lib/presentation/widgets/full_width_button.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index fc2ca78..fe9913c 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -6,6 +6,7 @@ class FullWidthButton extends StatelessWidget { required this.text, required this.borderColor, required this.infillColor, + this.disabledInfillColor, required this.sizeRelativeToWidth, required this.onPressed, }); @@ -13,6 +14,7 @@ class FullWidthButton extends StatelessWidget { final String text; final Color borderColor; final Color infillColor; + final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; @@ -21,6 +23,7 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( + disabledBackgroundColor: disabledInfillColor, minimumSize: Size( MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, -- 2.49.1 From 35f2f8754ae7d2b4f6644e8cf0e34035fd8663e1 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 18:24:30 +0100 Subject: [PATCH 122/563] added functionality to create group --- .../create_group/create_group_view.dart | 197 +++++++++++++----- 1 file changed, 142 insertions(+), 55 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 63daf62..13f1b7c 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -15,24 +16,30 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - List selectedPlayers = [ - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - ]; + List selectedPlayers = []; + List suggestedPlayers = []; + List allPlayers = []; + late final AppDatabase db; late Future> _allPlayersFuture; late final List skeletonData = List.filled( 7, Player(id: '0', name: 'Player 0'), ); + final _groupNameController = TextEditingController(); + final _searchBarController = TextEditingController(); @override @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); _allPlayersFuture = db.playerDao.getAllPlayers(); + _allPlayersFuture.then((loadedPlayers) { + setState(() { + allPlayers = loadedPlayers; + suggestedPlayers = loadedPlayers; + }); + }); } @override @@ -55,6 +62,10 @@ class _CreateGroupViewState extends State { Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextField( + controller: _groupNameController, + onChanged: (value) { + setState(() {}); + }, decoration: InputDecoration( filled: true, fillColor: CustomTheme.boxColor, @@ -90,6 +101,7 @@ class _CreateGroupViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SearchBar( + controller: _searchBarController, constraints: BoxConstraints(maxHeight: 45, minHeight: 45), hintText: "Search for players", hintStyle: WidgetStateProperty.all( @@ -107,10 +119,23 @@ class _CreateGroupViewState extends State { borderRadius: BorderRadius.circular(12), ), ), + onChanged: (value) { + setState(() { + if (value.isEmpty) { + suggestedPlayers = allPlayers; + } else { + suggestedPlayers = allPlayers.where((player) { + return player.name.toLowerCase().contains( + value.toLowerCase(), + ); + }).toList(); + } + }); + }, ), SizedBox(height: 10), Text( - "Ausgewählte Spieler: (X)", + "Ausgewählte Spieler: (${selectedPlayers.length})", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -123,7 +148,7 @@ class _CreateGroupViewState extends State { spacing: 8.0, runSpacing: 8.0, children: [ - for (var player in selectedPlayers) + for (var selectedPlayer in selectedPlayers) Container( padding: EdgeInsets.all(5), decoration: BoxDecoration( @@ -136,7 +161,7 @@ class _CreateGroupViewState extends State { children: [ SizedBox(width: 12), Text( - player.name, + selectedPlayer.name, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, @@ -147,7 +172,7 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { - selectedPlayers.remove(player); + selectedPlayers.remove(selectedPlayer); }); }, ), @@ -183,7 +208,10 @@ class _CreateGroupViewState extends State { } if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { + (!snapshot.hasData || + snapshot.data!.isEmpty || + (suggestedPlayers.isEmpty && + allPlayers.isEmpty))) { return const Center( child: TopCenteredMessage( icon: Icons.info, @@ -195,9 +223,6 @@ class _CreateGroupViewState extends State { final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List players = isLoading - ? skeletonData - : (snapshot.data ?? []); return Expanded( child: Skeletonizer( effect: PulseEffect( @@ -217,48 +242,69 @@ class _CreateGroupViewState extends State { layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, ), - child: ListView.builder( - itemCount: players.length, - itemBuilder: - (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, ), - borderRadius: BorderRadius.circular( - 12, + padding: const EdgeInsets.symmetric( + horizontal: 10, ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - players[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: + BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + suggestedPlayers[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), - ), - IconButton( - icon: Icon(Icons.add, size: 20), - onPressed: () {}, - ), - ], - ), - ); //GroupTile(group: groups[index]); - }, - ), + IconButton( + icon: Icon( + Icons.add, + size: 20, + ), + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + } + }); + }, + ), + ], + ), + ); + }, + ), ), ); }, @@ -271,14 +317,39 @@ class _CreateGroupViewState extends State { text: "Create group", infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, + disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, - onPressed: () {}, + onPressed: + (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) + ? null + : () { + String id = "ID_" + _groupNameController.text; + String name = _groupNameController.text; + List members = selectedPlayers; + db.groupDao.addGroup( + group: Group(id: id, name: name, members: members), + ); + print(name); + print(id); + for (int i = 0; i < members.length; i++) { + print(members[i].name); + print(members[i].id); + } + if (true) { + //eigentlich wenn create group erfolgreich + _groupNameController.clear(); + _searchBarController.clear(); + selectedPlayers.clear(); + } + setState(() {}); + }, ), SizedBox(height: 10), FullWidthButton( text: "Cancel", infillColor: CustomTheme.boxColor, borderColor: CustomTheme.primaryColor, + disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, onPressed: () { Navigator.pop(context); @@ -293,6 +364,22 @@ class _CreateGroupViewState extends State { Future addSamplePlayers(BuildContext context) async { final db = Provider.of(context, listen: false); + /*await db.groupDao.addGroup( + group: Group( + id: "dg1", + name: "Debug Gruppe 1", + members: [ + Player(id: '1', name: 'Spieler 1'), + Player(id: '2', name: 'Spieler 2'), + Player(id: '3', name: 'Spieler 3'), + ], + ), + ); + final group = await db.groupDao.getGroupById(groupId: "dg1"); + print(group.name); + print(group.id); + print(group.members.length); + */ final playerCount = await db.playerDao.getPlayerCount(); if (playerCount == 0) { for (int i = 1; i <= 10; i++) { -- 2.49.1 From a7f6a53b9ccc20a21b64414b3dd7ef707b085671 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 19:00:38 +0100 Subject: [PATCH 123/563] removed double @override --- .../views/main_menu/create_group/create_group_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 13f1b7c..631f3ae 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -28,7 +28,6 @@ class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); - @override @override void initState() { super.initState(); -- 2.49.1 From f8b6c00d5d8f1ceaa67f3862f1d379e2241a327b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 19:26:36 +0100 Subject: [PATCH 124/563] Fixed return --- lib/data/dao/group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..cc680a3 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -60,8 +60,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { await Future.wait( group.members.map((player) => db.playerDao.addPlayer(player: player)), ); - return true; }); + return true; } return false; } -- 2.49.1 From 6b2fb18ec095b3feeffe344f7ecdd9caabf4faa1 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 20:20:56 +0100 Subject: [PATCH 125/563] fixed groups not getting added & added feature to remove player from all players when selected --- .../create_group/create_group_view.dart | 67 +++++++++---------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 631f3ae..b27fb6a 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -35,6 +35,7 @@ class _CreateGroupViewState extends State { _allPlayersFuture = db.playerDao.getAllPlayers(); _allPlayersFuture.then((loadedPlayers) { setState(() { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = loadedPlayers; suggestedPlayers = loadedPlayers; }); @@ -171,7 +172,11 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { + suggestedPlayers.add(selectedPlayer); selectedPlayers.remove(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); }); }, ), @@ -295,6 +300,15 @@ class _CreateGroupViewState extends State { selectedPlayers.add( suggestedPlayers[index], ); + selectedPlayers.sort( + (a, b) => + a.name.compareTo( + b.name, + ), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); } }); }, @@ -321,39 +335,34 @@ class _CreateGroupViewState extends State { onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null - : () { + : () async { String id = "ID_" + _groupNameController.text; String name = _groupNameController.text; List members = selectedPlayers; - db.groupDao.addGroup( + bool success = await db.groupDao.addGroup( group: Group(id: id, name: name, members: members), ); - print(name); - print(id); - for (int i = 0; i < members.length; i++) { - print(members[i].name); - print(members[i].id); - } - if (true) { - //eigentlich wenn create group erfolgreich + if (success) { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + "Error while creating group, please try again", + style: TextStyle(color: Colors.white), + ), + ), + ), + ); } setState(() {}); }, ), - SizedBox(height: 10), - FullWidthButton( - text: "Cancel", - infillColor: CustomTheme.boxColor, - borderColor: CustomTheme.primaryColor, - disabledInfillColor: CustomTheme.boxColor, - sizeRelativeToWidth: 0.95, - onPressed: () { - Navigator.pop(context); - }, - ), SizedBox(height: 20), ], ), @@ -363,22 +372,6 @@ class _CreateGroupViewState extends State { Future addSamplePlayers(BuildContext context) async { final db = Provider.of(context, listen: false); - /*await db.groupDao.addGroup( - group: Group( - id: "dg1", - name: "Debug Gruppe 1", - members: [ - Player(id: '1', name: 'Spieler 1'), - Player(id: '2', name: 'Spieler 2'), - Player(id: '3', name: 'Spieler 3'), - ], - ), - ); - final group = await db.groupDao.getGroupById(groupId: "dg1"); - print(group.name); - print(group.id); - print(group.members.length); - */ final playerCount = await db.playerDao.getPlayerCount(); if (playerCount == 0) { for (int i = 1; i <= 10; i++) { -- 2.49.1 From c3a2ac77b0de0918a3d38c6cc72ded946de6d08d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 21:19:17 +0100 Subject: [PATCH 126/563] fixed color change in appbar when scrolling --- .../views/main_menu/create_group/create_group_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index b27fb6a..316f2d3 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -50,6 +50,7 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, title: const Text( "Create new group", style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), -- 2.49.1 From 3e89bfd641e5245264a5250fa7b320cb5072b127 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 21:34:51 +0100 Subject: [PATCH 127/563] added info message for when all players are selected --- .../create_group/create_group_view.dart | 241 +++++++++--------- 1 file changed, 116 insertions(+), 125 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 316f2d3..907671b 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -36,8 +36,8 @@ class _CreateGroupViewState extends State { _allPlayersFuture.then((loadedPlayers) { setState(() { loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = loadedPlayers; - suggestedPlayers = loadedPlayers; + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; }); }); } @@ -123,7 +123,7 @@ class _CreateGroupViewState extends State { onChanged: (value) { setState(() { if (value.isEmpty) { - suggestedPlayers = allPlayers; + suggestedPlayers = [...allPlayers]; } else { suggestedPlayers = allPlayers.where((player) { return player.name.toLowerCase().contains( @@ -197,131 +197,122 @@ class _CreateGroupViewState extends State { SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), - ); - } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (suggestedPlayers.isEmpty && - allPlayers.isEmpty))) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == - ConnectionState.waiting; - return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), - child: - (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) - ? TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - 'No players found with that name.', - ) - : ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty || + (selectedPlayers.isEmpty && + allPlayers.isEmpty))) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: + AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + suggestedPlayers[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), - borderRadius: - BorderRadius.circular(12), ), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - suggestedPlayers[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - IconButton( - icon: Icon( - Icons.add, - size: 20, - ), - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => - a.name.compareTo( - b.name, - ), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ), - ], + IconButton( + icon: Icon(Icons.add, size: 20), + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name + .compareTo(b.name), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, ), - ); - }, - ), - ), - ); - }, + ], + ), + ); + }, + ), + ), + ); + }, ), ], ), -- 2.49.1 From 3ca081419b9f3ec43d741727fad5b60ef0b4e689 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:04 +0100 Subject: [PATCH 128/563] Implemented new SettingsListTile --- .../widgets/tiles/settings_list_tile.dart | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lib/presentation/widgets/tiles/settings_list_tile.dart diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart new file mode 100644 index 0000000..1174627 --- /dev/null +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class SettingsListTile extends StatelessWidget { + final VoidCallback? onPressed; + final IconData icon; + final String title; + final Widget? suffixWidget; + const SettingsListTile({ + super.key, + required this.title, + required this.icon, + this.suffixWidget, + this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.95, + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: GestureDetector( + onTap: onPressed ?? () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: CustomTheme.primaryColor, + shape: BoxShape.circle, + ), + child: Icon(icon, size: 24), + ), + const SizedBox(width: 16), + Text(title, style: const TextStyle(fontSize: 18)), + ], + ), + if (suffixWidget != null) + suffixWidget! + else + const SizedBox.shrink(), + ], + ), + ), + ), + ), + ), + ); + } +} -- 2.49.1 From 62acc87e0e804c3a366145d2af32ab0d7ecb9ab0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:41 +0100 Subject: [PATCH 129/563] Moved game tile in tiles folder --- lib/presentation/views/main_menu/home_view.dart | 2 +- lib/presentation/widgets/{ => tiles}/game_tile.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/presentation/widgets/{ => tiles}/game_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index cf6288a..53816d0 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart similarity index 100% rename from lib/presentation/widgets/game_tile.dart rename to lib/presentation/widgets/tiles/game_tile.dart -- 2.49.1 From 2076e45fd505f1405d71e6efba05d87f86a9354d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:23:52 +0100 Subject: [PATCH 130/563] Created new layout for settings view --- .../views/main_menu/settings_view.dart | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c3e75f3..75ea163 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; class SettingsView extends StatelessWidget { const SettingsView({super.key}); @@ -6,8 +8,77 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Einstellungen')), - body: const Center(child: Text('Settings View')), + appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), + backgroundColor: CustomTheme.backgroundColor, + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(24, 0, 24, 10), + child: Text( + textAlign: TextAlign.start, + 'Menü', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + 'Einstellungen', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + + SettingsListTile( + title: 'Export Data', + icon: Icons.upload_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Export Data'), + ), + SettingsListTile( + title: 'Import Data', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Import Data'), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + 'Example Headline', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: 'Example Tile', + icon: Icons.upload_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Example Tile'), + ), + SettingsListTile( + title: 'Example Tile', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => print('Example Tile'), + ), + ], + ), + ), + ), ); } } -- 2.49.1 From 178aaa964323e4ea7fbcb2dae09fc53a4c892b94 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 23:25:12 +0100 Subject: [PATCH 131/563] Added function headers --- .../views/main_menu/settings_view.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 75ea163..b05ae8d 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -21,7 +21,7 @@ class SettingsView extends StatelessWidget { padding: EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - 'Menü', + 'Menu', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -32,7 +32,7 @@ class SettingsView extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), child: Text( textAlign: TextAlign.start, - 'Einstellungen', + 'Settings', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -44,13 +44,13 @@ class SettingsView extends StatelessWidget { title: 'Export Data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Export Data'), + onPressed: () => exportData(), ), SettingsListTile( title: 'Import Data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Import Data'), + onPressed: () => importData(), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), @@ -81,4 +81,10 @@ class SettingsView extends StatelessWidget { ), ); } + + // TODO: Implement export functionality + void exportData() {} + + // TODO: Implement import functionality + void importData() {} } -- 2.49.1 From c31d757615547a89344387c8eca2324898970b15 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:00:09 +0100 Subject: [PATCH 132/563] fixed renderoverflow for long player & group names in create group view and group view --- .../create_group/create_group_view.dart | 32 +++++++++++------ .../widgets/tiles/group_tile.dart | 36 +++++++++++-------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 907671b..823b6ff 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -70,7 +70,11 @@ class _CreateGroupViewState extends State { decoration: InputDecoration( filled: true, fillColor: CustomTheme.boxColor, - hint: Text("Group name", style: TextStyle(fontSize: 18)), + hint: Text( + "Group name", + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 18), + ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), borderSide: BorderSide(color: CustomTheme.boxBorder), @@ -161,11 +165,14 @@ class _CreateGroupViewState extends State { mainAxisSize: MainAxisSize.min, children: [ SizedBox(width: 12), - Text( - selectedPlayer.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, + Flexible( + child: Text( + selectedPlayer.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), ), SizedBox(width: 3), @@ -277,11 +284,14 @@ class _CreateGroupViewState extends State { MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ - Text( - suggestedPlayers[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + Flexible( + child: Text( + suggestedPlayers[index].name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), ), IconButton( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 448c68c..12d016e 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -24,24 +24,29 @@ class GroupTile extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - group.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + Flexible( + child: Text( + group.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), ), - const Spacer(), - Text( - '${group.members.length}', - style: const TextStyle( - fontWeight: FontWeight.w900, - fontSize: 18, - ), + Row( + children: [ + Text( + '${group.members.length}', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + const SizedBox(width: 3), + const Icon(Icons.group, size: 22), + ], ), - const SizedBox(width: 3), - const Icon(Icons.group, size: 22), ], ), const SizedBox(height: 5), @@ -64,6 +69,7 @@ class GroupTile extends StatelessWidget { child: Skeleton.ignore( child: Text( member.name, + overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, -- 2.49.1 From 412d1fd334b5475f5f1fd4f02c9ab5c7c6d639f8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:08:07 +0100 Subject: [PATCH 133/563] fixed search bugs where duplicates where created or search results were wrong --- .../create_group/create_group_view.dart | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 823b6ff..87ef5bc 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -130,9 +130,12 @@ class _CreateGroupViewState extends State { suggestedPlayers = [...allPlayers]; } else { suggestedPlayers = allPlayers.where((player) { - return player.name.toLowerCase().contains( - value.toLowerCase(), - ); + final bool nameMatches = player.name + .toLowerCase() + .contains(value.toLowerCase()); + final bool isNotSelected = !selectedPlayers + .contains(player); + return nameMatches && isNotSelected; }).toList(); } }); @@ -180,11 +183,19 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { - suggestedPlayers.add(selectedPlayer); + final currentSearch = _searchBarController + .text + .toLowerCase(); selectedPlayers.remove(selectedPlayer); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); + if (currentSearch.isEmpty || + selectedPlayer.name + .toLowerCase() + .contains(currentSearch)) { + suggestedPlayers.add(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } }); }, ), -- 2.49.1 From a5e508dbdaedb2e22039f0015de786d2e85b5a84 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:29:24 +0100 Subject: [PATCH 134/563] Refresh group list after adding a new group --- lib/presentation/views/main_menu/groups_view.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7c5e3d5..bdb3d4a 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -19,6 +19,7 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late Future> _allGroupsFuture; + late final AppDatabase db; final player = Player(id: 'p1', name: 'Sample'); late final List skeletonData = List.filled( @@ -33,7 +34,7 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); } @@ -109,8 +110,8 @@ class _GroupsViewState extends State { infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, sizeRelativeToWidth: 0.90, - onPressed: () { - Navigator.push( + onPressed: () async { + await Navigator.push( context, MaterialPageRoute( builder: (context) { @@ -118,6 +119,9 @@ class _GroupsViewState extends State { }, ), ); + setState(() { + _allGroupsFuture = db.groupDao.getAllGroups(); + }); }, ), ), -- 2.49.1 From 05c41707ca96f997f3608fc1206fc8e22913abda Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:34:09 +0100 Subject: [PATCH 135/563] Refactor: Remove sample player generation code --- .../create_group/create_group_view.dart | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 87ef5bc..1f616a2 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -44,7 +44,6 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { - addSamplePlayers(context); return SafeArea( child: Scaffold( backgroundColor: CustomTheme.backgroundColor, @@ -382,20 +381,4 @@ class _CreateGroupViewState extends State { ), ); } - - Future addSamplePlayers(BuildContext context) async { - final db = Provider.of(context, listen: false); - final playerCount = await db.playerDao.getPlayerCount(); - if (playerCount == 0) { - for (int i = 1; i <= 10; i++) { - final player = Player(id: '$i', name: 'Spieler $i'); - await db.playerDao.addPlayer(player: player); - } - print("10 Beispiel-Spieler wurden zur Datenbank hinzugefügt."); - final players = await db.playerDao.getAllPlayers(); - for (int i = 0; i < players.length; i++) { - print(players[i]); - } - } - } } -- 2.49.1 From 282841ecf16816f79d4af0c3de1aacfaff0f24b4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 20:07:53 +0100 Subject: [PATCH 136/563] Implemented uuid for all dtos --- lib/data/dto/game.dart | 7 ++++--- lib/data/dto/group.dart | 4 +++- lib/data/dto/player.dart | 4 +++- lib/presentation/views/main_menu/groups_view.dart | 6 +++--- pubspec.yaml | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 125af48..c84779d 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -1,5 +1,6 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:uuid/uuid.dart'; class Game { final String id; @@ -9,12 +10,12 @@ class Game { final String winner; Game({ + String? id, + required this.name, this.players, this.group, this.winner = '', - required this.id, - required this.name, - }); + }) : id = id ?? const Uuid().v4(); @override String toString() { diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 427a52b..0420477 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -1,11 +1,13 @@ import 'package:game_tracker/data/dto/player.dart'; +import 'package:uuid/uuid.dart'; class Group { final String id; final String name; final List members; - Group({required this.id, required this.name, required this.members}); + Group({String? id, required this.name, required this.members}) + : id = id ?? const Uuid().v4(); @override String toString() { diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 631a51f..1b00c2c 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -1,8 +1,10 @@ +import 'package:uuid/uuid.dart'; + class Player { final String id; final String name; - Player({required this.id, required this.name}); + Player({String? id, required this.name}) : id = id ?? const Uuid().v4(); @override String toString() { diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7f1f32d..cfeb0c3 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -19,12 +19,11 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late Future> _allGroupsFuture; - final player = Player(id: 'p1', name: 'Sample'); + final player = Player(name: 'Skeleton Player'); late final List skeletonData = List.filled( 7, Group( - id: '0', - name: 'Sample Game', + name: 'Skeleton Game', members: [player, player, player, player, player, player], ), ); @@ -34,6 +33,7 @@ class _GroupsViewState extends State { super.initState(); final db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); + print('Skeleton Data: $skeletonData'); } @override diff --git a/pubspec.yaml b/pubspec.yaml index ab6e30b..fbbc01a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.5 skeletonizer: ^2.1.0+1 + uuid: ^4.5.2 dev_dependencies: flutter_test: -- 2.49.1 From 1882d0007bd42daadec4f95bd749640203dcdf88 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:09:57 +0100 Subject: [PATCH 137/563] created widgets for search bar list tile, selected tile and text input field in create groups view --- .../widgets/custom_search_bar.dart | 36 +++++++++++++++ .../widgets/text_input_field.dart | 38 ++++++++++++++++ .../widgets/tiles/text_icon_list_tile.dart | 42 ++++++++++++++++++ .../widgets/tiles/text_icon_tile.dart | 44 +++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 lib/presentation/widgets/custom_search_bar.dart create mode 100644 lib/presentation/widgets/text_input_field.dart create mode 100644 lib/presentation/widgets/tiles/text_icon_list_tile.dart create mode 100644 lib/presentation/widgets/tiles/text_icon_tile.dart diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart new file mode 100644 index 0000000..d0f66e8 --- /dev/null +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomSearchBar extends StatelessWidget { + final TextEditingController controller; + final String hintText; + final ValueChanged? onChanged; + final BoxConstraints? constraints; + + const CustomSearchBar({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + this.constraints, + }); + + @override + Widget build(BuildContext context) { + return SearchBar( + controller: controller, + constraints: + constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: hintText, + onChanged: onChanged, + hintStyle: MaterialStateProperty.all(const TextStyle(fontSize: 16)), + leading: const Icon(Icons.search), + backgroundColor: MaterialStateProperty.all(CustomTheme.boxColor), + side: MaterialStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + elevation: MaterialStateProperty.all(0), + ); + } +} diff --git a/lib/presentation/widgets/text_input_field.dart b/lib/presentation/widgets/text_input_field.dart new file mode 100644 index 0000000..6cd9d75 --- /dev/null +++ b/lib/presentation/widgets/text_input_field.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextInputField extends StatelessWidget { + final TextEditingController controller; + final ValueChanged? onChanged; + final String hintText; + + const TextInputField({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + onChanged: onChanged, + decoration: InputDecoration( + filled: true, + fillColor: CustomTheme.boxColor, + hintText: hintText, + hintStyle: const TextStyle(fontSize: 18), + enabledBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart new file mode 100644 index 0000000..b32504f --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class IconListTile extends StatelessWidget { + final String text; + final IconData icon; + final VoidCallback onPressed; + + const IconListTile({ + super.key, + required this.text, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + ), + IconButton(icon: Icon(icon, size: 20), onPressed: onPressed), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart new file mode 100644 index 0000000..52174bd --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextIconTile extends StatelessWidget { + final String text; + final IconData? icon; + final VoidCallback? onIconTap; + + const TextIconTile({ + super.key, + required this.text, + this.icon, + this.onIconTap, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) const SizedBox(width: 3), + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ), + if (icon != null) ...[ + const SizedBox(width: 3), + GestureDetector(onTap: onIconTap, child: Icon(icon, size: 20)), + ], + ], + ), + ); + } +} -- 2.49.1 From 8f9289617f63b4f66d5131a4f9e8e61df8f2c0ac Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:10:26 +0100 Subject: [PATCH 138/563] changed group tile to use standardized text icon tile --- .../widgets/tiles/group_tile.dart | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 12d016e..d87cc12 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); @@ -56,27 +56,7 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) - Container( - padding: const EdgeInsets.symmetric( - vertical: 5, - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.onBoxColor, - borderRadius: BorderRadius.circular(12), - ), - child: Skeleton.ignore( - child: Text( - member.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ), + for (var member in group.members) TextIconTile(text: member.name), ], ), const SizedBox(height: 2.5), -- 2.49.1 From c0ff2bf6777d90393ae8941dbd2291c4cf5aae4d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 20:10:29 +0100 Subject: [PATCH 139/563] Removed unnecessary id declarations in tests --- test/db_tests/game_test.dart | 21 ++++++--------------- test/db_tests/group_test.dart | 20 ++++++++------------ test/db_tests/player_test.dart | 4 ++-- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index bee3ff8..29b0233 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -24,21 +24,12 @@ void main() { ), ); - player1 = Player(id: 'p1', name: 'Alice'); - player2 = Player(id: 'p2', name: 'Bob'); - player3 = Player(id: 'p3', name: 'Charlie'); - player4 = Player(id: 'p4', name: 'Diana'); - testgroup = Group( - id: 'gr1', - name: 'Test Group', - members: [player1, player2, player3], - ); - testgame = Game( - id: 'ga1', - name: 'Test Game', - group: testgroup, - players: [player4], - ); + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + testgroup = Group(name: 'Test Group', members: [player1, player2, player3]); + testgame = Game(name: 'Test Game', group: testgroup, players: [player4]); }); tearDown(() async { await database.close(); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index d8d6ce2..3a9d8ca 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -22,15 +22,11 @@ void main() { ), ); - player1 = Player(id: 'p1', name: 'Alice'); - player2 = Player(id: 'p2', name: 'Bob'); - player3 = Player(id: 'p3', name: 'Charlie'); - player4 = Player(id: 'p4', name: 'Diana'); - testgroup = Group( - id: 'gr1', - name: 'Test Group', - members: [player1, player2, player3], - ); + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + testgroup = Group(name: 'Test Group', members: [player1, player2, player3]); }); tearDown(() async { await database.close(); @@ -121,12 +117,12 @@ void main() { expect(playerAdded, true); - final playerAdded2 = await database.playerGroupDao.isPlayerInGroup( - playerId: 'a', + final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( + playerId: '', groupId: testgroup.id, ); - expect(playerAdded2, false); + expect(playerNotAdded, true); expect(playerAdded, true); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 5258c66..91f4acb 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -17,7 +17,7 @@ void main() { ), ); - testPlayer = Player(id: 'test_id', name: 'Test Player'); + testPlayer = Player(name: 'Test Player'); }); tearDown(() async { await database.close(); @@ -25,7 +25,7 @@ void main() { group('player tests', () { test('all players get fetched correctly', () async { - final testPlayer2 = Player(id: 'gr2', name: 'Second Group'); + final testPlayer2 = Player(name: 'Second Group'); await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); -- 2.49.1 From 77812842893db5d4c122761d5a74580331dc1639 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:10:48 +0100 Subject: [PATCH 140/563] changed to use standardized tiles and fixed search bug --- .../create_group/create_group_view.dart | 330 +++++++----------- 1 file changed, 128 insertions(+), 202 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 1f616a2..1a84956 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -3,10 +3,15 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; +import 'package:uuid/uuid.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -61,29 +66,12 @@ class _CreateGroupViewState extends State { children: [ Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: TextField( + child: TextInputField( controller: _groupNameController, + hintText: 'Group name', onChanged: (value) { setState(() {}); }, - decoration: InputDecoration( - filled: true, - fillColor: CustomTheme.boxColor, - hint: Text( - "Group name", - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 18), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - ), ), ), Expanded( @@ -104,29 +92,16 @@ class _CreateGroupViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SearchBar( + CustomSearchBar( controller: _searchBarController, constraints: BoxConstraints(maxHeight: 45, minHeight: 45), hintText: "Search for players", - hintStyle: WidgetStateProperty.all( - TextStyle(fontSize: 16), - ), - leading: Icon(Icons.search), - backgroundColor: WidgetStateProperty.all( - CustomTheme.boxColor, - ), - side: WidgetStateProperty.all( - BorderSide(color: CustomTheme.boxBorder), - ), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), onChanged: (value) { setState(() { if (value.isEmpty) { - suggestedPlayers = [...allPlayers]; + suggestedPlayers = allPlayers.where((player) { + return !selectedPlayers.contains(player); + }).toList(); } else { suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name @@ -156,50 +131,25 @@ class _CreateGroupViewState extends State { runSpacing: 8.0, children: [ for (var selectedPlayer in selectedPlayers) - Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - color: CustomTheme.onBoxColor, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 12), - Flexible( - child: Text( - selectedPlayer.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - SizedBox(width: 3), - GestureDetector( - child: const Icon(Icons.close, size: 20), - onTap: () { - setState(() { - final currentSearch = _searchBarController - .text - .toLowerCase(); - selectedPlayers.remove(selectedPlayer); - if (currentSearch.isEmpty || - selectedPlayer.name - .toLowerCase() - .contains(currentSearch)) { - suggestedPlayers.add(selectedPlayer); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); - } - }); - }, - ), - ], - ), + TextIconTile( + text: selectedPlayer.name, + icon: Icons.close, + onIconTap: () { + setState(() { + final currentSearch = _searchBarController.text + .toLowerCase(); + selectedPlayers.remove(selectedPlayer); + if (currentSearch.isEmpty || + selectedPlayer.name.toLowerCase().contains( + currentSearch, + )) { + suggestedPlayers.add(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } + }); + }, ), ], ), @@ -214,125 +164,100 @@ class _CreateGroupViewState extends State { SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (selectedPlayers.isEmpty && - allPlayers.isEmpty))) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), - child: - (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) - ? TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - (selectedPlayers.length == - allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ) - : ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, - ), - borderRadius: BorderRadius.circular( - 12, - ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Text( - suggestedPlayers[index].name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - IconButton( - icon: Icon(Icons.add, size: 20), - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty || + (selectedPlayers.isEmpty && + allPlayers.isEmpty))) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == + ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: + const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher + .defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: + (BuildContext context, int index) { + return IconListTile( + text: suggestedPlayers[index] + .name, + icon: Icons.add, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => a.name - .compareTo(b.name), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ), - ], - ), - ); - }, - ), - ), - ); - }, + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name + .compareTo(b.name), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, + ); + }, + ), + ), + ); + }, ), ], ), @@ -348,11 +273,12 @@ class _CreateGroupViewState extends State { (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null : () async { - String id = "ID_" + _groupNameController.text; - String name = _groupNameController.text; - List members = selectedPlayers; bool success = await db.groupDao.addGroup( - group: Group(id: id, name: name, members: members), + group: Group( + id: Uuid().v4(), + name: _groupNameController.text, + members: selectedPlayers, + ), ); if (success) { _groupNameController.clear(); -- 2.49.1 From 2f260d7cbcfd01be9ec595f3427088e120970c58 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:14:18 +0100 Subject: [PATCH 141/563] Add uuid dependency --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index ab6e30b..fbbc01a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.5 skeletonizer: ^2.1.0+1 + uuid: ^4.5.2 dev_dependencies: flutter_test: -- 2.49.1 From f4ed122220347dc4acfd55cac784a329aee30953 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 20:15:10 +0100 Subject: [PATCH 142/563] Removed print --- lib/presentation/views/main_menu/groups_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index cfeb0c3..8edf20f 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -33,7 +33,6 @@ class _GroupsViewState extends State { super.initState(); final db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); - print('Skeleton Data: $skeletonData'); } @override -- 2.49.1 From 80290efa0b1d0046b0c18bea101dab4a82c05b83 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:37:41 +0100 Subject: [PATCH 143/563] rename FullWidthButton to CustomWidthButton --- .../views/main_menu/create_group/create_group_view.dart | 4 ++-- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- .../{full_width_button.dart => custom_width_button.dart} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/presentation/widgets/{full_width_button.dart => custom_width_button.dart} (93%) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 1a84956..8a89501 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; @@ -263,7 +263,7 @@ class _CreateGroupViewState extends State { ), ), ), - FullWidthButton( + CustomWidthButton( text: "Create group", infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index bdb3d4a..200f1d0 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.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/create_group/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; @@ -105,7 +105,7 @@ class _GroupsViewState extends State { Positioned( bottom: 80, - child: FullWidthButton( + child: CustomWidthButton( text: 'Create Group', infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/custom_width_button.dart similarity index 93% rename from lib/presentation/widgets/full_width_button.dart rename to lib/presentation/widgets/custom_width_button.dart index fe9913c..b336a79 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class FullWidthButton extends StatelessWidget { - const FullWidthButton({ +class CustomWidthButton extends StatelessWidget { + const CustomWidthButton({ super.key, required this.text, required this.borderColor, -- 2.49.1 From 73d8e7522cc2dbd72ded5a08a06fa63526648555 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 20:44:55 +0100 Subject: [PATCH 144/563] Added fifth player --- test/db_tests/game_test.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 29b0233..4f4b23f 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -12,6 +12,7 @@ void main() { late Player player2; late Player player3; late Player player4; + late Player player5; late Group testgroup; late Game testgame; @@ -28,8 +29,13 @@ void main() { player2 = Player(name: 'Bob'); player3 = Player(name: 'Charlie'); player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); testgroup = Group(name: 'Test Group', members: [player1, player2, player3]); - testgame = Game(name: 'Test Game', group: testgroup, players: [player4]); + testgame = Game( + name: 'Test Game', + group: testgroup, + players: [player4, player5], + ); }); tearDown(() async { await database.close(); -- 2.49.1 From c67f688a7723c82c7026759472439f29bf65e98c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:03 +0100 Subject: [PATCH 145/563] Refactor CreateGroupView: remove UUID generation, update tiles & fix async gaps --- .../create_group/create_group_view.dart | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 8a89501..81d5e36 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -11,7 +11,6 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:uuid/uuid.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -28,7 +27,7 @@ class _CreateGroupViewState extends State { late Future> _allPlayersFuture; late final List skeletonData = List.filled( 7, - Player(id: '0', name: 'Player 0'), + Player(name: 'Player 0'), ); final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); @@ -56,8 +55,8 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: const Text( - "Create new group", - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + 'Create new group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), @@ -94,8 +93,11 @@ class _CreateGroupViewState extends State { children: [ CustomSearchBar( controller: _searchBarController, - constraints: BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: "Search for players", + constraints: const BoxConstraints( + maxHeight: 45, + minHeight: 45, + ), + hintText: 'Search for players', onChanged: (value) { setState(() { if (value.isEmpty) { @@ -115,35 +117,34 @@ class _CreateGroupViewState extends State { }); }, ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( - "Ausgewählte Spieler: (${selectedPlayers.length})", - style: TextStyle( + 'Ausgewählte Spieler: (${selectedPlayers.length})', + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), + const SizedBox(height: 10), Wrap( alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, spacing: 8.0, runSpacing: 8.0, children: [ - for (var selectedPlayer in selectedPlayers) + for (var player in selectedPlayers) TextIconTile( - text: selectedPlayer.name, - icon: Icons.close, + text: player.name, onIconTap: () { setState(() { final currentSearch = _searchBarController.text .toLowerCase(); - selectedPlayers.remove(selectedPlayer); + selectedPlayers.remove(player); if (currentSearch.isEmpty || - selectedPlayer.name.toLowerCase().contains( + player.name.toLowerCase().contains( currentSearch, )) { - suggestedPlayers.add(selectedPlayer); + suggestedPlayers.add(player); suggestedPlayers.sort( (a, b) => a.name.compareTo(b.name), ); @@ -153,15 +154,15 @@ class _CreateGroupViewState extends State { ), ], ), - SizedBox(height: 10), - Text( - "Alle Spieler:", + const SizedBox(height: 10), + const Text( + 'Alle Spieler:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), + const SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, builder: @@ -216,7 +217,7 @@ class _CreateGroupViewState extends State { ), child: (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) + allPlayers.isNotEmpty) ? TopCenteredMessage( icon: Icons.info, title: 'Info', @@ -230,10 +231,9 @@ class _CreateGroupViewState extends State { itemCount: suggestedPlayers.length, itemBuilder: (BuildContext context, int index) { - return IconListTile( + return TextIconListTile( text: suggestedPlayers[index] .name, - icon: Icons.add, onPressed: () { setState(() { if (!selectedPlayers.contains( @@ -264,9 +264,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: "Create group", - infillColor: CustomTheme.primaryColor, - borderColor: CustomTheme.primaryColor, + text: 'Create group', disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, onPressed: @@ -275,7 +273,6 @@ class _CreateGroupViewState extends State { : () async { bool success = await db.groupDao.addGroup( group: Group( - id: Uuid().v4(), name: _groupNameController.text, members: selectedPlayers, ), @@ -284,14 +281,16 @@ class _CreateGroupViewState extends State { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); + if (!mounted) return; Navigator.pop(context); } else { + if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, - content: Center( + content: const Center( child: Text( - "Error while creating group, please try again", + 'Error while creating group, please try again', style: TextStyle(color: Colors.white), ), ), @@ -301,7 +300,7 @@ class _CreateGroupViewState extends State { setState(() {}); }, ), - SizedBox(height: 20), + const SizedBox(height: 20), ], ), ), -- 2.49.1 From 51a8c4ea58ab95ff732949868502acd66553537c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:17 +0100 Subject: [PATCH 146/563] Replace `MaterialStateProperty` with `WidgetStateProperty` in `CustomSearchBar` --- lib/presentation/widgets/custom_search_bar.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index d0f66e8..b482efb 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -23,14 +23,14 @@ class CustomSearchBar extends StatelessWidget { constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: hintText, onChanged: onChanged, - hintStyle: MaterialStateProperty.all(const TextStyle(fontSize: 16)), + hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), - backgroundColor: MaterialStateProperty.all(CustomTheme.boxColor), - side: MaterialStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), - shape: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), + side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + shape: WidgetStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), - elevation: MaterialStateProperty.all(0), + elevation: WidgetStateProperty.all(0), ); } } -- 2.49.1 From d65dd3d9838a9fa1075bcfd62460ec52ecb7f87e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:40 +0100 Subject: [PATCH 147/563] Refactor CustomWidthButton to use ButtonStyle enum and CustomTheme - Replaced `borderColor` and `infillColor` parameters with a `buttonStyle` parameter. - Introduced `ButtonStyle` enum (primary/secondary) to control styling. - Updated `CustomWidthButton` to derive colors from `CustomTheme` based on the selected `ButtonStyle`. --- .../widgets/custom_width_button.dart | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/custom_width_button.dart index b336a79..b0b9bd3 100644 --- a/lib/presentation/widgets/custom_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +enum ButtonStyle { primary, secondary } class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - required this.borderColor, - required this.infillColor, this.disabledInfillColor, + this.buttonStyle = ButtonStyle.primary, required this.sizeRelativeToWidth, required this.onPressed, }); final String text; - final Color borderColor; - final Color infillColor; final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; + final ButtonStyle buttonStyle; @override Widget build(BuildContext context) { @@ -28,8 +29,15 @@ class CustomWidthButton extends StatelessWidget { MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, ), - backgroundColor: infillColor, - side: BorderSide(color: borderColor, width: 2), + backgroundColor: buttonStyle == ButtonStyle.primary + ? CustomTheme.primaryColor + : CustomTheme.secondaryColor, + side: BorderSide( + color: buttonStyle == ButtonStyle.primary + ? CustomTheme.primaryColor + : CustomTheme.secondaryColor, + width: 2, + ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( -- 2.49.1 From e0c83988730735e444bd87ad0e631be605dc5799 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:43:38 +0100 Subject: [PATCH 148/563] remove custom colors from Create Group button in GroupsView --- lib/presentation/views/main_menu/groups_view.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 73f3509..7e6f59d 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -106,8 +106,6 @@ class _GroupsViewState extends State { bottom: 80, child: CustomWidthButton( text: 'Create Group', - infillColor: CustomTheme.primaryColor, - borderColor: CustomTheme.primaryColor, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( -- 2.49.1 From d3a63bd299e23cdfdf6470ff3793dce71076ad70 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:56:20 +0100 Subject: [PATCH 149/563] renamed IconListTile to TextIconListTile and replaced the icon parameter with iconEnabled in both TextIconListTile and TextIconTile --- .../widgets/tiles/text_icon_list_tile.dart | 28 +++++++++++++------ .../widgets/tiles/text_icon_tile.dart | 13 +++++---- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index b32504f..92d0251 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -class IconListTile extends StatelessWidget { +class TextIconListTile extends StatelessWidget { final String text; - final IconData icon; final VoidCallback onPressed; + final bool iconEnabled; - const IconListTile({ + const TextIconListTile({ super.key, required this.text, - required this.icon, required this.onPressed, + this.iconEnabled = true, }); @override @@ -28,13 +28,23 @@ class IconListTile extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Flexible( - child: Text( - text, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.5), + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), ), ), - IconButton(icon: Icon(icon, size: 20), onPressed: onPressed), + if (iconEnabled) + IconButton( + icon: const Icon(Icons.add, size: 20), + onPressed: onPressed, + ), ], ), ); diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 52174bd..2544837 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -3,14 +3,14 @@ import 'package:game_tracker/core/custom_theme.dart'; class TextIconTile extends StatelessWidget { final String text; - final IconData? icon; + final bool iconEnabled; final VoidCallback? onIconTap; const TextIconTile({ super.key, required this.text, - this.icon, this.onIconTap, + this.iconEnabled = true, }); @override @@ -25,7 +25,7 @@ class TextIconTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ - if (icon != null) const SizedBox(width: 3), + if (iconEnabled) const SizedBox(width: 3), Flexible( child: Text( text, @@ -33,9 +33,12 @@ class TextIconTile extends StatelessWidget { style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), ), - if (icon != null) ...[ + if (iconEnabled) ...[ const SizedBox(width: 3), - GestureDetector(onTap: onIconTap, child: Icon(icon, size: 20)), + GestureDetector( + onTap: onIconTap, + child: const Icon(Icons.close, size: 20), + ), ], ], ), -- 2.49.1 From d34163488531a2cc37ac0f5c1577dca3c8df0f7b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:56:31 +0100 Subject: [PATCH 150/563] Disable icon for members in group tile --- lib/presentation/widgets/tiles/group_tile.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index d87cc12..fa91477 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -56,7 +56,8 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) TextIconTile(text: member.name), + for (var member in group.members) + TextIconTile(text: member.name, iconEnabled: false), ], ), const SizedBox(height: 2.5), -- 2.49.1 From a8a81c21513049fa6391517ad90ef6b322e8bb6d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 22:33:46 +0100 Subject: [PATCH 151/563] Fixed gesture detector area --- .../widgets/tiles/settings_list_tile.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 1174627..d5c421f 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -21,16 +21,16 @@ class SettingsListTile extends StatelessWidget { child: Center( child: SizedBox( width: MediaQuery.of(context).size.width * 0.95, - child: Container( - margin: EdgeInsets.zero, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: GestureDetector( - onTap: onPressed ?? () {}, + child: GestureDetector( + onTap: onPressed ?? () {}, + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ -- 2.49.1 From d86de0904285a7acfd8685f20af68b554cdd8d52 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:16:57 +0100 Subject: [PATCH 152/563] Added fromJson, toJson --- lib/data/dto/game.dart | 17 +++++++++++++++++ lib/data/dto/group.dart | 13 +++++++++++++ lib/data/dto/player.dart | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index c84779d..a52ee29 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -21,4 +21,21 @@ class Game { String toString() { return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; } + + /// Creates a Game instance from a JSON object. + Game.fromJson(Map json) + : id = json['id'], + name = json['name'], + players = json['players'] != null + ? (json['players'] as List) + .map((playerJson) => Player.fromJson(playerJson)) + .toList() + : null, + group = json['group'] != null ? Group.fromJson(json['group']) : null, + winner = json['winner'] ?? ''; + + /// Converts the Game instance to a JSON object. + String toJson() { + return 'Game{id: $id,name: $name,players: $players,group: $group,winner: $winner}'; + } } diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 0420477..0546dbd 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -13,4 +13,17 @@ class Group { String toString() { return 'Group{id: $id, name: $name,members: $members}'; } + + /// Creates a Group instance from a JSON object. + Group.fromJson(Map json) + : id = json['id'], + name = json['name'], + members = (json['members'] as List) + .map((memberJson) => Player.fromJson(memberJson)) + .toList(); + + /// Converts the Group instance to a JSON object. + String toJson() { + return 'Group{id: $id, name: $name,members: $members}'; + } } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 1b00c2c..9f10729 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -10,4 +10,14 @@ class Player { String toString() { return 'Player{id: $id,name: $name}'; } + + /// Creates a Player instance from a JSON object. + Player.fromJson(Map json) + : id = json['id'], + name = json['name']; + + /// Converts the Player instance to a JSON object. + String toJson() { + return 'Player{id: $id,name: $name}'; + } } -- 2.49.1 From 2da2e28cb66fd31f2074befb3fa0fcc6686b767f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:20:26 +0100 Subject: [PATCH 153/563] Added json schema --- assets/schema.json | 0 pubspec.yaml | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 assets/schema.json diff --git a/assets/schema.json b/assets/schema.json new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.yaml b/pubspec.yaml index fbbc01a..c7e55d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,3 +30,6 @@ dev_dependencies: flutter: uses-material-design: true + +assets: + - assets/schema.json -- 2.49.1 From 67c8a7e1811810a58e2344eade58703d096949a4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 23:21:46 +0100 Subject: [PATCH 154/563] added createdAt timestamp to Group, Game, and Player DTOs --- lib/data/dto/game.dart | 5 ++++- lib/data/dto/group.dart | 10 ++++++++-- lib/data/dto/player.dart | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index c84779d..b8ebf5c 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -8,14 +8,17 @@ class Game { final List? players; final Group? group; final String winner; + final DateTime createdAt; Game({ String? id, + DateTime? createdAt, required this.name, this.players, this.group, this.winner = '', - }) : id = id ?? const Uuid().v4(); + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now(); @override String toString() { diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 0420477..6a27de1 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -5,9 +5,15 @@ class Group { final String id; final String name; final List members; + final DateTime createdAt; - Group({String? id, required this.name, required this.members}) - : id = id ?? const Uuid().v4(); + Group({ + String? id, + DateTime? createdAt, + required this.name, + required this.members, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now(); @override String toString() { diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 1b00c2c..5cbceef 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -3,8 +3,11 @@ import 'package:uuid/uuid.dart'; class Player { final String id; final String name; + final DateTime createdAt; - Player({String? id, required this.name}) : id = id ?? const Uuid().v4(); + Player({String? id, DateTime? createdAt, required this.name}) + : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now(); @override String toString() { -- 2.49.1 From 8cc898cad6b4b5e610dc9a38d0bdc1b15f631f9e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 23:38:43 +0100 Subject: [PATCH 155/563] regenerated database.g.dart --- lib/data/db/database.g.dart | 298 +++++++++++++++++++++++++++++++----- 1 file changed, 263 insertions(+), 35 deletions(-) diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 03b7a10..3f10169 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -27,8 +27,19 @@ class $PlayerTableTable extends PlayerTable type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); @override - List get $columns => [id, name]; + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -54,6 +65,14 @@ class $PlayerTableTable extends PlayerTable } else if (isInserting) { context.missing(_nameMeta); } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } return context; } @@ -71,6 +90,10 @@ class $PlayerTableTable extends PlayerTable DriftSqlType.string, data['${effectivePrefix}name'], )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, ); } @@ -83,17 +106,27 @@ class $PlayerTableTable extends PlayerTable class PlayerTableData extends DataClass implements Insertable { final String id; final String name; - const PlayerTableData({required this.id, required this.name}); + final DateTime createdAt; + const PlayerTableData({ + required this.id, + required this.name, + required this.createdAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); + map['created_at'] = Variable(createdAt); return map; } PlayerTableCompanion toCompanion(bool nullToAbsent) { - return PlayerTableCompanion(id: Value(id), name: Value(name)); + return PlayerTableCompanion( + id: Value(id), + name: Value(name), + createdAt: Value(createdAt), + ); } factory PlayerTableData.fromJson( @@ -104,6 +137,7 @@ class PlayerTableData extends DataClass implements Insertable { return PlayerTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), + createdAt: serializer.fromJson(json['createdAt']), ); } @override @@ -112,15 +146,21 @@ class PlayerTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), + 'createdAt': serializer.toJson(createdAt), }; } - PlayerTableData copyWith({String? id, String? name}) => - PlayerTableData(id: id ?? this.id, name: name ?? this.name); + PlayerTableData copyWith({String? id, String? name, DateTime? createdAt}) => + PlayerTableData( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + ); PlayerTableData copyWithCompanion(PlayerTableCompanion data) { return PlayerTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -128,44 +168,52 @@ class PlayerTableData extends DataClass implements Insertable { String toString() { return (StringBuffer('PlayerTableData(') ..write('id: $id, ') - ..write('name: $name') + ..write('name: $name, ') + ..write('createdAt: $createdAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name); + int get hashCode => Object.hash(id, name, createdAt); @override bool operator ==(Object other) => identical(this, other) || (other is PlayerTableData && other.id == this.id && - other.name == this.name); + other.name == this.name && + other.createdAt == this.createdAt); } class PlayerTableCompanion extends UpdateCompanion { final Value id; final Value name; + final Value createdAt; final Value rowid; const PlayerTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), + this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); PlayerTableCompanion.insert({ required String id, required String name, + required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), - name = Value(name); + name = Value(name), + createdAt = Value(createdAt); static Insertable custom({ Expression? id, Expression? name, + Expression? createdAt, Expression? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, + if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); } @@ -173,11 +221,13 @@ class PlayerTableCompanion extends UpdateCompanion { PlayerTableCompanion copyWith({ Value? id, Value? name, + Value? createdAt, Value? rowid, }) { return PlayerTableCompanion( id: id ?? this.id, name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); } @@ -191,6 +241,9 @@ class PlayerTableCompanion extends UpdateCompanion { if (name.present) { map['name'] = Variable(name.value); } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -202,6 +255,7 @@ class PlayerTableCompanion extends UpdateCompanion { return (StringBuffer('PlayerTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') + ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -232,8 +286,19 @@ class $GroupTableTable extends GroupTable type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); @override - List get $columns => [id, name]; + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -259,6 +324,14 @@ class $GroupTableTable extends GroupTable } else if (isInserting) { context.missing(_nameMeta); } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } return context; } @@ -276,6 +349,10 @@ class $GroupTableTable extends GroupTable DriftSqlType.string, data['${effectivePrefix}name'], )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, ); } @@ -288,17 +365,27 @@ class $GroupTableTable extends GroupTable class GroupTableData extends DataClass implements Insertable { final String id; final String name; - const GroupTableData({required this.id, required this.name}); + final DateTime createdAt; + const GroupTableData({ + required this.id, + required this.name, + required this.createdAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); + map['created_at'] = Variable(createdAt); return map; } GroupTableCompanion toCompanion(bool nullToAbsent) { - return GroupTableCompanion(id: Value(id), name: Value(name)); + return GroupTableCompanion( + id: Value(id), + name: Value(name), + createdAt: Value(createdAt), + ); } factory GroupTableData.fromJson( @@ -309,6 +396,7 @@ class GroupTableData extends DataClass implements Insertable { return GroupTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), + createdAt: serializer.fromJson(json['createdAt']), ); } @override @@ -317,15 +405,21 @@ class GroupTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), + 'createdAt': serializer.toJson(createdAt), }; } - GroupTableData copyWith({String? id, String? name}) => - GroupTableData(id: id ?? this.id, name: name ?? this.name); + GroupTableData copyWith({String? id, String? name, DateTime? createdAt}) => + GroupTableData( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + ); GroupTableData copyWithCompanion(GroupTableCompanion data) { return GroupTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -333,44 +427,52 @@ class GroupTableData extends DataClass implements Insertable { String toString() { return (StringBuffer('GroupTableData(') ..write('id: $id, ') - ..write('name: $name') + ..write('name: $name, ') + ..write('createdAt: $createdAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name); + int get hashCode => Object.hash(id, name, createdAt); @override bool operator ==(Object other) => identical(this, other) || (other is GroupTableData && other.id == this.id && - other.name == this.name); + other.name == this.name && + other.createdAt == this.createdAt); } class GroupTableCompanion extends UpdateCompanion { final Value id; final Value name; + final Value createdAt; final Value rowid; const GroupTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), + this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); GroupTableCompanion.insert({ required String id, required String name, + required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), - name = Value(name); + name = Value(name), + createdAt = Value(createdAt); static Insertable custom({ Expression? id, Expression? name, + Expression? createdAt, Expression? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, + if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); } @@ -378,11 +480,13 @@ class GroupTableCompanion extends UpdateCompanion { GroupTableCompanion copyWith({ Value? id, Value? name, + Value? createdAt, Value? rowid, }) { return GroupTableCompanion( id: id ?? this.id, name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); } @@ -396,6 +500,9 @@ class GroupTableCompanion extends UpdateCompanion { if (name.present) { map['name'] = Variable(name.value); } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -407,6 +514,7 @@ class GroupTableCompanion extends UpdateCompanion { return (StringBuffer('GroupTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') + ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -451,8 +559,19 @@ class $GameTableTable extends GameTable 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); @override - List get $columns => [id, name, winnerId]; + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, name, winnerId, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -486,6 +605,14 @@ class $GameTableTable extends GameTable } else if (isInserting) { context.missing(_winnerIdMeta); } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } return context; } @@ -507,6 +634,10 @@ class $GameTableTable extends GameTable DriftSqlType.string, data['${effectivePrefix}winner_id'], )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, ); } @@ -520,10 +651,12 @@ class GameTableData extends DataClass implements Insertable { final String id; final String name; final String winnerId; + final DateTime createdAt; const GameTableData({ required this.id, required this.name, required this.winnerId, + required this.createdAt, }); @override Map toColumns(bool nullToAbsent) { @@ -531,6 +664,7 @@ class GameTableData extends DataClass implements Insertable { map['id'] = Variable(id); map['name'] = Variable(name); map['winner_id'] = Variable(winnerId); + map['created_at'] = Variable(createdAt); return map; } @@ -539,6 +673,7 @@ class GameTableData extends DataClass implements Insertable { id: Value(id), name: Value(name), winnerId: Value(winnerId), + createdAt: Value(createdAt), ); } @@ -551,6 +686,7 @@ class GameTableData extends DataClass implements Insertable { id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), winnerId: serializer.fromJson(json['winnerId']), + createdAt: serializer.fromJson(json['createdAt']), ); } @override @@ -560,20 +696,27 @@ class GameTableData extends DataClass implements Insertable { 'id': serializer.toJson(id), 'name': serializer.toJson(name), 'winnerId': serializer.toJson(winnerId), + 'createdAt': serializer.toJson(createdAt), }; } - GameTableData copyWith({String? id, String? name, String? winnerId}) => - GameTableData( - id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, - ); + GameTableData copyWith({ + String? id, + String? name, + String? winnerId, + DateTime? createdAt, + }) => GameTableData( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId ?? this.winnerId, + createdAt: createdAt ?? this.createdAt, + ); GameTableData copyWithCompanion(GameTableCompanion data) { return GameTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -582,51 +725,59 @@ class GameTableData extends DataClass implements Insertable { return (StringBuffer('GameTableData(') ..write('id: $id, ') ..write('name: $name, ') - ..write('winnerId: $winnerId') + ..write('winnerId: $winnerId, ') + ..write('createdAt: $createdAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, winnerId); + int get hashCode => Object.hash(id, name, winnerId, createdAt); @override bool operator ==(Object other) => identical(this, other) || (other is GameTableData && other.id == this.id && other.name == this.name && - other.winnerId == this.winnerId); + other.winnerId == this.winnerId && + other.createdAt == this.createdAt); } class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; final Value winnerId; + final Value createdAt; final Value rowid; const GameTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), this.winnerId = const Value.absent(), + this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); GameTableCompanion.insert({ required String id, required String name, required String winnerId, + required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), name = Value(name), - winnerId = Value(winnerId); + winnerId = Value(winnerId), + createdAt = Value(createdAt); static Insertable custom({ Expression? id, Expression? name, Expression? winnerId, + Expression? createdAt, Expression? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, if (winnerId != null) 'winner_id': winnerId, + if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); } @@ -635,12 +786,14 @@ class GameTableCompanion extends UpdateCompanion { Value? id, Value? name, Value? winnerId, + Value? createdAt, Value? rowid, }) { return GameTableCompanion( id: id ?? this.id, name: name ?? this.name, winnerId: winnerId ?? this.winnerId, + createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); } @@ -657,6 +810,9 @@ class GameTableCompanion extends UpdateCompanion { if (winnerId.present) { map['winner_id'] = Variable(winnerId.value); } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -669,6 +825,7 @@ class GameTableCompanion extends UpdateCompanion { ..write('id: $id, ') ..write('name: $name, ') ..write('winnerId: $winnerId, ') + ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -1437,12 +1594,14 @@ typedef $$PlayerTableTableCreateCompanionBuilder = PlayerTableCompanion Function({ required String id, required String name, + required DateTime createdAt, Value rowid, }); typedef $$PlayerTableTableUpdateCompanionBuilder = PlayerTableCompanion Function({ Value id, Value name, + Value createdAt, Value rowid, }); @@ -1534,6 +1693,11 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + Expression gameTableRefs( Expression Function($$GameTableTableFilterComposer f) f, ) { @@ -1628,6 +1792,11 @@ class $$PlayerTableTableOrderingComposer column: $table.name, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); } class $$PlayerTableTableAnnotationComposer @@ -1645,6 +1814,9 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + Expression gameTableRefs( Expression Function($$GameTableTableAnnotationComposer a) f, ) { @@ -1755,15 +1927,26 @@ class $$PlayerTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value createdAt = const Value.absent(), Value rowid = const Value.absent(), - }) => PlayerTableCompanion(id: id, name: name, rowid: rowid), + }) => PlayerTableCompanion( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), createCompanionCallback: ({ required String id, required String name, + required DateTime createdAt, Value rowid = const Value.absent(), - }) => - PlayerTableCompanion.insert(id: id, name: name, rowid: rowid), + }) => PlayerTableCompanion.insert( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), withReferenceMapper: (p0) => p0 .map( (e) => ( @@ -1881,12 +2064,14 @@ typedef $$GroupTableTableCreateCompanionBuilder = GroupTableCompanion Function({ required String id, required String name, + required DateTime createdAt, Value rowid, }); typedef $$GroupTableTableUpdateCompanionBuilder = GroupTableCompanion Function({ Value id, Value name, + Value createdAt, Value rowid, }); @@ -1958,6 +2143,11 @@ class $$GroupTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -2027,6 +2217,11 @@ class $$GroupTableTableOrderingComposer column: $table.name, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); } class $$GroupTableTableAnnotationComposer @@ -2044,6 +2239,9 @@ class $$GroupTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -2128,15 +2326,26 @@ class $$GroupTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value createdAt = const Value.absent(), Value rowid = const Value.absent(), - }) => GroupTableCompanion(id: id, name: name, rowid: rowid), + }) => GroupTableCompanion( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), createCompanionCallback: ({ required String id, required String name, + required DateTime createdAt, Value rowid = const Value.absent(), - }) => - GroupTableCompanion.insert(id: id, name: name, rowid: rowid), + }) => GroupTableCompanion.insert( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), withReferenceMapper: (p0) => p0 .map( (e) => ( @@ -2228,6 +2437,7 @@ typedef $$GameTableTableCreateCompanionBuilder = required String id, required String name, required String winnerId, + required DateTime createdAt, Value rowid, }); typedef $$GameTableTableUpdateCompanionBuilder = @@ -2235,6 +2445,7 @@ typedef $$GameTableTableUpdateCompanionBuilder = Value id, Value name, Value winnerId, + Value createdAt, Value rowid, }); @@ -2319,6 +2530,11 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + $$PlayerTableTableFilterComposer get winnerId { final $$PlayerTableTableFilterComposer composer = $composerBuilder( composer: this, @@ -2412,6 +2628,11 @@ class $$GameTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + $$PlayerTableTableOrderingComposer get winnerId { final $$PlayerTableTableOrderingComposer composer = $composerBuilder( composer: this, @@ -2451,6 +2672,9 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + $$PlayerTableTableAnnotationComposer get winnerId { final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( composer: this, @@ -2560,11 +2784,13 @@ class $$GameTableTableTableManager Value id = const Value.absent(), Value name = const Value.absent(), Value winnerId = const Value.absent(), + Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion( id: id, name: name, winnerId: winnerId, + createdAt: createdAt, rowid: rowid, ), createCompanionCallback: @@ -2572,11 +2798,13 @@ class $$GameTableTableTableManager required String id, required String name, required String winnerId, + required DateTime createdAt, Value rowid = const Value.absent(), }) => GameTableCompanion.insert( id: id, name: name, winnerId: winnerId, + createdAt: createdAt, rowid: rowid, ), withReferenceMapper: (p0) => p0 -- 2.49.1 From fd86f5193fbe68df9a5bf32fb00a0ad50030011f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:46:32 +0100 Subject: [PATCH 156/563] Fixed toJson methods --- lib/data/dto/game.dart | 10 +++++++--- lib/data/dto/group.dart | 8 +++++--- lib/data/dto/player.dart | 4 +--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index a52ee29..bec22fb 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -35,7 +35,11 @@ class Game { winner = json['winner'] ?? ''; /// Converts the Game instance to a JSON object. - String toJson() { - return 'Game{id: $id,name: $name,players: $players,group: $group,winner: $winner}'; - } + Map toJson() => { + 'id': id, + 'name': name, + 'players': players?.map((player) => player.toJson()).toList(), + 'group': group?.toJson(), + 'winner': winner, + }; } diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 0546dbd..71347b1 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -23,7 +23,9 @@ class Group { .toList(); /// Converts the Group instance to a JSON object. - String toJson() { - return 'Group{id: $id, name: $name,members: $members}'; - } + Map toJson() => { + 'id': id, + 'name': name, + 'members': members.map((member) => member.toJson()).toList(), + }; } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 9f10729..cc73f87 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -17,7 +17,5 @@ class Player { name = json['name']; /// Converts the Player instance to a JSON object. - String toJson() { - return 'Player{id: $id,name: $name}'; - } + Map toJson() => {'id': id, 'name': name}; } -- 2.49.1 From 2ee8edcf9b94a9cca5a57534656b76ecfbaca4d0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 23:47:45 +0100 Subject: [PATCH 157/563] add createdAt column to game, group and player tables and DAOs --- lib/data/dao/game_dao.dart | 8 +++++++- lib/data/dao/group_dao.dart | 24 +++++++++++++++++++----- lib/data/dao/player_dao.dart | 22 +++++++++++++++++----- lib/data/db/tables/game_table.dart | 1 + lib/data/db/tables/group_table.dart | 1 + lib/data/db/tables/player_table.dart | 1 + 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index fc931ad..94d010c 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -15,7 +15,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { Future> getAllGames() async { final query = select(gameTable); final result = await query.get(); - return result.map((row) => Game(id: row.id, name: row.name)).toList(); + return result + .map( + (row) => Game(id: row.id, name: row.name, createdAt: row.createdAt), + ) + .toList(); } /// Retrieves a [Game] by its [gameId]. @@ -38,6 +42,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { players: players, group: group, winner: result.winnerId, + createdAt: result.createdAt, ); } @@ -58,6 +63,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { id: game.id, name: game.name, winnerId: game.winner, + createdAt: game.createdAt, ), mode: InsertMode.insertOrReplace, ); diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..1b0b09a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -19,7 +19,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final members = await db.playerGroupDao.getPlayersOfGroupById( groupId: groupData.id, ); - return Group(id: groupData.id, name: groupData.name, members: members); + return Group( + id: groupData.id, + name: groupData.name, + members: members, + createdAt: groupData.createdAt, + ); }), ); } @@ -33,7 +38,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { groupId: groupId, ); - return Group(id: result.id, name: result.name, members: members); + return Group( + id: result.id, + name: result.name, + members: members, + createdAt: result.createdAt, + ); } /// Adds a new group with the given [id] and [name] to the database. @@ -41,9 +51,13 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { Future addGroup({required Group group}) async { if (!await groupExists(groupId: group.id)) { await db.transaction(() async { - await into( - groupTable, - ).insert(GroupTableCompanion.insert(id: group.id, name: group.name)); + await into(groupTable).insert( + GroupTableCompanion.insert( + id: group.id, + name: group.name, + createdAt: group.createdAt, + ), + ); await db.batch( (b) => b.insertAll( db.playerGroupTable, diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 591634c..36f9305 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -13,14 +13,22 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { Future> getAllPlayers() async { final query = select(playerTable); final result = await query.get(); - return result.map((row) => Player(id: row.id, name: row.name)).toList(); + return result + .map( + (row) => Player(id: row.id, name: row.name, createdAt: row.createdAt), + ) + .toList(); } /// Retrieves a [Player] by their [id]. Future getPlayerById({required String playerId}) async { final query = select(playerTable)..where((p) => p.id.equals(playerId)); final result = await query.getSingle(); - return Player(id: result.id, name: result.name); + return Player( + id: result.id, + name: result.name, + createdAt: result.createdAt, + ); } /// Adds a new [player] to the database. @@ -28,9 +36,13 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// the new one. Future addPlayer({required Player player}) async { if (!await playerExists(playerId: player.id)) { - await into( - playerTable, - ).insert(PlayerTableCompanion.insert(id: player.id, name: player.name)); + await into(playerTable).insert( + PlayerTableCompanion.insert( + id: player.id, + name: player.name, + createdAt: player.createdAt, + ), + ); return true; } return false; diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 9651a79..0fe5a3c 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -6,6 +6,7 @@ class GameTable extends Table { TextColumn get name => text()(); TextColumn get winnerId => text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + DateTimeColumn get createdAt => dateTime()(); @override Set> get primaryKey => {id}; diff --git a/lib/data/db/tables/group_table.dart b/lib/data/db/tables/group_table.dart index dc9335d..5c52355 100644 --- a/lib/data/db/tables/group_table.dart +++ b/lib/data/db/tables/group_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class GroupTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + DateTimeColumn get createdAt => dateTime()(); @override Set> get primaryKey => {id}; diff --git a/lib/data/db/tables/player_table.dart b/lib/data/db/tables/player_table.dart index 3d97459..794958e 100644 --- a/lib/data/db/tables/player_table.dart +++ b/lib/data/db/tables/player_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class PlayerTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + DateTimeColumn get createdAt => dateTime()(); @override Set> get primaryKey => {id}; -- 2.49.1 From 08fcaa35ee31ee83e891b343ab6c03ca4a4dbdd3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:59:18 +0100 Subject: [PATCH 158/563] Added methods for deleting all entities --- lib/data/dao/game_dao.dart | 8 ++++++++ lib/data/dao/group_dao.dart | 8 ++++++++ lib/data/dao/player_dao.dart | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index fc931ad..a5946f2 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -88,4 +88,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final result = await query.getSingleOrNull(); return result != null; } + + /// Deletes all games from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllGames() async { + final query = delete(gameTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..39f8c45 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -103,4 +103,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final result = await query.getSingleOrNull(); return result != null; } + + /// Deletes all groups from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllGroups() async { + final query = delete(groupTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 591634c..e0aa165 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -70,4 +70,12 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { .getSingle(); return count ?? 0; } + + /// Deletes all players from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllPlayers() async { + final query = delete(playerTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } -- 2.49.1 From 42ce69f4d3faf4fd210364bf504543d2887c6290 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 18 Nov 2025 23:59:28 +0100 Subject: [PATCH 159/563] Added schema.json --- assets/schema.json | 137 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/assets/schema.json b/assets/schema.json index e69de29..c33fab2 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -0,0 +1,137 @@ + + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "games": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "players": { + "type": "null" + }, + "group": { + "type": "null" + }, + "winner": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "players", + "group", + "winner" + ] + } + ] + }, + "groups": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "members": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + ] + } + }, + "required": [ + "id", + "name", + "members" + ] + } + ] + }, + "players": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + ] + } + }, + "required": [ + "games", + "groups", + "players" + ] +} + -- 2.49.1 From 69c95ca672731b7562b28ec7aab7c9b905f510e0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:22:13 +0100 Subject: [PATCH 160/563] Added custom statement for cascade deleting --- lib/data/db/database.dart | 9 ++ lib/data/db/database.g.dart | 304 +++++------------------------------- 2 files changed, 52 insertions(+), 261 deletions(-) diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 73ad73e..704e1f0 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -40,6 +40,15 @@ class AppDatabase extends _$AppDatabase { @override int get schemaVersion => 1; + @override + MigrationStrategy get migration { + return MigrationStrategy( + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + }, + ); + } + static QueryExecutor _openConnection() { return driftDatabase( name: 'gametracker_db', diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 03b7a10..20e4cc1 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -444,12 +444,9 @@ class $GameTableTable extends GameTable late final GeneratedColumn winnerId = GeneratedColumn( 'winner_id', aliasedName, - false, + true, type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id) ON DELETE CASCADE', - ), + requiredDuringInsert: false, ); @override List get $columns => [id, name, winnerId]; @@ -483,8 +480,6 @@ class $GameTableTable extends GameTable _winnerIdMeta, winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), ); - } else if (isInserting) { - context.missing(_winnerIdMeta); } return context; } @@ -506,7 +501,7 @@ class $GameTableTable extends GameTable winnerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}winner_id'], - )!, + ), ); } @@ -519,18 +514,16 @@ class $GameTableTable extends GameTable class GameTableData extends DataClass implements Insertable { final String id; final String name; - final String winnerId; - const GameTableData({ - required this.id, - required this.name, - required this.winnerId, - }); + final String? winnerId; + const GameTableData({required this.id, required this.name, this.winnerId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); - map['winner_id'] = Variable(winnerId); + if (!nullToAbsent || winnerId != null) { + map['winner_id'] = Variable(winnerId); + } return map; } @@ -538,7 +531,9 @@ class GameTableData extends DataClass implements Insertable { return GameTableCompanion( id: Value(id), name: Value(name), - winnerId: Value(winnerId), + winnerId: winnerId == null && nullToAbsent + ? const Value.absent() + : Value(winnerId), ); } @@ -550,7 +545,7 @@ class GameTableData extends DataClass implements Insertable { return GameTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), + winnerId: serializer.fromJson(json['winnerId']), ); } @override @@ -559,16 +554,19 @@ class GameTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), + 'winnerId': serializer.toJson(winnerId), }; } - GameTableData copyWith({String? id, String? name, String? winnerId}) => - GameTableData( - id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, - ); + GameTableData copyWith({ + String? id, + String? name, + Value winnerId = const Value.absent(), + }) => GameTableData( + id: id ?? this.id, + name: name ?? this.name, + winnerId: winnerId.present ? winnerId.value : this.winnerId, + ); GameTableData copyWithCompanion(GameTableCompanion data) { return GameTableData( id: data.id.present ? data.id.value : this.id, @@ -601,7 +599,7 @@ class GameTableData extends DataClass implements Insertable { class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; - final Value winnerId; + final Value winnerId; final Value rowid; const GameTableCompanion({ this.id = const Value.absent(), @@ -612,11 +610,10 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion.insert({ required String id, required String name, - required String winnerId, + this.winnerId = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), - name = Value(name), - winnerId = Value(winnerId); + name = Value(name); static Insertable custom({ Expression? id, Expression? name, @@ -634,7 +631,7 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion copyWith({ Value? id, Value? name, - Value? winnerId, + Value? winnerId, Value? rowid, }) { return GameTableCompanion( @@ -1381,13 +1378,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ - WritePropagation( - on: TableUpdateQuery.onTableName( - 'player_table', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('game_table', kind: UpdateKind.delete)], - ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', @@ -1450,24 +1440,6 @@ final class $$PlayerTableTableReferences extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$GameTableTable, List> - _gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.gameTable, - aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId), - ); - - $$GameTableTableProcessedTableManager get gameTableRefs { - final manager = $$GameTableTableTableManager( - $_db, - $_db.gameTable, - ).filter((f) => f.winnerId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } - static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -1534,31 +1506,6 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression gameTableRefs( - Expression Function($$GameTableTableFilterComposer f) f, - ) { - final $$GameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -1645,31 +1592,6 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - Expression gameTableRefs( - Expression Function($$GameTableTableAnnotationComposer a) f, - ) { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -1735,7 +1657,6 @@ class $$PlayerTableTableTableManager (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -1773,42 +1694,16 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - gameTableRefs = false, - playerGroupTableRefs = false, - playerGameTableRefs = false, - }) { + ({playerGroupTableRefs = false, playerGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (gameTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - GameTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._gameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).gameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.winnerId == item.id, - ), - typedResults: items, - ), if (playerGroupTableRefs) await $_getPrefetchedData< PlayerTableData, @@ -1872,7 +1767,6 @@ typedef $$PlayerTableTableProcessedTableManager = (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -2227,14 +2121,14 @@ typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ required String id, required String name, - required String winnerId, + Value winnerId, Value rowid, }); typedef $$GameTableTableUpdateCompanionBuilder = GameTableCompanion Function({ Value id, Value name, - Value winnerId, + Value winnerId, Value rowid, }); @@ -2242,25 +2136,6 @@ final class $$GameTableTableReferences extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PlayerTableTable _winnerIdTable(_$AppDatabase db) => - db.playerTable.createAlias( - $_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id), - ); - - $$PlayerTableTableProcessedTableManager get winnerId { - final $_column = $_itemColumn('winner_id')!; - - final manager = $$PlayerTableTableTableManager( - $_db, - $_db.playerTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_winnerIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - static MultiTypedResultKey<$PlayerGameTableTable, List> _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGameTable, @@ -2319,28 +2194,10 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - $$PlayerTableTableFilterComposer get winnerId { - final $$PlayerTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableFilterComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + ColumnFilters get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnFilters(column), + ); Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableFilterComposer f) f, @@ -2412,28 +2269,10 @@ class $$GameTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - $$PlayerTableTableOrderingComposer get winnerId { - final $$PlayerTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableOrderingComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + ColumnOrderings get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnOrderings(column), + ); } class $$GameTableTableAnnotationComposer @@ -2451,28 +2290,8 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - $$PlayerTableTableAnnotationComposer get winnerId { - final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableAnnotationComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } + GeneratedColumn get winnerId => + $composableBuilder(column: $table.winnerId, builder: (column) => column); Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableAnnotationComposer a) f, @@ -2539,7 +2358,6 @@ class $$GameTableTableTableManager (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) @@ -2559,7 +2377,7 @@ class $$GameTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), - Value winnerId = const Value.absent(), + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion( id: id, @@ -2571,7 +2389,7 @@ class $$GameTableTableTableManager ({ required String id, required String name, - required String winnerId, + Value winnerId = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion.insert( id: id, @@ -2588,49 +2406,14 @@ class $$GameTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - winnerId = false, - playerGameTableRefs = false, - groupGameTableRefs = false, - }) { + ({playerGameTableRefs = false, groupGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, ], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (winnerId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.winnerId, - referencedTable: $$GameTableTableReferences - ._winnerIdTable(db), - referencedColumn: $$GameTableTableReferences - ._winnerIdTable(db) - .id, - ) - as T; - } - - return state; - }, + addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (playerGameTableRefs) @@ -2696,7 +2479,6 @@ typedef $$GameTableTableProcessedTableManager = (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) -- 2.49.1 From f6ebda7984414ac429c5e45227da508272070d30 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:22:35 +0100 Subject: [PATCH 161/563] Changed table column because of importing issues --- lib/data/db/tables/game_table.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart index 9651a79..b2dc6ba 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/game_table.dart @@ -1,11 +1,9 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; class GameTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); - TextColumn get winnerId => - text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + late final winnerId = text().nullable()(); @override Set> get primaryKey => {id}; -- 2.49.1 From 5dcd0826bdd5b221170b4780c7f425be09706247 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:23:28 +0100 Subject: [PATCH 162/563] Adjusted attributes to table definition --- lib/data/dao/game_dao.dart | 2 +- lib/data/dao/game_dao.g.dart | 1 - lib/data/dao/group_game_dao.g.dart | 1 - lib/data/dto/game.dart | 11 +++-------- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index a5946f2..7df03b0 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -57,7 +57,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: game.winner, + winnerId: Value(game.winner), ), mode: InsertMode.insertOrReplace, ); diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart index ebf5524..b5a29fe 100644 --- a/lib/data/dao/game_dao.g.dart +++ b/lib/data/dao/game_dao.g.dart @@ -4,6 +4,5 @@ part of 'game_dao.dart'; // ignore_for_file: type=lint mixin _$GameDaoMixin on DatabaseAccessor { - $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; } diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart index 426f192..735a35f 100644 --- a/lib/data/dao/group_game_dao.g.dart +++ b/lib/data/dao/group_game_dao.g.dart @@ -5,7 +5,6 @@ part of 'group_game_dao.dart'; // ignore_for_file: type=lint mixin _$GroupGameDaoMixin on DatabaseAccessor { $GroupTableTable get groupTable => attachedDatabase.groupTable; - $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; } diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index bec22fb..60bcff0 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -7,15 +7,10 @@ class Game { final String name; final List? players; final Group? group; - final String winner; + final String? winner; - Game({ - String? id, - required this.name, - this.players, - this.group, - this.winner = '', - }) : id = id ?? const Uuid().v4(); + Game({String? id, required this.name, this.players, this.group, this.winner}) + : id = id ?? const Uuid().v4(); @override String toString() { -- 2.49.1 From f2a749cb0fe7b43f6b232437f8487c43e07f3e3c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:24:08 +0100 Subject: [PATCH 163/563] First version of settings view --- .../views/main_menu/settings_view.dart | 218 +++++++++++++++--- pubspec.yaml | 8 +- 2 files changed, 192 insertions(+), 34 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index b05ae8d..2a1d193 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,10 +1,55 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'dart:convert'; +import 'dart:io'; -class SettingsView extends StatelessWidget { +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:json_schema/json_schema.dart'; +import 'package:provider/provider.dart'; + +class SettingsView extends StatefulWidget { const SettingsView({super.key}); + @override + State createState() => _SettingsViewState(); + + /// Helper method to read file content from either bytes or path + static Future _readFileContent(PlatformFile file) async { + if (file.bytes != null) return utf8.decode(file.bytes!); + if (file.path != null) return await File(file.path!).readAsString(); + + throw Exception('Die Datei hat keinen lesbaren Inhalt'); + } + + static Future validateJsonSchema(String jsonString) async { + final String schemaString; + + schemaString = await rootBundle.loadString('assets/schema.json'); + + try { + final schema = JsonSchema.create(json.decode(schemaString)); + final jsonData = json.decode(jsonString); + final result = schema.validate(jsonData); + + if (result.isValid) { + return true; + } + return false; + } catch (e, stack) { + print('[validateJsonSchema] $e'); + print(stack); + return false; + } + } +} + +class _SettingsViewState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -41,39 +86,31 @@ class SettingsView extends StatelessWidget { ), SettingsListTile( - title: 'Export Data', + title: 'Export data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => exportData(), + onPressed: () async { + final String json = await _getAppDataAsJson(context); + await exportData(json, 'export'); + }, ), SettingsListTile( - title: 'Import Data', + title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => importData(), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), - child: Text( - textAlign: TextAlign.start, - 'Example Headline', - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), + onPressed: () => importData(context), ), SettingsListTile( - title: 'Example Tile', + title: 'Delete all data', + icon: Icons.download_outlined, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () => deleteAllData(context), + ), + SettingsListTile( + title: 'Add Sample Data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Example Tile'), - ), - SettingsListTile( - title: 'Example Tile', - icon: Icons.download_outlined, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => print('Example Tile'), + onPressed: () => addSampleData(context), ), ], ), @@ -82,9 +119,128 @@ class SettingsView extends StatelessWidget { ); } - // TODO: Implement export functionality - void exportData() {} + Future deleteAllData(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.gameDao.deleteAllGames(); + await db.groupDao.deleteAllGroups(); + await db.playerDao.deleteAllPlayers(); + print('[deleteAllData] All data deleted'); + } - // TODO: Implement import functionality - void importData() {} + Future addSampleData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final player1 = Player(name: 'Alice'); + final player2 = Player(name: 'Bob'); + final group = Group(name: 'Friends', members: [player1, player2]); + final game = Game(name: 'Sample Game', group: group, winner: 'Alice'); + + await db.playerDao.addPlayer(player: player1); + await db.playerDao.addPlayer(player: player2); + await db.groupDao.addGroup(group: group); + await db.gameDao.addGame(game: game); + } + + Future _getAppDataAsJson(BuildContext context) async { + final db = Provider.of(context, listen: false); + final games = await db.gameDao.getAllGames(); + final groups = await db.groupDao.getAllGroups(); + final players = await db.playerDao.getAllPlayers(); + + // Construct a JSON representation of the data + final Map jsonMap = { + 'games': games.map((game) => game.toJson()).toList(), + 'groups': groups.map((group) => group.toJson()).toList(), + 'players': players.map((player) => player.toJson()).toList(), + }; + + return json.encode(jsonMap); + } + + Future exportData(String jsonString, String fileName) async { + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + await FilePicker.platform.saveFile( + fileName: '$fileName.json', + bytes: bytes, + ); + return true; + } catch (e, stack) { + print('[exportData] $e'); + print(stack); + return false; + } + } + + Future importData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final path = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + + if (path == null) { + print('[importData] No file selected'); + return; + } + + try { + final jsonString = await SettingsView._readFileContent(path.files.single); + + // Checks if the JSON String is in the gameList format + if (await SettingsView.validateJsonSchema(jsonString)) { + final Map jsonData = + json.decode(jsonString) as Map; + print('[importData] : $jsonData'); + final List? gamesJson = jsonData['games'] as List?; + final List? groupsJson = jsonData['groups'] as List?; + final List? playersJson = + jsonData['players'] as List?; + + final List importedGames = + gamesJson + ?.map((g) => Game.fromJson(g as Map)) + .toList() ?? + []; + final List importedGroups = + groupsJson + ?.map((g) => Group.fromJson(g as Map)) + .toList() ?? + []; + final List importedPlayers = + playersJson + ?.map((p) => Player.fromJson(p as Map)) + .toList() ?? + []; + + for (Player player in importedPlayers) { + await db.playerDao.addPlayer(player: player); + } + + for (Group group in importedGroups) { + await db.groupDao.addGroup(group: group); + } + + for (Game game in importedGames) { + await db.gameDao.addGame(game: game); + } + } else { + print('[importData] Invalid JSON schema'); + return; + } + print('[importData] Data imported successfully'); + return; + } on FormatException catch (e, stack) { + print('[importData] FormatException'); + print('[importData] $e'); + print(stack); + return; + } on Exception catch (e, stack) { + print('[importData] Exception'); + print('[importData] $e'); + print(stack); + return; + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index c7e55d7..bce31f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,9 @@ dependencies: provider: ^6.1.5 skeletonizer: ^2.1.0+1 uuid: ^4.5.2 + file_picker: ^10.3.6 + json_schema: ^5.2.2 + file_saver: ^0.3.1 dev_dependencies: flutter_test: @@ -30,6 +33,5 @@ dev_dependencies: flutter: uses-material-design: true - -assets: - - assets/schema.json + assets: + - assets/schema.json -- 2.49.1 From 19c99eef9c5a2f581ab71d5d6bca8ee4120bd62c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 00:27:08 +0100 Subject: [PATCH 164/563] use clock.now() instead of DateTime.now() for DTO creation timestamps --- lib/data/dto/game.dart | 3 ++- lib/data/dto/group.dart | 3 ++- lib/data/dto/player.dart | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index b8ebf5c..96e9d73 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:uuid/uuid.dart'; @@ -18,7 +19,7 @@ class Game { this.group, this.winner = '', }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? DateTime.now(); + createdAt = createdAt ?? clock.now(); @override String toString() { diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 6a27de1..46c6f91 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:uuid/uuid.dart'; @@ -13,7 +14,7 @@ class Group { required this.name, required this.members, }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? DateTime.now(); + createdAt = createdAt ?? clock.now(); @override String toString() { diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index 5cbceef..4ef58b1 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:uuid/uuid.dart'; class Player { @@ -7,7 +8,7 @@ class Player { Player({String? id, DateTime? createdAt, required this.name}) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? DateTime.now(); + createdAt = createdAt ?? clock.now(); @override String toString() { -- 2.49.1 From 75c6f4e01c601cb8a084a25b33d7e3876cd29e88 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 00:27:40 +0100 Subject: [PATCH 165/563] verify createdAt timestamps in database tests using mocked clock --- test/db_tests/game_test.dart | 79 ++++++++++++-------- test/db_tests/group_test.dart | 133 +++++++++++++++++++-------------- test/db_tests/player_test.dart | 52 ++++++++----- 3 files changed, 156 insertions(+), 108 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 4f4b23f..0c86e45 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -15,6 +16,8 @@ void main() { late Player player5; late Group testgroup; late Game testgame; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); setUp(() { database = AppDatabase( @@ -25,17 +28,22 @@ void main() { ), ); - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); - testgroup = Group(name: 'Test Group', members: [player1, player2, player3]); - testgame = Game( - name: 'Test Game', - group: testgroup, - players: [player4, player5], - ); + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + testgame = Game( + name: 'Test Game', + group: testgroup, + players: [player4, player5], + ); + }); }); tearDown(() async { await database.close(); @@ -43,34 +51,41 @@ void main() { group('game tests', () { test('game is added correctly', () async { - await database.gameDao.addGame(game: testgame); + await withClock(fakeClock, () async { + await database.gameDao.addGame(game: testgame); - final result = await database.gameDao.getGameById(gameId: testgame.id); + final result = await database.gameDao.getGameById(gameId: testgame.id); - expect(result.id, testgame.id); - expect(result.name, testgame.name); - expect(result.winner, testgame.winner); + expect(result.id, testgame.id); + expect(result.name, testgame.name); + expect(result.winner, testgame.winner); + expect(result.createdAt, testgame.createdAt); - if (result.group != null) { - expect(result.group!.members.length, testgroup.members.length); + if (result.group != null) { + expect(result.group!.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.group!.members[i].id, testgroup.members[i].id); - expect(result.group!.members[i].name, testgroup.members[i].name); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.group!.members[i].id, testgroup.members[i].id); + expect(result.group!.members[i].name, testgroup.members[i].name); + } + } else { + fail('Group is null'); } - } else { - fail('Group is null'); - } - if (result.players != null) { - expect(result.players!.length, testgame.players!.length); + if (result.players != null) { + expect(result.players!.length, testgame.players!.length); - for (int i = 0; i < testgame.players!.length; i++) { - expect(result.players![i].id, testgame.players![i].id); - expect(result.players![i].name, testgame.players![i].name); + for (int i = 0; i < testgame.players!.length; i++) { + expect(result.players![i].id, testgame.players![i].id); + expect(result.players![i].name, testgame.players![i].name); + expect( + result.players![i].createdAt, + testgame.players![i].createdAt, + ); + } + } else { + fail('Players is null'); } - } else { - fail('Players is null'); - } + }); }); test('game is deleted correctly', () async { diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 3a9d8ca..5730617 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,6 +13,8 @@ void main() { late Player player3; late Player player4; late Group testgroup; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); setUp(() { database = AppDatabase( @@ -22,54 +25,69 @@ void main() { ), ); - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - testgroup = Group(name: 'Test Group', members: [player1, player2, player3]); + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + }); }); tearDown(() async { await database.close(); }); group('group tests', () { test('all groups get fetched correctly', () async { - final testgroup2 = Group( - id: 'gr2', - name: 'Second Group', - members: [player2, player3, player4], - ); - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); + await withClock(fakeClock, () async { + final testgroup2 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player3, player4], + ); + await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup2); - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 2); + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); - final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); - expect(fetchedGroup1.name, testgroup.name); - expect(fetchedGroup1.members.length, testgroup.members.length); - expect(fetchedGroup1.members.elementAt(0).id, player1.id); + final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); + expect(fetchedGroup1.name, testgroup.name); + expect(fetchedGroup1.members.length, testgroup.members.length); + expect(fetchedGroup1.members.elementAt(0).id, player1.id); + expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt); - final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); - expect(fetchedGroup2.name, testgroup2.name); - expect(fetchedGroup2.members.length, testgroup2.members.length); - expect(fetchedGroup2.members.elementAt(0).id, player2.id); + final fetchedGroup2 = allGroups.firstWhere( + (g) => g.id == testgroup2.id, + ); + expect(fetchedGroup2.name, testgroup2.name); + expect(fetchedGroup2.members.length, testgroup2.members.length); + expect(fetchedGroup2.members.elementAt(0).id, player2.id); + expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); + }); }); test('group and group members gets added correctly', () async { - await database.groupDao.addGroup(group: testgroup); + await withClock(fakeClock, () async { + await database.groupDao.addGroup(group: testgroup); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); - expect(result.id, testgroup.id); - expect(result.name, testgroup.name); + expect(result.id, testgroup.id); + expect(result.name, testgroup.name); + expect(result.createdAt, testgroup.createdAt); - expect(result.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.members[i].id, testgroup.members[i].id); - expect(result.members[i].name, testgroup.members[i].name); - } + expect(result.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.members[i].id, testgroup.members[i].id); + expect(result.members[i].name, testgroup.members[i].name); + expect(result.members[i].createdAt, testgroup.members[i].createdAt); + } + }); }); test('group gets deleted correctly', () async { @@ -103,36 +121,39 @@ void main() { }); test('Adding player to group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); + await withClock(fakeClock, () async { + await database.groupDao.addGroup(group: testgroup); - await database.playerGroupDao.addPlayerToGroup( - player: player4, - groupId: testgroup.id, - ); + await database.playerGroupDao.addPlayerToGroup( + player: player4, + groupId: testgroup.id, + ); - final playerAdded = await database.playerGroupDao.isPlayerInGroup( - playerId: player4.id, - groupId: testgroup.id, - ); + final playerAdded = await database.playerGroupDao.isPlayerInGroup( + playerId: player4.id, + groupId: testgroup.id, + ); - expect(playerAdded, true); + expect(playerAdded, true); - final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( - playerId: '', - groupId: testgroup.id, - ); + final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( + playerId: '', + groupId: testgroup.id, + ); - expect(playerNotAdded, true); + expect(playerNotAdded, true); - expect(playerAdded, true); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length + 1); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length + 1); - - final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); - expect(addedPlayer.name, player4.name); + final addedPlayer = result.members.firstWhere( + (p) => p.id == player4.id, + ); + expect(addedPlayer.name, player4.name); + expect(addedPlayer.createdAt, player4.createdAt); + }); }); test('Removing player from group works correctly', () async { diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 91f4acb..ce75297 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +8,8 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; late Player testPlayer; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); setUp(() { database = AppDatabase( @@ -17,7 +20,9 @@ void main() { ), ); - testPlayer = Player(name: 'Test Player'); + withClock(fakeClock, () { + testPlayer = Player(name: 'Test Player'); + }); }); tearDown(() async { await database.close(); @@ -25,32 +30,39 @@ void main() { group('player tests', () { test('all players get fetched correctly', () async { - final testPlayer2 = Player(name: 'Second Group'); - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer2); + await withClock(fakeClock, () async { + final testPlayer2 = Player(name: 'Second Group'); + await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer2); - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, - ); - expect(fetchedPlayer1.name, testPlayer.name); + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer.id, + ); + expect(fetchedPlayer1.name, testPlayer.name); + expect(fetchedPlayer1.createdAt, testPlayer.createdAt); - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); + }); }); test('players get inserted correcly ', () async { - await database.playerDao.addPlayer(player: testPlayer); - final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, - ); + await withClock(fakeClock, () async { + await database.playerDao.addPlayer(player: testPlayer); + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); - expect(result.id, testPlayer.id); - expect(result.name, testPlayer.name); + expect(result.id, testPlayer.id); + expect(result.name, testPlayer.name); + expect(result.createdAt, testPlayer.createdAt); + }); }); test('players get deleted correcly ', () async { -- 2.49.1 From 8150b42dbad6a3ce79c46a53d3431b345a2ce3c9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 00:27:47 +0100 Subject: [PATCH 166/563] add `clock` dependency to pubspec.yaml --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index fbbc01a..b17f409 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: provider: ^6.1.5 skeletonizer: ^2.1.0+1 uuid: ^4.5.2 + clock: ^1.1.2 dev_dependencies: flutter_test: -- 2.49.1 From 82e28b7509a29bc23dd59f8a01a0bc4a156f1ea8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 00:32:16 +0100 Subject: [PATCH 167/563] Refactored whole export/import methods in DataTransferService --- .../views/main_menu/settings_view.dart | 160 +----------------- lib/services/data_transfer_service.dart | 135 +++++++++++++++ 2 files changed, 144 insertions(+), 151 deletions(-) create mode 100644 lib/services/data_transfer_service.dart diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 2a1d193..f0a530b 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,17 +1,11 @@ import 'dart:convert'; -import 'dart:io'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:json_schema/json_schema.dart'; -import 'package:provider/provider.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -19,14 +13,6 @@ class SettingsView extends StatefulWidget { @override State createState() => _SettingsViewState(); - /// Helper method to read file content from either bytes or path - static Future _readFileContent(PlatformFile file) async { - if (file.bytes != null) return utf8.decode(file.bytes!); - if (file.path != null) return await File(file.path!).readAsString(); - - throw Exception('Die Datei hat keinen lesbaren Inhalt'); - } - static Future validateJsonSchema(String jsonString) async { final String schemaString; @@ -84,33 +70,30 @@ class _SettingsViewState extends State { ), ), ), - SettingsListTile( title: 'Export data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { - final String json = await _getAppDataAsJson(context); - await exportData(json, 'export'); + final String json = + await DataTransferService.getAppDataAsJson(context); + await DataTransferService.exportData( + json, + 'exported_data', + ); }, ), SettingsListTile( title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => importData(context), + onPressed: () => DataTransferService.importData(context), ), SettingsListTile( title: 'Delete all data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => deleteAllData(context), - ), - SettingsListTile( - title: 'Add Sample Data', - icon: Icons.upload_outlined, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => addSampleData(context), + onPressed: () => DataTransferService.deleteAllData(context), ), ], ), @@ -118,129 +101,4 @@ class _SettingsViewState extends State { ), ); } - - Future deleteAllData(BuildContext context) async { - final db = Provider.of(context, listen: false); - await db.gameDao.deleteAllGames(); - await db.groupDao.deleteAllGroups(); - await db.playerDao.deleteAllPlayers(); - print('[deleteAllData] All data deleted'); - } - - Future addSampleData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final player1 = Player(name: 'Alice'); - final player2 = Player(name: 'Bob'); - final group = Group(name: 'Friends', members: [player1, player2]); - final game = Game(name: 'Sample Game', group: group, winner: 'Alice'); - - await db.playerDao.addPlayer(player: player1); - await db.playerDao.addPlayer(player: player2); - await db.groupDao.addGroup(group: group); - await db.gameDao.addGame(game: game); - } - - Future _getAppDataAsJson(BuildContext context) async { - final db = Provider.of(context, listen: false); - final games = await db.gameDao.getAllGames(); - final groups = await db.groupDao.getAllGroups(); - final players = await db.playerDao.getAllPlayers(); - - // Construct a JSON representation of the data - final Map jsonMap = { - 'games': games.map((game) => game.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), - }; - - return json.encode(jsonMap); - } - - Future exportData(String jsonString, String fileName) async { - try { - final bytes = Uint8List.fromList(utf8.encode(jsonString)); - await FilePicker.platform.saveFile( - fileName: '$fileName.json', - bytes: bytes, - ); - return true; - } catch (e, stack) { - print('[exportData] $e'); - print(stack); - return false; - } - } - - Future importData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final path = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['json'], - ); - - if (path == null) { - print('[importData] No file selected'); - return; - } - - try { - final jsonString = await SettingsView._readFileContent(path.files.single); - - // Checks if the JSON String is in the gameList format - if (await SettingsView.validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; - print('[importData] : $jsonData'); - final List? gamesJson = jsonData['games'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; - - final List importedGames = - gamesJson - ?.map((g) => Game.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; - - for (Player player in importedPlayers) { - await db.playerDao.addPlayer(player: player); - } - - for (Group group in importedGroups) { - await db.groupDao.addGroup(group: group); - } - - for (Game game in importedGames) { - await db.gameDao.addGame(game: game); - } - } else { - print('[importData] Invalid JSON schema'); - return; - } - print('[importData] Data imported successfully'); - return; - } on FormatException catch (e, stack) { - print('[importData] FormatException'); - print('[importData] $e'); - print(stack); - return; - } on Exception catch (e, stack) { - print('[importData] Exception'); - print('[importData] $e'); - print(stack); - return; - } - } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart new file mode 100644 index 0000000..839c0e0 --- /dev/null +++ b/lib/services/data_transfer_service.dart @@ -0,0 +1,135 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:provider/provider.dart'; + +class DataTransferService { + /// Deletes all data from the database. + static Future deleteAllData(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.gameDao.deleteAllGames(); + await db.groupDao.deleteAllGroups(); + await db.playerDao.deleteAllPlayers(); + print('[deleteAllData] All data deleted'); + } + + static Future getAppDataAsJson(BuildContext context) async { + final db = Provider.of(context, listen: false); + final games = await db.gameDao.getAllGames(); + final groups = await db.groupDao.getAllGroups(); + final players = await db.playerDao.getAllPlayers(); + + // Construct a JSON representation of the data + final Map jsonMap = { + 'games': games.map((game) => game.toJson()).toList(), + 'groups': groups.map((group) => group.toJson()).toList(), + 'players': players.map((player) => player.toJson()).toList(), + }; + + return json.encode(jsonMap); + } + + /// Exports the given JSON string to a file with the specified name. + static Future exportData(String jsonString, String fileName) async { + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + await FilePicker.platform.saveFile( + fileName: '$fileName.json', + bytes: bytes, + ); + return true; + } catch (e, stack) { + print('[exportData] $e'); + print(stack); + return false; + } + } + + /// Imports data from a selected JSON file into the database. + static Future importData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final path = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + + if (path == null) { + print('[importData] No file selected'); + return; + } + + try { + final jsonString = await _readFileContent(path.files.single); + + if (await SettingsView.validateJsonSchema(jsonString)) { + final Map jsonData = + json.decode(jsonString) as Map; + + final List? gamesJson = jsonData['games'] as List?; + final List? groupsJson = jsonData['groups'] as List?; + final List? playersJson = + jsonData['players'] as List?; + + final List importedGames = + gamesJson + ?.map((g) => Game.fromJson(g as Map)) + .toList() ?? + []; + final List importedGroups = + groupsJson + ?.map((g) => Group.fromJson(g as Map)) + .toList() ?? + []; + final List importedPlayers = + playersJson + ?.map((p) => Player.fromJson(p as Map)) + .toList() ?? + []; + + for (Player player in importedPlayers) { + await db.playerDao.addPlayer(player: player); + } + + for (Group group in importedGroups) { + await db.groupDao.addGroup(group: group); + } + + for (Game game in importedGames) { + await db.gameDao.addGame(game: game); + } + } else { + print('[importData] Invalid JSON schema'); + return; + } + print('[importData] Data imported successfully'); + return; + } on FormatException catch (e, stack) { + print('[importData] FormatException'); + print('[importData] $e'); + print(stack); + return; + } on Exception catch (e, stack) { + print('[importData] Exception'); + print('[importData] $e'); + print(stack); + return; + } + } + + /// Helper method to read file content from either bytes or path + static Future _readFileContent(PlatformFile file) async { + if (file.bytes != null) return utf8.decode(file.bytes!); + if (file.path != null) return await File(file.path!).readAsString(); + + throw Exception('Die Datei hat keinen lesbaren Inhalt'); + } +} -- 2.49.1 From 69e13e877e2d08fedc5bd8ed8a4c2cdbed09cce6 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:58:47 +0100 Subject: [PATCH 168/563] add game_history_tile --- .../views/main_menu/game_history_view.dart | 13 ++--- .../widgets/game_history_tile.dart | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 lib/presentation/widgets/game_history_tile.dart diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b840d70..b14244b 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; +import 'package:game_tracker/presentation/widgets/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { @@ -178,10 +178,11 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return TopCenteredMessage("Keine Spiele erstellt"); + return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele erstellt', message: '',); } else if (suggestedGameData.isEmpty) { - return TopCenteredMessage("Kein Spiel mit den Suchparametern gefunden."); + return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele mit den Suchparametern gefunden', message: '',); } + return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), itemCount: suggestedGameData.length, @@ -191,12 +192,12 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { ), itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; - return GameTile( + return GameHistoryTile( gameTitle: currentGame['title'], gameType: currentGame['game'], ruleset: currentGame['date'], - players: '${currentGame['players']} Spieler', - winner: currentGame['group'], + groupName: currentGame['group'], + winner: "ich", ); }, ); diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/game_history_tile.dart new file mode 100644 index 0000000..e461f04 --- /dev/null +++ b/lib/presentation/widgets/game_history_tile.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class GameHistoryTile extends StatefulWidget { + final String gameTitle; + final String gameType; + final String ruleset; + final String groupName; + final String winner; + + const GameHistoryTile({ + super.key, + required this.gameTitle, + required this.gameType, + required this.ruleset, + required this.groupName, + required this.winner, + }); + + @override + State createState() => _GameHistoryTileState(); +} + +class _GameHistoryTileState extends State { + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.gameTitle, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(width: 5), + Text( + widget.gameType, + style: const TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + ], + ); + } + +} -- 2.49.1 From 322c51a7646da556e4c8a92924c5aa9ad237d872 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:44:48 +0100 Subject: [PATCH 169/563] Added documentation and feedback in snackbar --- .../views/main_menu/settings_view.dart | 97 ++++++++++++++++++- lib/services/data_transfer_service.dart | 56 +++++++---- 2 files changed, 133 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index f0a530b..1a762ce 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -77,23 +77,37 @@ class _SettingsViewState extends State { onPressed: () async { final String json = await DataTransferService.getAppDataAsJson(context); - await DataTransferService.exportData( + final result = await DataTransferService.exportData( json, 'exported_data', ); + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); }, ), SettingsListTile( title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => DataTransferService.importData(context), + onPressed: () async { + final result = await DataTransferService.importData( + context, + ); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, ), SettingsListTile( title: 'Delete all data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => DataTransferService.deleteAllData(context), + onPressed: () { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: 'Data successfully deleted', + ); + }, ), ], ), @@ -101,4 +115,81 @@ class _SettingsViewState extends State { ), ); } + + /// Displays a snackbar based on the import result. + /// + /// [context] The BuildContext to show the snackbar in. + /// [result] The result of the import operation. + void showImportSnackBar({ + required BuildContext context, + required ImportResult result, + }) { + switch (result) { + case ImportResult.success: + showSnackbar(context: context, message: 'Data successfully imported'); + case ImportResult.invalidSchema: + showSnackbar(context: context, message: 'Invalid Schema'); + case ImportResult.fileReadError: + showSnackbar(context: context, message: 'Error reading file'); + case ImportResult.canceled: + showSnackbar(context: context, message: 'Import canceled'); + case ImportResult.formatException: + showSnackbar( + context: context, + message: 'Format Exception (see console)', + ); + case ImportResult.unknownException: + showSnackbar( + context: context, + message: 'Unknown Exception (see console)', + ); + } + } + + /// Displays a snackbar based on the export result. + /// + /// [context] The BuildContext to show the snackbar in. + /// [result] The result of the export operation. + void showExportSnackBar({ + required BuildContext context, + required ExportResult result, + }) { + switch (result) { + case ExportResult.success: + showSnackbar(context: context, message: 'Data successfully exported'); + case ExportResult.canceled: + showSnackbar(context: context, message: 'Export canceled'); + case ExportResult.unknownException: + showSnackbar( + context: context, + message: 'Unknown Exception (see console)', + ); + } + } + + /// Displays a snackbar with the given message and optional action. + /// + /// [context] The BuildContext to show the snackbar in. + /// [message] The message to display in the snackbar. + /// [duration] The duration for which the snackbar is displayed. + /// [action] An optional callback function to execute when the action button is pressed. + void showSnackbar({ + required BuildContext context, + required String message, + Duration duration = const Duration(seconds: 3), + VoidCallback? action, + }) { + final messenger = ScaffoldMessenger.of(context); + messenger.hideCurrentSnackBar(); + messenger.showSnackBar( + SnackBar( + content: Text(message, style: const TextStyle(color: Colors.white)), + backgroundColor: CustomTheme.onBoxColor, + duration: duration, + action: action != null + ? SnackBarAction(label: 'Rückgängig', onPressed: action) + : null, + ), + ); + } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 839c0e0..6eba1ee 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -11,6 +11,17 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:provider/provider.dart'; +enum ImportResult { + success, + canceled, + fileReadError, + invalidSchema, + formatException, + unknownException, +} + +enum ExportResult { success, canceled, unknownException } + class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { @@ -18,9 +29,10 @@ class DataTransferService { await db.gameDao.deleteAllGames(); await db.groupDao.deleteAllGroups(); await db.playerDao.deleteAllPlayers(); - print('[deleteAllData] All data deleted'); } + /// Retrieves all application data and converts it to a JSON string. + /// Returns the JSON string representation of the data. static Future getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); final games = await db.gameDao.getAllGames(); @@ -38,23 +50,34 @@ class DataTransferService { } /// Exports the given JSON string to a file with the specified name. - static Future exportData(String jsonString, String fileName) async { + /// Returns an [ExportResult] indicating the outcome. + /// + /// [jsonString] The JSON string to be exported. + /// [fileName] The desired name for the exported file (without extension). + static Future exportData( + String jsonString, + String fileName, + ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); - await FilePicker.platform.saveFile( + final path = await FilePicker.platform.saveFile( fileName: '$fileName.json', bytes: bytes, ); - return true; + if (path == null) { + return ExportResult.canceled; + } else { + return ExportResult.success; + } } catch (e, stack) { print('[exportData] $e'); print(stack); - return false; + return ExportResult.unknownException; } } /// Imports data from a selected JSON file into the database. - static Future importData(BuildContext context) async { + static Future importData(BuildContext context) async { final db = Provider.of(context, listen: false); final path = await FilePicker.platform.pickFiles( @@ -63,12 +86,14 @@ class DataTransferService { ); if (path == null) { - print('[importData] No file selected'); - return; + return ImportResult.canceled; } try { final jsonString = await _readFileContent(path.files.single); + if (jsonString == null) { + return ImportResult.fileReadError; + } if (await SettingsView.validateJsonSchema(jsonString)) { final Map jsonData = @@ -107,29 +132,26 @@ class DataTransferService { await db.gameDao.addGame(game: game); } } else { - print('[importData] Invalid JSON schema'); - return; + return ImportResult.invalidSchema; } - print('[importData] Data imported successfully'); - return; + return ImportResult.success; } on FormatException catch (e, stack) { print('[importData] FormatException'); print('[importData] $e'); print(stack); - return; + return ImportResult.formatException; } on Exception catch (e, stack) { print('[importData] Exception'); print('[importData] $e'); print(stack); - return; + return ImportResult.unknownException; } } /// Helper method to read file content from either bytes or path - static Future _readFileContent(PlatformFile file) async { + static Future _readFileContent(PlatformFile file) async { if (file.bytes != null) return utf8.decode(file.bytes!); if (file.path != null) return await File(file.path!).readAsString(); - - throw Exception('Die Datei hat keinen lesbaren Inhalt'); + return null; } } -- 2.49.1 From a8962e68b66bc42793b705557db1e3474ecab765 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:51:37 +0100 Subject: [PATCH 170/563] Added first workflow --- .gitea/workflows/pull_request.yaml | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .gitea/workflows/pull_request.yaml diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml new file mode 100644 index 0000000..8daedbc --- /dev/null +++ b/.gitea/workflows/pull_request.yaml @@ -0,0 +1,39 @@ +name: Pull Request Pipeline + +on: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set Up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 'stable' + channel: 'stable' + + - name: Check Formatting + run: flutter analyze + + test: + runs-on: ubuntu-latest + needs: lint + + steps: + - uses: actions/checkout@v4 + + - name: Set Up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 'stable' + channel: 'stable' + + - name: Get dependencies + run: flutter pub get + + - name: Run Tests + run: flutter test \ No newline at end of file -- 2.49.1 From eeec92181a1f6e63350b77c4f1c936d202d4a4c8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:53:54 +0100 Subject: [PATCH 171/563] Added jq installation --- .gitea/workflows/pull_request.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 8daedbc..9985eb8 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -10,6 +10,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Install jq + run: sudo apt-get install -y jq + - name: Set Up Flutter uses: subosito/flutter-action@v2 with: -- 2.49.1 From ddc8d93592f715779499b7122c86e41324563022 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:56:05 +0100 Subject: [PATCH 172/563] Removed sudo --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 9985eb8..1593c18 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v4 - name: Install jq - run: sudo apt-get install -y jq + run: apt-get install -y jq - name: Set Up Flutter uses: subosito/flutter-action@v2 -- 2.49.1 From 8d91eb37808a41ea865716ab6f10e1a2d104b3c5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 09:58:03 +0100 Subject: [PATCH 173/563] Added update --- .gitea/workflows/pull_request.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 1593c18..25b7c7e 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -11,7 +11,9 @@ jobs: uses: actions/checkout@v4 - name: Install jq - run: apt-get install -y jq + run: | + apt-get update + apt-get install -y jq - name: Set Up Flutter uses: subosito/flutter-action@v2 -- 2.49.1 From 003835472d107cd578dd4ff486a3767f038f0264 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:00:09 +0100 Subject: [PATCH 174/563] Added flutter version again --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 25b7c7e..22869cf 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -18,7 +18,7 @@ jobs: - name: Set Up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: 'stable' + flutter-version: '3.38.2' channel: 'stable' - name: Check Formatting -- 2.49.1 From 6ae39717fdfe4214bfe6383c22433dc46fbdc12c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:02:39 +0100 Subject: [PATCH 175/563] Cleaned flutter cache --- .gitea/workflows/pull_request.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 22869cf..4ac911b 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -14,6 +14,12 @@ jobs: run: | apt-get update apt-get install -y jq + + + - name: Clean Flutter Cache + run: | + rm -rf /opt/hostedtoolcache/flutter + rm -rf ~/flutter - name: Set Up Flutter uses: subosito/flutter-action@v2 -- 2.49.1 From 63d2117a6a40836642801180029aafd4571929b5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:05:55 +0100 Subject: [PATCH 176/563] Tried sth --- .gitea/workflows/pull_request.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 4ac911b..2550dcf 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -21,15 +21,12 @@ jobs: rm -rf /opt/hostedtoolcache/flutter rm -rf ~/flutter - - name: Set Up Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.38.2' - channel: 'stable' - - name: Check Formatting run: flutter analyze + + + test: runs-on: ubuntu-latest needs: lint -- 2.49.1 From 974f06b6b886ef23846ac890906c77264e6c7566 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:06:46 +0100 Subject: [PATCH 177/563] Back again --- .gitea/workflows/pull_request.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 2550dcf..8f8f0d1 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -21,12 +21,17 @@ jobs: rm -rf /opt/hostedtoolcache/flutter rm -rf ~/flutter + - name: Set Up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.38.2' + channel: 'stable' + - name: Check Formatting run: flutter analyze - test: runs-on: ubuntu-latest needs: lint -- 2.49.1 From 6638c2deee81a4f67402e0b3de48fbc6467677f0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:08:04 +0100 Subject: [PATCH 178/563] Removed cache clearing --- .gitea/workflows/pull_request.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 8f8f0d1..b2027d6 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -14,12 +14,6 @@ jobs: run: | apt-get update apt-get install -y jq - - - - name: Clean Flutter Cache - run: | - rm -rf /opt/hostedtoolcache/flutter - rm -rf ~/flutter - name: Set Up Flutter uses: subosito/flutter-action@v2 -- 2.49.1 From 10e56a7241928f2873e52d271b99d006fbe0362f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:09:15 +0100 Subject: [PATCH 179/563] Downgraded flutter version --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index b2027d6..d209dd2 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -18,8 +18,8 @@ jobs: - name: Set Up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.38.2' channel: 'stable' + flutter-version: '3.22.0' - name: Check Formatting run: flutter analyze -- 2.49.1 From 7cc72015d3705a7e75821fec31bf7c84d5049ae6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:11:22 +0100 Subject: [PATCH 180/563] Upgraded to flutter 3.35.6 --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index d209dd2..867f174 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -19,7 +19,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' - flutter-version: '3.22.0' + flutter-version: '3.35.6' - name: Check Formatting run: flutter analyze -- 2.49.1 From 7123d36cd811a6cabc45e36622c382faa7df6d63 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:15:12 +0100 Subject: [PATCH 181/563] Updated flutter installation way --- .gitea/workflows/pull_request.yaml | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 867f174..0b71231 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -15,11 +15,18 @@ jobs: apt-get update apt-get install -y jq - - name: Set Up Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - flutter-version: '3.35.6' + - name: Install Flutter + run: | + # Flutter SDK herunterladen und entpacken + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.6-stable.tar.xz + tar xf flutter_linux_3.35.6-stable.tar.xz + # Flutter zum PATH hinzufügen + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + # Flutter Doctor ausführen (optional) + flutter doctor -v + + - name: Get dependencies + run: flutter pub get - name: Check Formatting run: flutter analyze @@ -31,13 +38,19 @@ jobs: needs: lint steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install jq + run: | + apt-get update + apt-get install -y jq - name: Set Up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: 'stable' channel: 'stable' + flutter-version: '3.35.0' - name: Get dependencies run: flutter pub get -- 2.49.1 From 17c14dd2302c32230667b30de1abd668a22c7c98 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:19:45 +0100 Subject: [PATCH 182/563] Added container --- .gitea/workflows/pull_request.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 0b71231..a28864a 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -6,6 +6,7 @@ on: jobs: lint: runs-on: ubuntu-latest + container: node:18-bullseye steps: - name: Checkout code uses: actions/checkout@v4 @@ -21,7 +22,7 @@ jobs: wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.6-stable.tar.xz tar xf flutter_linux_3.35.6-stable.tar.xz # Flutter zum PATH hinzufügen - echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + echo "$(pwd)/flutter/bin" >> /usr/local/bin # Flutter Doctor ausführen (optional) flutter doctor -v -- 2.49.1 From e108bb41f6b8ffa464bce490c6ac440dcf76d4ba Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:22:55 +0100 Subject: [PATCH 183/563] Corrected installation --- .gitea/workflows/pull_request.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index a28864a..62aa58e 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -18,13 +18,12 @@ jobs: - name: Install Flutter run: | - # Flutter SDK herunterladen und entpacken wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.6-stable.tar.xz tar xf flutter_linux_3.35.6-stable.tar.xz - # Flutter zum PATH hinzufügen - echo "$(pwd)/flutter/bin" >> /usr/local/bin - # Flutter Doctor ausführen (optional) - flutter doctor -v + # Flutter-Pfad zur PATH-Variable hinzufügen + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + # Alternativ für Docker/act: + echo "PATH=$(pwd)/flutter/bin:$PATH" >> $GITHUB_ENV - name: Get dependencies run: flutter pub get -- 2.49.1 From e852a4d53976fe0a6a051dfd03169ece057d9392 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:26:34 +0100 Subject: [PATCH 184/563] Added git safe directory --- .gitea/workflows/pull_request.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 62aa58e..a222eff 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -25,6 +25,12 @@ jobs: # Alternativ für Docker/act: echo "PATH=$(pwd)/flutter/bin:$PATH" >> $GITHUB_ENV + - name: Configure Git Safe Directory + run: | + git config --global --add safe.directory /workspace/liquid-development/game-tracker + git config --global --add safe.directory /workspace/liquid-development/game-tracker/flutter + + - name: Get dependencies run: flutter pub get -- 2.49.1 From 601b7d0a4f654bfc8bff848178560c3ddf169ee8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:36:48 +0100 Subject: [PATCH 185/563] Changed flutter installation --- .gitea/workflows/pull_request.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index a222eff..c8b25e9 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -16,14 +16,11 @@ jobs: apt-get update apt-get install -y jq - - name: Install Flutter - run: | - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.6-stable.tar.xz - tar xf flutter_linux_3.35.6-stable.tar.xz - # Flutter-Pfad zur PATH-Variable hinzufügen - echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - # Alternativ für Docker/act: - echo "PATH=$(pwd)/flutter/bin:$PATH" >> $GITHUB_ENV + - name: Set Up Flutter + uses: flutter-actions/setup-flutter@v2 + with: + flutter-version: '3.35.6' + channel: 'stable' - name: Configure Git Safe Directory run: | -- 2.49.1 From 7ac5986588749d2d55e6eca9225b95900135d088 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:41:12 +0100 Subject: [PATCH 186/563] Updated whole workflow --- .gitea/workflows/pull_request.yaml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index c8b25e9..5338cab 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -11,28 +11,29 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Install jq + - name: Install dependencies run: | apt-get update - apt-get install -y jq + apt-get install -y jq git wget unzip xz-utils - - name: Set Up Flutter - uses: flutter-actions/setup-flutter@v2 - with: - flutter-version: '3.35.6' - channel: 'stable' - - - name: Configure Git Safe Directory + - name: Install Flutter (lokal) run: | - git config --global --add safe.directory /workspace/liquid-development/game-tracker - git config --global --add safe.directory /workspace/liquid-development/game-tracker/flutter + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz + tar xf flutter_linux_3.38.2-stable.tar.xz + # Git-Safe-Directory für Flutter-Pfad setzen + git config --global --add safe.directory "$(pwd)/flutter" + # Flutter-Pfad setzen + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + - name: Configure Git Safe Directory (für Projekt) + run: | + git config --global --add safe.directory "$(pwd)" - name: Get dependencies - run: flutter pub get + run: ./flutter/bin/flutter pub get - name: Check Formatting - run: flutter analyze + run: ./flutter/bin/flutter analyze -- 2.49.1 From 6ae1ce9bc71ea90cd231188d315adf2042612bef Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:47:14 +0100 Subject: [PATCH 187/563] Updated analyzing --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 5338cab..f44a8f9 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -33,7 +33,7 @@ jobs: run: ./flutter/bin/flutter pub get - name: Check Formatting - run: ./flutter/bin/flutter analyze + run: ./flutter/bin/flutter analyze lib test -- 2.49.1 From 91b68eac3edd02f30b3db38fbee01567b308837b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:50:56 +0100 Subject: [PATCH 188/563] Implemented test workflow --- .gitea/workflows/pull_request.yaml | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index f44a8f9..2dbe146 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -25,9 +25,9 @@ jobs: # Flutter-Pfad setzen echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - - name: Configure Git Safe Directory (für Projekt) - run: | - git config --global --add safe.directory "$(pwd)" + # - name: Configure Git Safe Directory (für Projekt) + # run: | + # git config --global --add safe.directory "$(pwd)" - name: Get dependencies run: ./flutter/bin/flutter pub get @@ -39,25 +39,27 @@ jobs: test: runs-on: ubuntu-latest - needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - - name: Install jq + - name: Install dependencies run: | apt-get update - apt-get install -y jq + apt-get install -y jq git wget unzip xz-utils - - name: Set Up Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - flutter-version: '3.35.0' + - name: Install Flutter (lokal) + run: | + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz + tar xf flutter_linux_3.38.2-stable.tar.xz + # Git-Safe-Directory für Flutter-Pfad setzen + git config --global --add safe.directory "$(pwd)/flutter" + # Flutter-Pfad setzen + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - name: Get dependencies - run: flutter pub get + run: ./flutter/bin/flutter pub get - - name: Run Tests - run: flutter test \ No newline at end of file + - name: Check Formatting + run: ./flutter/bin/flutter test \ No newline at end of file -- 2.49.1 From a3b45053e72108812beaee76feeb0df20eae6d63 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:54:58 +0100 Subject: [PATCH 189/563] Finalized workflow --- .gitea/workflows/pull_request.yaml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 2dbe146..1122252 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -11,12 +11,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Install dependencies + - name: Install jq run: | apt-get update - apt-get install -y jq git wget unzip xz-utils + apt-get install -y jq - - name: Install Flutter (lokal) + - name: Install Flutter (wget) run: | wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz @@ -25,18 +25,12 @@ jobs: # Flutter-Pfad setzen echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - # - name: Configure Git Safe Directory (für Projekt) - # run: | - # git config --global --add safe.directory "$(pwd)" - - name: Get dependencies run: ./flutter/bin/flutter pub get - - name: Check Formatting + - name: Analyze Formatting run: ./flutter/bin/flutter analyze lib test - - test: runs-on: ubuntu-latest @@ -47,9 +41,9 @@ jobs: - name: Install dependencies run: | apt-get update - apt-get install -y jq git wget unzip xz-utils + apt-get install -y jq - - name: Install Flutter (lokal) + - name: Install Flutter (wget) run: | wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz @@ -61,5 +55,5 @@ jobs: - name: Get dependencies run: ./flutter/bin/flutter pub get - - name: Check Formatting + - name: Run tests run: ./flutter/bin/flutter test \ No newline at end of file -- 2.49.1 From c89243f886d9241fdf26d13b49de2c443e2aafb1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 10:57:53 +0100 Subject: [PATCH 190/563] Tried sth --- .gitea/workflows/pull_request.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 1122252..187eef9 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -29,7 +29,7 @@ jobs: run: ./flutter/bin/flutter pub get - name: Analyze Formatting - run: ./flutter/bin/flutter analyze lib test + run: flutter analyze lib test test: runs-on: ubuntu-latest @@ -53,7 +53,7 @@ jobs: echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - name: Get dependencies - run: ./flutter/bin/flutter pub get + run: flutter pub get - name: Run tests - run: ./flutter/bin/flutter test \ No newline at end of file + run: flutter test \ No newline at end of file -- 2.49.1 From f136400c7e9bc5e0dde36e9de823a9e665ce6e97 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:01:33 +0100 Subject: [PATCH 191/563] Final changes? --- .gitea/workflows/pull_request.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 187eef9..dcc95c1 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -20,13 +20,13 @@ jobs: run: | wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz - # Git-Safe-Directory für Flutter-Pfad setzen + # Set Git safe directory for Flutter path git config --global --add safe.directory "$(pwd)/flutter" - # Flutter-Pfad setzen + # Set Flutter path echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - name: Get dependencies - run: ./flutter/bin/flutter pub get + run: flutter pub get - name: Analyze Formatting run: flutter analyze lib test @@ -47,9 +47,9 @@ jobs: run: | wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz - # Git-Safe-Directory für Flutter-Pfad setzen + # Set Git safe directory for Flutter path git config --global --add safe.directory "$(pwd)/flutter" - # Flutter-Pfad setzen + # Set Flutter path echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - name: Get dependencies -- 2.49.1 From 1732878c7fcb3d64f6ad4c840175ab1737c23bdc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:03:10 +0100 Subject: [PATCH 192/563] Tested sth --- .gitea/workflows/pull_request.yaml | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index dcc95c1..6299f66 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -56,4 +56,44 @@ jobs: run: flutter pub get - name: Run tests - run: flutter test \ No newline at end of file + run: flutter test + + format: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + apt-get update + apt-get install -y jq + + - name: Install Flutter (wget) + run: | + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz + tar xf flutter_linux_3.38.2-stable.tar.xz + # Set Git safe directory for Flutter path + git config --global --add safe.directory "$(pwd)/flutter" + # Set Flutter path + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + + - name: Get & upgrade dependencies + run: | + flutter pub get + flutter pub upgrade --major-versions + + - name: Auto-format + run: | + dart format . + dart fix --apply + + - name: Commit Changes + if: steps.check_changes.outputs.changes_detected == 'true' + run: | + # git config --global user.name "GitHub Actions" + # git config --global user.email "actions@github.com" + git add . + git commit -m "Actions: Auto-formatting [skip ci]" + git push \ No newline at end of file -- 2.49.1 From e5268ebc12d776aaa83f49a8921c86e2d920b388 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:03:10 +0100 Subject: [PATCH 193/563] Tested sth --- .gitea/workflows/pull_request.yaml | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index dcc95c1..6299f66 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -56,4 +56,44 @@ jobs: run: flutter pub get - name: Run tests - run: flutter test \ No newline at end of file + run: flutter test + + format: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + apt-get update + apt-get install -y jq + + - name: Install Flutter (wget) + run: | + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz + tar xf flutter_linux_3.38.2-stable.tar.xz + # Set Git safe directory for Flutter path + git config --global --add safe.directory "$(pwd)/flutter" + # Set Flutter path + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + + - name: Get & upgrade dependencies + run: | + flutter pub get + flutter pub upgrade --major-versions + + - name: Auto-format + run: | + dart format . + dart fix --apply + + - name: Commit Changes + if: steps.check_changes.outputs.changes_detected == 'true' + run: | + # git config --global user.name "GitHub Actions" + # git config --global user.email "actions@github.com" + git add . + git commit -m "Actions: Auto-formatting [skip ci]" + git push \ No newline at end of file -- 2.49.1 From 594ea947c2e074236935541e9d5aa67d8b9d11a5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:06:40 +0100 Subject: [PATCH 194/563] Tested sth --- .gitea/workflows/pull_request.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 6299f66..01a3780 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -6,7 +6,6 @@ on: jobs: lint: runs-on: ubuntu-latest - container: node:18-bullseye steps: - name: Checkout code uses: actions/checkout@v4 @@ -33,7 +32,6 @@ jobs: test: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 @@ -60,7 +58,6 @@ jobs: format: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 -- 2.49.1 From 346dddcf62ec7bdd82858ccb256bc6f20d2f3fed Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:08:01 +0100 Subject: [PATCH 195/563] testing formatting --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 22e794d..0e075d5 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -86,7 +86,7 @@ jobs: - name: Auto-format run: | dart format . - dart fix --apply + dart fix --apply - name: Commit Changes if: steps.check_changes.outputs.changes_detected == 'true' -- 2.49.1 From dd8af42a472568178efca2103d14ab5b5c20c7e9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:08:30 +0100 Subject: [PATCH 196/563] corrected workflow --- .gitea/workflows/pull_request.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 0e075d5..d30f3fe 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -91,8 +91,6 @@ jobs: - name: Commit Changes if: steps.check_changes.outputs.changes_detected == 'true' run: | - # git config --global user.name "GitHub Actions" - # git config --global user.email "actions@github.com" git add . git commit -m "Actions: Auto-formatting [skip ci]" - git push \ No newline at end of file + git push -- 2.49.1 From 5d8047b3ba4a59e1743e5d4ae5b9315dd792da2c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:12:02 +0100 Subject: [PATCH 197/563] Updated directorys --- .gitea/workflows/pull_request.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index d30f3fe..89bf9df 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -60,6 +60,7 @@ jobs: format: runs-on: ubuntu-latest + if: false # steps: - name: Checkout code uses: actions/checkout@v4 @@ -85,8 +86,8 @@ jobs: - name: Auto-format run: | - dart format . - dart fix --apply + dart format lib test + dart fix --apply lib test - name: Commit Changes if: steps.check_changes.outputs.changes_detected == 'true' -- 2.49.1 From 89d7bb54a1bf16f5da98fe32ff0d0c8cd4d44c25 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:12:46 +0100 Subject: [PATCH 198/563] Removed false --- .gitea/workflows/pull_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 89bf9df..c4504a6 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -60,7 +60,7 @@ jobs: format: runs-on: ubuntu-latest - if: false # + # if: false # Needs bot user steps: - name: Checkout code uses: actions/checkout@v4 -- 2.49.1 From 81cdeb7ed6a29edda6bb1069d35646f150ccdf8e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:15:30 +0100 Subject: [PATCH 199/563] Skipped other runs for testing --- .gitea/workflows/pull_request.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index c4504a6..1c8e4d3 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -5,6 +5,7 @@ on: jobs: lint: + if: false runs-on: ubuntu-latest container: node:18-bullseye steps: @@ -32,8 +33,8 @@ jobs: run: flutter analyze lib test test: + if: false runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 @@ -86,8 +87,8 @@ jobs: - name: Auto-format run: | - dart format lib test - dart fix --apply lib test + dart format lib + dart fix --apply lib - name: Commit Changes if: steps.check_changes.outputs.changes_detected == 'true' -- 2.49.1 From 0659d202b3d90c01c0d3ebe4a58e46b34615a3ac Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:18:23 +0100 Subject: [PATCH 200/563] Remove if clause --- .gitea/workflows/pull_request.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 1c8e4d3..7f85a05 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -91,7 +91,6 @@ jobs: dart fix --apply lib - name: Commit Changes - if: steps.check_changes.outputs.changes_detected == 'true' run: | git add . git commit -m "Actions: Auto-formatting [skip ci]" -- 2.49.1 From aade42c0a600a63ac75cbaa548649dcc163d88ce Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:21:43 +0100 Subject: [PATCH 201/563] Tried sth --- .gitea/workflows/pull_request.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 7f85a05..58f29e5 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -87,11 +87,13 @@ jobs: - name: Auto-format run: | - dart format lib + dart format lib dart fix --apply lib - name: Commit Changes run: | - git add . + git status + git add lib/ + git status git commit -m "Actions: Auto-formatting [skip ci]" git push -- 2.49.1 From ca4bf03bab19910730ad3bbddffb51fe13596e4c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:27:48 +0100 Subject: [PATCH 202/563] Finalized pull request workflow --- .gitea/workflows/pull_request.yaml | 44 +----------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 58f29e5..43d36d2 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -5,9 +5,7 @@ on: jobs: lint: - if: false runs-on: ubuntu-latest - container: node:18-bullseye steps: - name: Checkout code uses: actions/checkout@v4 @@ -33,7 +31,6 @@ jobs: run: flutter analyze lib test test: - if: false runs-on: ubuntu-latest steps: - name: Checkout code @@ -57,43 +54,4 @@ jobs: run: flutter pub get - name: Run tests - run: flutter test - - format: - runs-on: ubuntu-latest - # if: false # Needs bot user - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - apt-get update - apt-get install -y jq - - - name: Install Flutter (wget) - run: | - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz - tar xf flutter_linux_3.38.2-stable.tar.xz - # Set Git safe directory for Flutter path - git config --global --add safe.directory "$(pwd)/flutter" - # Set Flutter path - echo "$(pwd)/flutter/bin" >> $GITHUB_PATH - - - name: Get & upgrade dependencies - run: | - flutter pub get - flutter pub upgrade --major-versions - - - name: Auto-format - run: | - dart format lib - dart fix --apply lib - - - name: Commit Changes - run: | - git status - git add lib/ - git status - git commit -m "Actions: Auto-formatting [skip ci]" - git push + run: flutter test \ No newline at end of file -- 2.49.1 From 74fffa95e287cc0b2df5e778fb461fb81ebfd54c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:28:00 +0100 Subject: [PATCH 203/563] Added push workflow (not active) --- .gitea/workflows/push.yaml | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .gitea/workflows/push.yaml diff --git a/.gitea/workflows/push.yaml b/.gitea/workflows/push.yaml new file mode 100644 index 0000000..7a6bb0b --- /dev/null +++ b/.gitea/workflows/push.yaml @@ -0,0 +1,48 @@ +name: Pull Request Pipeline + +on: + push: + branches: + - "development" + - "main" + +jobs: + format: + runs-on: ubuntu-latest + if: false # Needs bot user + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + apt-get update + apt-get install -y jq + + - name: Install Flutter (wget) + run: | + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz + tar xf flutter_linux_3.38.2-stable.tar.xz + # Set Git safe directory for Flutter path + git config --global --add safe.directory "$(pwd)/flutter" + # Set Flutter path + echo "$(pwd)/flutter/bin" >> $GITHUB_PATH + + - name: Get & upgrade dependencies + run: | + flutter pub get + flutter pub upgrade --major-versions + + - name: Auto-format + run: | + dart format lib + dart fix --apply lib + + # Needs credentials, push access and the right files need to be staged + - name: Commit Changes + run: | + git status + git add lib/ + git status + git commit -m "Actions: Auto-formatting [skip ci]" + git push -- 2.49.1 From 0ac8c2105260328c506cdd39ee18d0301124d1cd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 11:32:22 +0100 Subject: [PATCH 204/563] Formatted files so that pipeline doesnt fail --- .../views/main_menu/game_history_view.dart | 22 ++++++++--------- .../widgets/double_row_info_tile.dart | 24 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3642a88..90cc50a 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -134,16 +134,16 @@ class _GameHistoryViewState extends State { children: [ Column( children: [ - Container(margin: EdgeInsets.only(bottom: 75)), + Container(margin: const EdgeInsets.only(bottom: 75)), Expanded( child: gameHistoryListView(allGameData, suggestedGameData), ), ], ), Container( - margin: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), + margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), child: SearchBar( - leading: Icon(Icons.search), + leading: const Icon(Icons.search), onChanged: (value) { if (value.isEmpty) { setState(() { @@ -178,16 +178,16 @@ class _GameHistoryViewState extends State { Widget gameHistoryListView(allGameData, suggestedGameData) { if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return TopCenteredMessage( + return const TopCenteredMessage( icon: Icons.info, - title: "Info", - message: "Keine Spiele erstellt", + title: 'Info', + message: 'Keine Spiele erstellt', ); } else if (suggestedGameData.isEmpty) { - return TopCenteredMessage( + return const TopCenteredMessage( icon: Icons.search, - title: "Info", - message: "Kein Spiel mit den Suchparametern gefunden.", + title: 'Info', + message: 'Kein Spiel mit den Suchparametern gefunden.', ); } return ListView.builder( @@ -195,9 +195,9 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { itemBuilder: (context, index) { final currentGame = suggestedGameData[index]; return doubleRowInfoTile( - currentGame['game'] + ": ", + currentGame['game'] + ': ', currentGame['title'], - currentGame['players'].toString() + " Spieler", + "${currentGame['players']} Spieler", currentGame['group'], currentGame['date'], ); diff --git a/lib/presentation/widgets/double_row_info_tile.dart b/lib/presentation/widgets/double_row_info_tile.dart index 621cc74..57404ff 100644 --- a/lib/presentation/widgets/double_row_info_tile.dart +++ b/lib/presentation/widgets/double_row_info_tile.dart @@ -9,8 +9,8 @@ Widget doubleRowInfoTile( String titleLowerRight, ) { return Container( - margin: EdgeInsets.symmetric(vertical: 5, horizontal: 10), - padding: EdgeInsets.all(10), + margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: CustomTheme.secondaryColor, @@ -22,18 +22,18 @@ Widget doubleRowInfoTile( Expanded( flex: 10, child: Text( - "$titleOneUpperLeft $titleTwoUpperLeft", - style: TextStyle(fontSize: 20), + '$titleOneUpperLeft $titleTwoUpperLeft', + style: const TextStyle(fontSize: 20), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), - Spacer(), + const Spacer(), Expanded( flex: 3, child: Text( - "$titleUpperRight", - style: TextStyle(fontSize: 20), + titleUpperRight, + style: const TextStyle(fontSize: 20), overflow: TextOverflow.ellipsis, maxLines: 1, textAlign: TextAlign.end, @@ -46,18 +46,18 @@ Widget doubleRowInfoTile( Expanded( flex: 10, child: Text( - "$titleLowerLeft", - style: TextStyle(fontSize: 20), + titleLowerLeft, + style: const TextStyle(fontSize: 20), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), - Spacer(), + const Spacer(), Expanded( flex: 4, child: Text( - "$titleLowerRight", - style: TextStyle(fontSize: 20), + titleLowerRight, + style: const TextStyle(fontSize: 20), overflow: TextOverflow.ellipsis, maxLines: 1, textAlign: TextAlign.end, -- 2.49.1 From c76e193b4d63396877e2a1af8b4a57e0f31bb9f6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 15:02:32 +0100 Subject: [PATCH 205/563] created all objects in setup() funktion to avoid redundant withClock --- test/db_tests/game_test.dart | 51 +++++++++++------------- test/db_tests/group_test.dart | 71 ++++++++++++++++------------------ test/db_tests/player_test.dart | 49 +++++++++++------------ 3 files changed, 79 insertions(+), 92 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0c86e45..d726425 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -51,41 +51,36 @@ void main() { group('game tests', () { test('game is added correctly', () async { - await withClock(fakeClock, () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame); - final result = await database.gameDao.getGameById(gameId: testgame.id); + final result = await database.gameDao.getGameById(gameId: testgame.id); - expect(result.id, testgame.id); - expect(result.name, testgame.name); - expect(result.winner, testgame.winner); - expect(result.createdAt, testgame.createdAt); + expect(result.id, testgame.id); + expect(result.name, testgame.name); + expect(result.winner, testgame.winner); + expect(result.createdAt, testgame.createdAt); - if (result.group != null) { - expect(result.group!.members.length, testgroup.members.length); + if (result.group != null) { + expect(result.group!.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.group!.members[i].id, testgroup.members[i].id); - expect(result.group!.members[i].name, testgroup.members[i].name); - } - } else { - fail('Group is null'); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.group!.members[i].id, testgroup.members[i].id); + expect(result.group!.members[i].name, testgroup.members[i].name); } - if (result.players != null) { - expect(result.players!.length, testgame.players!.length); + } else { + fail('Group is null'); + } + if (result.players != null) { + expect(result.players!.length, testgame.players!.length); - for (int i = 0; i < testgame.players!.length; i++) { - expect(result.players![i].id, testgame.players![i].id); - expect(result.players![i].name, testgame.players![i].name); - expect( - result.players![i].createdAt, - testgame.players![i].createdAt, - ); - } - } else { - fail('Players is null'); + for (int i = 0; i < testgame.players!.length; i++) { + expect(result.players![i].id, testgame.players![i].id); + expect(result.players![i].name, testgame.players![i].name); + expect(result.players![i].createdAt, testgame.players![i].createdAt); } - }); + } else { + fail('Players is null'); + } }); test('game is deleted correctly', () async { diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 5730617..1241419 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -13,6 +13,7 @@ void main() { late Player player3; late Player player4; late Group testgroup; + late Group testgroup2; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -34,6 +35,11 @@ void main() { name: 'Test Group', members: [player1, player2, player3], ); + testgroup2 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player3, player4], + ); }); }); tearDown(() async { @@ -41,53 +47,42 @@ void main() { }); group('group tests', () { test('all groups get fetched correctly', () async { - await withClock(fakeClock, () async { - final testgroup2 = Group( - id: 'gr2', - name: 'Second Group', - members: [player2, player3, player4], - ); - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); + await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup2); - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 2); + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); - final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); - expect(fetchedGroup1.name, testgroup.name); - expect(fetchedGroup1.members.length, testgroup.members.length); - expect(fetchedGroup1.members.elementAt(0).id, player1.id); - expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt); + final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); + expect(fetchedGroup1.name, testgroup.name); + expect(fetchedGroup1.members.length, testgroup.members.length); + expect(fetchedGroup1.members.elementAt(0).id, player1.id); + expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt); - final fetchedGroup2 = allGroups.firstWhere( - (g) => g.id == testgroup2.id, - ); - expect(fetchedGroup2.name, testgroup2.name); - expect(fetchedGroup2.members.length, testgroup2.members.length); - expect(fetchedGroup2.members.elementAt(0).id, player2.id); - expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); - }); + final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); + expect(fetchedGroup2.name, testgroup2.name); + expect(fetchedGroup2.members.length, testgroup2.members.length); + expect(fetchedGroup2.members.elementAt(0).id, player2.id); + expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); }); test('group and group members gets added correctly', () async { - await withClock(fakeClock, () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); - expect(result.id, testgroup.id); - expect(result.name, testgroup.name); - expect(result.createdAt, testgroup.createdAt); + expect(result.id, testgroup.id); + expect(result.name, testgroup.name); + expect(result.createdAt, testgroup.createdAt); - expect(result.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.members[i].id, testgroup.members[i].id); - expect(result.members[i].name, testgroup.members[i].name); - expect(result.members[i].createdAt, testgroup.members[i].createdAt); - } - }); + expect(result.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.members[i].id, testgroup.members[i].id); + expect(result.members[i].name, testgroup.members[i].name); + expect(result.members[i].createdAt, testgroup.members[i].createdAt); + } }); test('group gets deleted correctly', () async { diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index ce75297..fa65f67 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -8,6 +8,7 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; late Player testPlayer; + late Player testPlayer2; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -22,6 +23,7 @@ void main() { withClock(fakeClock, () { testPlayer = Player(name: 'Test Player'); + testPlayer2 = Player(name: 'Second Group'); }); }); tearDown(() async { @@ -30,39 +32,34 @@ void main() { group('player tests', () { test('all players get fetched correctly', () async { - await withClock(fakeClock, () async { - final testPlayer2 = Player(name: 'Second Group'); - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer2); + await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer2); - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, - ); - expect(fetchedPlayer1.name, testPlayer.name); - expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer.id, + ); + expect(fetchedPlayer1.name, testPlayer.name); + expect(fetchedPlayer1.createdAt, testPlayer.createdAt); - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); - expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); - }); + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); test('players get inserted correcly ', () async { - await withClock(fakeClock, () async { - await database.playerDao.addPlayer(player: testPlayer); - final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, - ); + await database.playerDao.addPlayer(player: testPlayer); + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); - expect(result.id, testPlayer.id); - expect(result.name, testPlayer.name); - expect(result.createdAt, testPlayer.createdAt); - }); + expect(result.id, testPlayer.id); + expect(result.name, testPlayer.name); + expect(result.createdAt, testPlayer.createdAt); }); test('players get deleted correcly ', () async { -- 2.49.1 From b82261317c87c14b0001c8f3fd457373bc5cbd4f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 15:09:40 +0100 Subject: [PATCH 206/563] move CreateGroupView to main_menu directory --- .../views/main_menu/{create_group => }/create_group_view.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/presentation/views/main_menu/{create_group => }/create_group_view.dart (100%) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_group/create_group_view.dart rename to lib/presentation/views/main_menu/create_group_view.dart -- 2.49.1 From 98b02adc85a8d8958596ff28fa93934d843970e2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 15:43:49 +0100 Subject: [PATCH 207/563] Formatted files so that pipeline doesnt fail --- .gitea/workflows/push.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/push.yaml b/.gitea/workflows/push.yaml index 7a6bb0b..c5afb83 100644 --- a/.gitea/workflows/push.yaml +++ b/.gitea/workflows/push.yaml @@ -41,7 +41,9 @@ jobs: # Needs credentials, push access and the right files need to be staged - name: Commit Changes run: | - git status + git config --global user.name "Gitea Actions" + git config --global user.email "actions@gitea.com" + git status git add lib/ git status git commit -m "Actions: Auto-formatting [skip ci]" -- 2.49.1 From 3b6a91402290ca5b51bb19728d19c4c462d963a3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 15:52:04 +0100 Subject: [PATCH 208/563] removed uneccessary withClock --- test/db_tests/group_test.dart | 48 ++++++++++++++++------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 1241419..a076ab0 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -116,39 +116,35 @@ void main() { }); test('Adding player to group works correctly', () async { - await withClock(fakeClock, () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testgroup); - await database.playerGroupDao.addPlayerToGroup( - player: player4, - groupId: testgroup.id, - ); + await database.playerGroupDao.addPlayerToGroup( + player: player4, + groupId: testgroup.id, + ); - final playerAdded = await database.playerGroupDao.isPlayerInGroup( - playerId: player4.id, - groupId: testgroup.id, - ); + final playerAdded = await database.playerGroupDao.isPlayerInGroup( + playerId: player4.id, + groupId: testgroup.id, + ); - expect(playerAdded, true); + expect(playerAdded, true); - final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( - playerId: '', - groupId: testgroup.id, - ); + final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( + playerId: '', + groupId: testgroup.id, + ); - expect(playerNotAdded, true); + expect(playerNotAdded, true); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length + 1); + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length + 1); - final addedPlayer = result.members.firstWhere( - (p) => p.id == player4.id, - ); - expect(addedPlayer.name, player4.name); - expect(addedPlayer.createdAt, player4.createdAt); - }); + final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); + expect(addedPlayer.name, player4.name); + expect(addedPlayer.createdAt, player4.createdAt); }); test('Removing player from group works correctly', () async { -- 2.49.1 From 54e1756e79326324205acba4874fd0e12b14e88a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:39:05 +0100 Subject: [PATCH 209/563] moved create_group_view from subfolder to root --- lib/presentation/views/main_menu/groups_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7e6f59d..f74d20c 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_group/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -- 2.49.1 From 9365313c9215a5ad77f2a16b8c2c05f7dad03ad9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:39:32 +0100 Subject: [PATCH 210/563] button not working --- .../views/main_menu/create_group_view.dart | 7 ++- .../widgets/custom_width_button.dart | 46 ++++++++++++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 81d5e36..f43fc8d 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ButtonStyle; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -265,8 +265,8 @@ class _CreateGroupViewState extends State { ), CustomWidthButton( text: 'Create group', - disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, + buttonStyle: ButtonStyle.secondary, onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null @@ -277,14 +277,13 @@ class _CreateGroupViewState extends State { members: selectedPlayers, ), ); + if (!context.mounted) return; if (success) { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); - if (!mounted) return; Navigator.pop(context); } else { - if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/custom_width_button.dart index b0b9bd3..2a79d1b 100644 --- a/lib/presentation/widgets/custom_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -7,45 +7,67 @@ class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - this.disabledInfillColor, this.buttonStyle = ButtonStyle.primary, required this.sizeRelativeToWidth, - required this.onPressed, + this.onPressed, }); final String text; - final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; final ButtonStyle buttonStyle; @override Widget build(BuildContext context) { + + final Color buttonBackgroundColor; + final Color disabledBackgroundColor; + final Color borderSideColor; + final Color disabledBorderSideColor; + final Color textcolor; + final Color disabledTextColor; + + + if(buttonStyle == ButtonStyle.primary){ + buttonBackgroundColor = CustomTheme.primaryColor; + disabledBackgroundColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + borderSideColor = Colors.transparent; + disabledBorderSideColor = Colors.transparent; + textcolor = Colors.white; + disabledTextColor = Colors.white.withValues(alpha: 0.24); + } else{ + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + borderSideColor = CustomTheme.primaryColor.withValues(alpha: 0.6 ); + disabledBorderSideColor = Colors.transparent; + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + } + + return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - disabledBackgroundColor: disabledInfillColor, + disabledBackgroundColor: disabledBackgroundColor, minimumSize: Size( MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, ), - backgroundColor: buttonStyle == ButtonStyle.primary - ? CustomTheme.primaryColor - : CustomTheme.secondaryColor, + backgroundColor: buttonBackgroundColor, side: BorderSide( - color: buttonStyle == ButtonStyle.primary - ? CustomTheme.primaryColor - : CustomTheme.secondaryColor, + color: borderSideColor, width: 2, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( text, - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.w500, fontSize: 22, - color: Colors.white, + color: (onPressed == null) + ? disabledTextColor + : textcolor, ), ), ); -- 2.49.1 From 201fd70685c2d7040db9c9d0533254db2bef0dd6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:44:46 +0100 Subject: [PATCH 211/563] Update `TextIconListTile` padding and replace `IconButton` with `GestureDetector` --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index 92d0251..1907928 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -17,7 +17,7 @@ class TextIconListTile extends StatelessWidget { Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 15), decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), @@ -41,9 +41,9 @@ class TextIconListTile extends StatelessWidget { ), ), if (iconEnabled) - IconButton( - icon: const Icon(Icons.add, size: 20), - onPressed: onPressed, + GestureDetector( + child: const Icon(Icons.add, size: 20), + onTap: onPressed, ), ], ), -- 2.49.1 From 018332d8e603cabe5ab6ee6e46770fd1e16a595c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:48:43 +0100 Subject: [PATCH 212/563] Refactor widget directory structure by organizing tiles and buttons - Move `GameTile` and `DoubleRowInfoTile` to `presentation/widgets/tiles/` - Move `CustomWidthButton` and `QuickCreateButton` to `presentation/widgets/buttons/` - Update import paths in `HomeView`, `GroupsView`, `GameHistoryView`, and `CreateGroupView` --- lib/presentation/views/main_menu/create_group_view.dart | 2 +- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/views/main_menu/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 4 ++-- .../widgets/{ => buttons}/custom_width_button.dart | 0 .../widgets/{ => buttons}/quick_create_button.dart | 0 .../widgets/{ => tiles}/double_row_info_tile.dart | 0 lib/presentation/widgets/{ => tiles}/game_tile.dart | 0 8 files changed, 5 insertions(+), 5 deletions(-) rename lib/presentation/widgets/{ => buttons}/custom_width_button.dart (100%) rename lib/presentation/widgets/{ => buttons}/quick_create_button.dart (100%) rename lib/presentation/widgets/{ => tiles}/double_row_info_tile.dart (100%) rename lib/presentation/widgets/{ => tiles}/game_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f43fc8d..365cf27 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -3,8 +3,8 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3642a88..7c19bbf 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/double_row_info_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index f74d20c..c45cf21 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.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/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index cf6288a..34e4be3 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; -import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart similarity index 100% rename from lib/presentation/widgets/custom_width_button.dart rename to lib/presentation/widgets/buttons/custom_width_button.dart diff --git a/lib/presentation/widgets/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart similarity index 100% rename from lib/presentation/widgets/quick_create_button.dart rename to lib/presentation/widgets/buttons/quick_create_button.dart diff --git a/lib/presentation/widgets/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart similarity index 100% rename from lib/presentation/widgets/double_row_info_tile.dart rename to lib/presentation/widgets/tiles/double_row_info_tile.dart diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart similarity index 100% rename from lib/presentation/widgets/game_tile.dart rename to lib/presentation/widgets/tiles/game_tile.dart -- 2.49.1 From 1232cb8f0dc259c4c4105030fe25255c14683a06 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:50:02 +0100 Subject: [PATCH 213/563] Fix `GestureDetector` child ordering in `TextIconListTile` --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index 1907928..c0fe673 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -42,8 +42,8 @@ class TextIconListTile extends StatelessWidget { ), if (iconEnabled) GestureDetector( - child: const Icon(Icons.add, size: 20), onTap: onPressed, + child: const Icon(Icons.add, size: 20), ), ], ), -- 2.49.1 From 3f79a7b89826b60fd33712cb016418507da1b292 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:26:51 +0100 Subject: [PATCH 214/563] sourcing enums out to enums.dart --- lib/core/enums.dart | 2 ++ lib/presentation/views/main_menu/create_group_view.dart | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 lib/core/enums.dart diff --git a/lib/core/enums.dart b/lib/core/enums.dart new file mode 100644 index 0000000..320eaf7 --- /dev/null +++ b/lib/core/enums.dart @@ -0,0 +1,2 @@ +/// Button types used for styling the [CustomWidthButton] +enum ButtonType { primary, secondary, tertiary } diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 365cf27..db8890f 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' hide ButtonStyle; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -266,7 +267,7 @@ class _CreateGroupViewState extends State { CustomWidthButton( text: 'Create group', sizeRelativeToWidth: 0.95, - buttonStyle: ButtonStyle.secondary, + buttonType: ButtonType.primary, onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null -- 2.49.1 From e71e65b197f1fa05bb82d977d8012e5a92dd73f2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:27:27 +0100 Subject: [PATCH 215/563] Corrected button color behaviour and added tertiary button --- .../widgets/buttons/custom_width_button.dart | 127 ++++++++++++------ 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 2a79d1b..bce78ed 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; - -enum ButtonStyle { primary, secondary } +import 'package:game_tracker/core/enums.dart'; class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - this.buttonStyle = ButtonStyle.primary, + this.buttonType = ButtonType.primary, required this.sizeRelativeToWidth, this.onPressed, }); @@ -15,61 +14,101 @@ class CustomWidthButton extends StatelessWidget { final String text; final double sizeRelativeToWidth; final VoidCallback? onPressed; - final ButtonStyle buttonStyle; + final ButtonType buttonType; @override Widget build(BuildContext context) { - final Color buttonBackgroundColor; final Color disabledBackgroundColor; final Color borderSideColor; - final Color disabledBorderSideColor; final Color textcolor; final Color disabledTextColor; - - if(buttonStyle == ButtonStyle.primary){ - buttonBackgroundColor = CustomTheme.primaryColor; - disabledBackgroundColor = CustomTheme.primaryColor.withValues(alpha: 0.24); - borderSideColor = Colors.transparent; - disabledBorderSideColor = Colors.transparent; + if (buttonType == ButtonType.primary) { textcolor = Colors.white; disabledTextColor = Colors.white.withValues(alpha: 0.24); - } else{ + buttonBackgroundColor = CustomTheme.primaryColor; + disabledBackgroundColor = CustomTheme.primaryColor.withValues( + alpha: 0.24, + ); + + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else if (buttonType == ButtonType.secondary) { + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.5); buttonBackgroundColor = Colors.transparent; disabledBackgroundColor = Colors.transparent; - borderSideColor = CustomTheme.primaryColor.withValues(alpha: 0.6 ); - disabledBorderSideColor = Colors.transparent; + borderSideColor = onPressed != null + ? CustomTheme.primaryColor + : CustomTheme.primaryColor.withValues(alpha: 0.5); + + return OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: BorderSide(color: borderSideColor, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else { textcolor = CustomTheme.primaryColor; - disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.3); + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + + return TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: const BorderSide(style: BorderStyle.none), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); } - - - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - disabledBackgroundColor: disabledBackgroundColor, - minimumSize: Size( - MediaQuery.sizeOf(context).width * sizeRelativeToWidth, - 60, - ), - backgroundColor: buttonBackgroundColor, - side: BorderSide( - color: borderSideColor, - width: 2, - ), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - child: Text( - text, - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 22, - color: (onPressed == null) - ? disabledTextColor - : textcolor, - ), - ), - ); } } -- 2.49.1 From 248d652e0641cba2a45447a486b97b6b52c5a175 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:32:25 +0100 Subject: [PATCH 216/563] Made onPressed not required --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index c0fe673..5e272c9 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -3,13 +3,13 @@ import 'package:game_tracker/core/custom_theme.dart'; class TextIconListTile extends StatelessWidget { final String text; - final VoidCallback onPressed; + final VoidCallback? onPressed; final bool iconEnabled; const TextIconListTile({ super.key, required this.text, - required this.onPressed, + this.onPressed, this.iconEnabled = true, }); -- 2.49.1 From 8e2befaf3da1a7199d2e388705e60f75fa4c7024 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 19:19:04 +0100 Subject: [PATCH 217/563] Fixed button color problem --- .../widgets/buttons/custom_width_button.dart | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index bce78ed..17c9dc5 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -26,11 +26,13 @@ class CustomWidthButton extends StatelessWidget { if (buttonType == ButtonType.primary) { textcolor = Colors.white; - disabledTextColor = Colors.white.withValues(alpha: 0.24); + disabledTextColor = Color.lerp(textcolor, Colors.black, 0.5)!; buttonBackgroundColor = CustomTheme.primaryColor; - disabledBackgroundColor = CustomTheme.primaryColor.withValues( - alpha: 0.24, - ); + disabledBackgroundColor = Color.lerp( + buttonBackgroundColor, + Colors.black, + 0.5, + )!; return ElevatedButton( onPressed: onPressed, @@ -55,12 +57,12 @@ class CustomWidthButton extends StatelessWidget { ); } else if (buttonType == ButtonType.secondary) { textcolor = CustomTheme.primaryColor; - disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.5); + disabledTextColor = Color.lerp(textcolor, Colors.black, 0.5)!; buttonBackgroundColor = Colors.transparent; disabledBackgroundColor = Colors.transparent; borderSideColor = onPressed != null ? CustomTheme.primaryColor - : CustomTheme.primaryColor.withValues(alpha: 0.5); + : Color.lerp(CustomTheme.primaryColor, Colors.black, 0.5)!; return OutlinedButton( onPressed: onPressed, @@ -86,7 +88,11 @@ class CustomWidthButton extends StatelessWidget { ); } else { textcolor = CustomTheme.primaryColor; - disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.3); + disabledTextColor = Color.lerp( + CustomTheme.primaryColor, + Colors.black, + 0.5, + )!; buttonBackgroundColor = Colors.transparent; disabledBackgroundColor = Colors.transparent; -- 2.49.1 From 87b1a7d57f547db37ad2ff868e3476d862605672 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 19:26:35 +0100 Subject: [PATCH 218/563] Moved ImportStatus & ExportStatus to enums.dart --- lib/core/enums.dart | 22 +++++++++++++++++++ .../views/main_menu/settings_view.dart | 1 + lib/services/data_transfer_service.dart | 12 +--------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 320eaf7..8c809b0 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -1,2 +1,24 @@ /// Button types used for styling the [CustomWidthButton] enum ButtonType { primary, secondary, tertiary } + +/// Result types for import operations in the [SettingsView] +/// - [ImportResult.success]: The import operation was successful. +/// - [ImportResult.canceled]: The import operation was canceled by the user. +/// - [ImportResult.fileReadError]: There was an error reading the selected file. +/// - [ImportResult.invalidSchema]: The JSON schema of the imported data is invalid. +/// - [ImportResult.formatException]: A format exception occurred during import. +/// - [ImportResult.unknownException]: An exception occurred during import. +enum ImportResult { + success, + canceled, + fileReadError, + invalidSchema, + formatException, + unknownException, +} + +/// Result types for export operations in the [SettingsView] +/// - [ExportResult.success]: The export operation was successful. +/// - [ExportResult.canceled]: The export operation was canceled by the user. +/// - [ExportResult.unknownException]: An exception occurred during export. +enum ExportResult { success, canceled, unknownException } diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 1a762ce..e679467 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:json_schema/json_schema.dart'; diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 6eba1ee..93c788d 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -11,17 +12,6 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:provider/provider.dart'; -enum ImportResult { - success, - canceled, - fileReadError, - invalidSchema, - formatException, - unknownException, -} - -enum ExportResult { success, canceled, unknownException } - class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { -- 2.49.1 From 9434282ed194b089c1c075ea5410c5c549301728 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:20:21 +0100 Subject: [PATCH 219/563] Added createdAt attribute in dto classes and json schema --- assets/schema.json | 45 ++++++++++++---------------------------- lib/data/dto/game.dart | 4 +++- lib/data/dto/group.dart | 4 +++- lib/data/dto/player.dart | 9 ++++++-- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index c33fab2..eedfb05 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -13,6 +13,9 @@ "id": { "type": "string" }, + "createdAt": { + "type": "string" + }, "name": { "type": "string" }, @@ -28,6 +31,7 @@ }, "required": [ "id", + "createdAt", "name", "players", "group", @@ -45,6 +49,9 @@ "id": { "type": "string" }, + "createdAt": { + "type": "string" + }, "name": { "type": "string" }, @@ -57,19 +64,7 @@ "id": { "type": "string" }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "name" - ] - }, - { - "type": "object", - "properties": { - "id": { + "createdAt": { "type": "string" }, "name": { @@ -78,6 +73,7 @@ }, "required": [ "id", + "createdAt", "name" ] } @@ -86,6 +82,7 @@ }, "required": [ "id", + "createdAt", "name", "members" ] @@ -101,19 +98,7 @@ "id": { "type": "string" }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "name" - ] - }, - { - "type": "object", - "properties": { - "id": { + "createdAt": { "type": "string" }, "name": { @@ -122,16 +107,12 @@ }, "required": [ "id", + "createdAt", "name" ] } ] } - }, - "required": [ - "games", - "groups", - "players" - ] + } } diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 30898e5..4188bc4 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -5,11 +5,11 @@ import 'package:uuid/uuid.dart'; class Game { final String id; + final DateTime createdAt; final String name; final List? players; final Group? group; final String? winner; - final DateTime createdAt; Game({ String? id, @@ -30,6 +30,7 @@ class Game { Game.fromJson(Map json) : id = json['id'], name = json['name'], + createdAt = DateTime.parse(json['createdAt']), players = json['players'] != null ? (json['players'] as List) .map((playerJson) => Player.fromJson(playerJson)) @@ -41,6 +42,7 @@ class Game { /// Converts the Game instance to a JSON object. Map toJson() => { 'id': id, + 'createdAt': createdAt.toIso8601String(), 'name': name, 'players': players?.map((player) => player.toJson()).toList(), 'group': group?.toJson(), diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 3f00bf5..92dbd09 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -4,9 +4,9 @@ import 'package:uuid/uuid.dart'; class Group { final String id; + final DateTime createdAt; final String name; final List members; - final DateTime createdAt; Group({ String? id, @@ -24,6 +24,7 @@ class Group { /// Creates a Group instance from a JSON object. Group.fromJson(Map json) : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), name = json['name'], members = (json['members'] as List) .map((memberJson) => Player.fromJson(memberJson)) @@ -32,6 +33,7 @@ class Group { /// Converts the Group instance to a JSON object. Map toJson() => { 'id': id, + 'createdAt': createdAt.toIso8601String(), 'name': name, 'members': members.map((member) => member.toJson()).toList(), }; diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index f7e05d2..cfb4f4b 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -3,8 +3,8 @@ import 'package:uuid/uuid.dart'; class Player { final String id; - final String name; final DateTime createdAt; + final String name; Player({String? id, DateTime? createdAt, required this.name}) : id = id ?? const Uuid().v4(), @@ -18,8 +18,13 @@ class Player { /// Creates a Player instance from a JSON object. Player.fromJson(Map json) : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), name = json['name']; /// Converts the Player instance to a JSON object. - Map toJson() => {'id': id, 'name': name}; + Map toJson() => { + 'id': id, + 'createdAt': createdAt.toIso8601String(), + 'name': name, + }; } -- 2.49.1 From f7073a83a42c334a404e1ed21bf69d717f3d2345 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:21:30 +0100 Subject: [PATCH 220/563] Added insert mode --- lib/data/dao/player_game_dao.dart | 1 + lib/data/dao/player_group_dao.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 8f367f8..d58417e 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -46,6 +46,7 @@ class PlayerGameDao extends DatabaseAccessor }) async { await into(playerGameTable).insert( PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, ); } } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index fe067ae..e200958 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -56,6 +56,7 @@ class PlayerGroupDao extends DatabaseAccessor await into(playerGroupTable).insert( PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + mode: InsertMode.insertOrReplace, ); return true; -- 2.49.1 From 412cfff9f53bebe9ea2df6a453c1c3687b440f2d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:21:55 +0100 Subject: [PATCH 221/563] Added methods for inserting list of players, groups, games --- lib/data/dao/game_dao.dart | 116 +++++++++- lib/data/dao/group_dao.dart | 50 +++- lib/data/dao/player_dao.dart | 24 ++ lib/data/db/database.g.dart | 293 +++--------------------- lib/services/data_transfer_service.dart | 14 +- 5 files changed, 220 insertions(+), 277 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 9ed9849..018866a 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -50,14 +50,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Also adds associated players and group if they exist. Future addGame({required Game game}) async { await db.transaction(() async { - for (final p in game.players ?? []) { - await db.playerDao.addPlayer(player: p); - await db.playerGameDao.addPlayerToGame(gameId: game.id, playerId: p.id); - } - if (game.group != null) { - await db.groupDao.addGroup(group: game.group!); - await db.groupGameDao.addGroupToGame(game.id, game.group!.id); - } await into(gameTable).insert( GameTableCompanion.insert( id: game.id, @@ -67,6 +59,114 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ), mode: InsertMode.insertOrReplace, ); + + if (game.players != null) { + await db.playerDao.addPlayers(players: game.players!); + for (final p in game.players ?? []) { + await db.playerGameDao.addPlayerToGame( + gameId: game.id, + playerId: p.id, + ); + } + } + + if (game.group != null) { + await db.groupDao.addGroup(group: game.group!); + await db.groupGameDao.addGroupToGame(game.id, game.group!.id); + } + }); + } + + Future addGames({required List games}) async { + if (games.isEmpty) return; + await db.transaction(() async { + // Add all games in batch + await db.batch( + (b) => b.insertAll( + gameTable, + games + .map( + (game) => GameTableCompanion.insert( + id: game.id, + name: game.name, + createdAt: game.createdAt, + winnerId: Value(game.winner), + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + // Add all groups of the games in batch + await db.batch( + (b) => b.insertAll( + db.groupTable, + games + .where((game) => game.group != null) + .map( + (game) => GroupTableCompanion.insert( + id: game.group!.id, + name: game.group!.name, + createdAt: game.group!.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all players of the games in batch + await db.batch((b) async { + for (final game in games) { + if (game.players != null) { + for (final p in game.players ?? []) { + b.insert( + db.playerGameTable, + PlayerGameTableCompanion.insert( + gameId: game.id, + playerId: p.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all group-game associations in batch + await db.batch((b) async { + for (final game in games) { + if (game.group != null) { + b.insert( + db.groupGameTable, + GroupGameTableCompanion.insert( + gameId: game.id, + groupId: game.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + }); + + // Add all player-game associations in batch + await db.batch((b) async { + for (final game in games) { + if (game.players != null) { + for (final p in game.players ?? []) { + b.insert( + db.playerTable, + PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); }); } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 6eaea09..695d78a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -57,6 +57,10 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { name: group.name, createdAt: group.createdAt, ), + mode: InsertMode.insertOrReplace, + ); + await Future.wait( + group.members.map((player) => db.playerDao.addPlayer(player: player)), ); await db.batch( (b) => b.insertAll( @@ -69,17 +73,57 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), + mode: InsertMode.insertOrReplace, ), ); - await Future.wait( - group.members.map((player) => db.playerDao.addPlayer(player: player)), - ); }); return true; } return false; } + /// Adds multiple groups to the database. + /// Also adds the group's members to the [PlayerGroupTable]. + Future addGroups({required List groups}) async { + if (groups.isEmpty) return; + await db.transaction(() async { + await db.batch( + (b) => b.insertAll( + groupTable, + groups + .map( + (group) => GroupTableCompanion.insert( + id: group.id, + name: group.name, + createdAt: group.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + for (final group in groups) { + await db.playerDao.addPlayers(players: group.members); + + await db.batch( + (b) => b.insertAll( + db.playerGroupTable, + group.members + .map( + (member) => PlayerGroupTableCompanion.insert( + playerId: member.id, + groupId: group.id, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + }); + } + /// Deletes the group with the given [id] from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future deleteGroup({required String groupId}) async { diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 353819c..53e251f 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -42,12 +42,36 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { name: player.name, createdAt: player.createdAt, ), + mode: InsertMode.insertOrReplace, ); return true; } return false; } + /// Adds multiple [players] to the database in a batch operation. + Future addPlayers({required List players}) async { + if (players.isEmpty) return false; + + await db.batch( + (b) => b.insertAll( + playerTable, + players + .map( + (player) => PlayerTableCompanion.insert( + id: player.id, + name: player.name, + createdAt: player.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + return true; + } + /// Deletes the player with the given [id] from the database. /// Returns `true` if the player was deleted, `false` if the player did not exist. Future deletePlayer({required String playerId}) async { diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 3f10169..f211d0c 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -552,12 +552,9 @@ class $GameTableTable extends GameTable late final GeneratedColumn winnerId = GeneratedColumn( 'winner_id', aliasedName, - false, + true, type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES player_table (id) ON DELETE CASCADE', - ), + requiredDuringInsert: false, ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', @@ -602,8 +599,6 @@ class $GameTableTable extends GameTable _winnerIdMeta, winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), ); - } else if (isInserting) { - context.missing(_winnerIdMeta); } if (data.containsKey('created_at')) { context.handle( @@ -633,7 +628,7 @@ class $GameTableTable extends GameTable winnerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}winner_id'], - )!, + ), createdAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}created_at'], @@ -650,12 +645,12 @@ class $GameTableTable extends GameTable class GameTableData extends DataClass implements Insertable { final String id; final String name; - final String winnerId; + final String? winnerId; final DateTime createdAt; const GameTableData({ required this.id, required this.name, - required this.winnerId, + this.winnerId, required this.createdAt, }); @override @@ -663,7 +658,9 @@ class GameTableData extends DataClass implements Insertable { final map = {}; map['id'] = Variable(id); map['name'] = Variable(name); - map['winner_id'] = Variable(winnerId); + if (!nullToAbsent || winnerId != null) { + map['winner_id'] = Variable(winnerId); + } map['created_at'] = Variable(createdAt); return map; } @@ -672,7 +669,9 @@ class GameTableData extends DataClass implements Insertable { return GameTableCompanion( id: Value(id), name: Value(name), - winnerId: Value(winnerId), + winnerId: winnerId == null && nullToAbsent + ? const Value.absent() + : Value(winnerId), createdAt: Value(createdAt), ); } @@ -685,7 +684,7 @@ class GameTableData extends DataClass implements Insertable { return GameTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), + winnerId: serializer.fromJson(json['winnerId']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -695,7 +694,7 @@ class GameTableData extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), + 'winnerId': serializer.toJson(winnerId), 'createdAt': serializer.toJson(createdAt), }; } @@ -703,12 +702,12 @@ class GameTableData extends DataClass implements Insertable { GameTableData copyWith({ String? id, String? name, - String? winnerId, + Value winnerId = const Value.absent(), DateTime? createdAt, }) => GameTableData( id: id ?? this.id, name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, + winnerId: winnerId.present ? winnerId.value : this.winnerId, createdAt: createdAt ?? this.createdAt, ); GameTableData copyWithCompanion(GameTableCompanion data) { @@ -746,7 +745,7 @@ class GameTableData extends DataClass implements Insertable { class GameTableCompanion extends UpdateCompanion { final Value id; final Value name; - final Value winnerId; + final Value winnerId; final Value createdAt; final Value rowid; const GameTableCompanion({ @@ -759,12 +758,11 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion.insert({ required String id, required String name, - required String winnerId, + this.winnerId = const Value.absent(), required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), name = Value(name), - winnerId = Value(winnerId), createdAt = Value(createdAt); static Insertable custom({ Expression? id, @@ -785,7 +783,7 @@ class GameTableCompanion extends UpdateCompanion { GameTableCompanion copyWith({ Value? id, Value? name, - Value? winnerId, + Value? winnerId, Value? createdAt, Value? rowid, }) { @@ -1538,13 +1536,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ - WritePropagation( - on: TableUpdateQuery.onTableName( - 'player_table', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('game_table', kind: UpdateKind.delete)], - ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', @@ -1609,24 +1600,6 @@ final class $$PlayerTableTableReferences extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> { $$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$GameTableTable, List> - _gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.gameTable, - aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId), - ); - - $$GameTableTableProcessedTableManager get gameTableRefs { - final manager = $$GameTableTableTableManager( - $_db, - $_db.gameTable, - ).filter((f) => f.winnerId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } - static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -1698,31 +1671,6 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression gameTableRefs( - Expression Function($$GameTableTableFilterComposer f) f, - ) { - final $$GameTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -1817,31 +1765,6 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - Expression gameTableRefs( - Expression Function($$GameTableTableAnnotationComposer a) f, - ) { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.gameTable, - getReferencedColumn: (t) => t.winnerId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( - $db: $db, - $table: $db.gameTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } - Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -1907,7 +1830,6 @@ class $$PlayerTableTableTableManager (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -1956,42 +1878,16 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - gameTableRefs = false, - playerGroupTableRefs = false, - playerGameTableRefs = false, - }) { + ({playerGroupTableRefs = false, playerGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (gameTableRefs) db.gameTable, if (playerGroupTableRefs) db.playerGroupTable, if (playerGameTableRefs) db.playerGameTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (gameTableRefs) - await $_getPrefetchedData< - PlayerTableData, - $PlayerTableTable, - GameTableData - >( - currentTable: table, - referencedTable: $$PlayerTableTableReferences - ._gameTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlayerTableTableReferences( - db, - table, - p0, - ).gameTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.winnerId == item.id, - ), - typedResults: items, - ), if (playerGroupTableRefs) await $_getPrefetchedData< PlayerTableData, @@ -2055,7 +1951,6 @@ typedef $$PlayerTableTableProcessedTableManager = (PlayerTableData, $$PlayerTableTableReferences), PlayerTableData, PrefetchHooks Function({ - bool gameTableRefs, bool playerGroupTableRefs, bool playerGameTableRefs, }) @@ -2436,7 +2331,7 @@ typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ required String id, required String name, - required String winnerId, + Value winnerId, required DateTime createdAt, Value rowid, }); @@ -2444,7 +2339,7 @@ typedef $$GameTableTableUpdateCompanionBuilder = GameTableCompanion Function({ Value id, Value name, - Value winnerId, + Value winnerId, Value createdAt, Value rowid, }); @@ -2453,25 +2348,6 @@ final class $$GameTableTableReferences extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PlayerTableTable _winnerIdTable(_$AppDatabase db) => - db.playerTable.createAlias( - $_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id), - ); - - $$PlayerTableTableProcessedTableManager get winnerId { - final $_column = $_itemColumn('winner_id')!; - - final manager = $$PlayerTableTableTableManager( - $_db, - $_db.playerTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_winnerIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - static MultiTypedResultKey<$PlayerGameTableTable, List> _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGameTable, @@ -2530,34 +2406,16 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column), ); - $$PlayerTableTableFilterComposer get winnerId { - final $$PlayerTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableFilterComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableFilterComposer f) f, ) { @@ -2628,33 +2486,15 @@ class $$GameTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get winnerId => $composableBuilder( + column: $table.winnerId, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column), ); - - $$PlayerTableTableOrderingComposer get winnerId { - final $$PlayerTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableOrderingComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } } class $$GameTableTableAnnotationComposer @@ -2672,32 +2512,12 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get winnerId => + $composableBuilder(column: $table.winnerId, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - $$PlayerTableTableAnnotationComposer get winnerId { - final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.winnerId, - referencedTable: $db.playerTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$PlayerTableTableAnnotationComposer( - $db: $db, - $table: $db.playerTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - Expression playerGameTableRefs( Expression Function($$PlayerGameTableTableAnnotationComposer a) f, ) { @@ -2763,7 +2583,6 @@ class $$GameTableTableTableManager (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) @@ -2783,7 +2602,7 @@ class $$GameTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), - Value winnerId = const Value.absent(), + Value winnerId = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => GameTableCompanion( @@ -2797,7 +2616,7 @@ class $$GameTableTableTableManager ({ required String id, required String name, - required String winnerId, + Value winnerId = const Value.absent(), required DateTime createdAt, Value rowid = const Value.absent(), }) => GameTableCompanion.insert( @@ -2816,49 +2635,14 @@ class $$GameTableTableTableManager ) .toList(), prefetchHooksCallback: - ({ - winnerId = false, - playerGameTableRefs = false, - groupGameTableRefs = false, - }) { + ({playerGameTableRefs = false, groupGameTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGameTableRefs) db.playerGameTable, if (groupGameTableRefs) db.groupGameTable, ], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (winnerId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.winnerId, - referencedTable: $$GameTableTableReferences - ._winnerIdTable(db), - referencedColumn: $$GameTableTableReferences - ._winnerIdTable(db) - .id, - ) - as T; - } - - return state; - }, + addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (playerGameTableRefs) @@ -2924,7 +2708,6 @@ typedef $$GameTableTableProcessedTableManager = (GameTableData, $$GameTableTableReferences), GameTableData, PrefetchHooks Function({ - bool winnerId, bool playerGameTableRefs, bool groupGameTableRefs, }) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 93c788d..13eb658 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -110,17 +110,9 @@ class DataTransferService { .toList() ?? []; - for (Player player in importedPlayers) { - await db.playerDao.addPlayer(player: player); - } - - for (Group group in importedGroups) { - await db.groupDao.addGroup(group: group); - } - - for (Game game in importedGames) { - await db.gameDao.addGame(game: game); - } + await db.playerDao.addPlayers(players: importedPlayers); + await db.groupDao.addGroups(groups: importedGroups); + await db.gameDao.addGames(games: importedGames); } else { return ImportResult.invalidSchema; } -- 2.49.1 From b684ebd4f66b54ee378ffdf58a365b3fc30953b2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 20:25:37 +0100 Subject: [PATCH 222/563] Renamed workflow according to file name --- .gitea/workflows/push.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/push.yaml b/.gitea/workflows/push.yaml index c5afb83..700e96b 100644 --- a/.gitea/workflows/push.yaml +++ b/.gitea/workflows/push.yaml @@ -1,4 +1,4 @@ -name: Pull Request Pipeline +name: Push Pipeline on: push: -- 2.49.1 From f7c1d6e975c6719122810426c47b9fd691f1b8e1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:13:29 +0100 Subject: [PATCH 223/563] Added missing await --- lib/data/dao/player_group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index e200958..93acf0b 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -51,7 +51,7 @@ class PlayerGroupDao extends DatabaseAccessor } if (await db.playerDao.playerExists(playerId: player.id) == false) { - db.playerDao.addPlayer(player: player); + await db.playerDao.addPlayer(player: player); } await into(playerGroupTable).insert( -- 2.49.1 From cf71b40718d3a13005fb8f277b96ee9fcd351e0a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:19:54 +0100 Subject: [PATCH 224/563] changed export file name --- lib/presentation/views/main_menu/settings_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index e679467..a6e66fa 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -80,7 +80,7 @@ class _SettingsViewState extends State { await DataTransferService.getAppDataAsJson(context); final result = await DataTransferService.exportData( json, - 'exported_data', + 'game_tracker-data', ); if (!context.mounted) return; showExportSnackBar(context: context, result: result); -- 2.49.1 From a8d4e640cfaeee074deff45956fec538c550039c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:39:01 +0100 Subject: [PATCH 225/563] Tabs update themselves after settings view --- .../main_menu/custom_navigation_bar.dart | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 20ced7a..9dd0658 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -17,12 +17,7 @@ class CustomNavigationBar extends StatefulWidget { class _CustomNavigationBarState extends State with SingleTickerProviderStateMixin { int currentIndex = 0; - final List tabs = [ - const HomeView(), - const GameHistoryView(), - const GroupsView(), - const StatisticsView(), - ]; + int tabKeyCount = 0; @override void initState() { @@ -31,6 +26,22 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { + // Pretty ugly but works + final List tabs = [ + KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), + KeyedSubtree( + key: ValueKey('games_$tabKeyCount'), + child: const GameHistoryView(), + ), + KeyedSubtree( + key: ValueKey('groups_$tabKeyCount'), + child: const GroupsView(), + ), + KeyedSubtree( + key: ValueKey('stats_$tabKeyCount'), + child: const StatisticsView(), + ), + ]; return Scaffold( appBar: AppBar( centerTitle: true, @@ -42,10 +53,15 @@ class _CustomNavigationBarState extends State scrolledUnderElevation: 0, actions: [ IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsView()), - ), + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), + ); + setState(() { + tabKeyCount++; + }); + }, icon: const Icon(Icons.settings), ), ], -- 2.49.1 From 822bc03c83363789ef412f7bcdfbecf3d1ebcf45 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:41:30 +0100 Subject: [PATCH 226/563] Added dialog --- .../views/main_menu/settings_view.dart | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index a6e66fa..70f7663 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -103,11 +103,31 @@ class _SettingsViewState extends State { icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { - DataTransferService.deleteAllData(context); - showSnackbar( + showDialog( context: context, - message: 'Data successfully deleted', - ); + builder: (context) => AlertDialog( + title: const Text('Delete all data?'), + content: const Text('This can\'t be undone'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Abbrechen'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Löschen'), + ), + ], + ), + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: 'Daten erfolgreich gelöscht', + ); + } + }); }, ), ], -- 2.49.1 From f40a9ad09b0c0f119a35e788cb2c44e6c38ebc28 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:42:07 +0100 Subject: [PATCH 227/563] Updated schema --- assets/schema.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index eedfb05..4bbd5d3 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -20,10 +20,10 @@ "type": "string" }, "players": { - "type": "null" + "type": "object" }, "group": { - "type": "null" + "type": "object" }, "winner": { "type": "string" @@ -33,8 +33,6 @@ "id", "createdAt", "name", - "players", - "group", "winner" ] } -- 2.49.1 From 45650133a7daa9b41bdd7a989e1dd6a95339865d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 21:51:13 +0100 Subject: [PATCH 228/563] Corrected schema again --- assets/schema.json | 84 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 4bbd5d3..69f889b 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -1,5 +1,3 @@ - - { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", @@ -20,15 +18,78 @@ "type": "string" }, "players": { - "type": "object" - }, - "group": { - "type": "object" - }, - "winner": { - "type": "string" + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] } }, + "group": { + "type": [ + "object", + "null" + ], + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "members": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] + } + ] + } + }, + "required": [ + "id", + "createdAt", + "name", + "members" + ] + }, + "winner": { + "type": "string" + }, "required": [ "id", "createdAt", @@ -91,7 +152,10 @@ "type": "array", "items": [ { - "type": "object", + "type": [ + "object", + "null" + ], "properties": { "id": { "type": "string" -- 2.49.1 From fa0e9a5dfd7c957e51b370bb9505211b3da51ca0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 20 Nov 2025 16:53:14 +0100 Subject: [PATCH 229/563] add trailing button functionality to CustomSearchBar --- .../widgets/custom_search_bar.dart | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index b482efb..61a2154 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -6,11 +6,17 @@ class CustomSearchBar extends StatelessWidget { final String hintText; final ValueChanged? onChanged; final BoxConstraints? constraints; + final bool trailingButtonEnabled; + final bool trailingButtonShown; + final VoidCallback? onTrailingButtonPressed; const CustomSearchBar({ super.key, required this.controller, required this.hintText, + this.trailingButtonShown = false, + this.trailingButtonEnabled = true, + this.onTrailingButtonPressed, this.onChanged, this.constraints, }); @@ -25,6 +31,20 @@ class CustomSearchBar extends StatelessWidget { onChanged: onChanged, hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), + trailing: trailingButtonShown + ? [ + GestureDetector( + onTap: onTrailingButtonPressed, + child: Icon( + Icons.add_circle, + color: trailingButtonEnabled + ? null + : Colors.grey.withValues(alpha: 0.2), + ), + ), + SizedBox(width: 5), + ] + : null, backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), shape: WidgetStateProperty.all( -- 2.49.1 From bce4cdcb2d32caf4a0b3d6398909d08ec2ee4652 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 20 Nov 2025 16:53:38 +0100 Subject: [PATCH 230/563] Enable player creation via search bar in CreateGroupView --- .../views/main_menu/create_group_view.dart | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index db8890f..45f5af8 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -37,6 +37,10 @@ class _CreateGroupViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); + loadPlayerList(); + } + + void loadPlayerList() { _allPlayersFuture = db.playerDao.getAllPlayers(); _allPlayersFuture.then((loadedPlayers) { setState(() { @@ -99,6 +103,41 @@ class _CreateGroupViewState extends State { minHeight: 45, ), hintText: 'Search for players', + trailingButtonShown: true, + trailingButtonEnabled: + _searchBarController.text.isNotEmpty, + onTrailingButtonPressed: () async { + String playerName = _searchBarController.text; + if (playerName.isEmpty) return; + bool success = await db.playerDao.addPlayer( + player: Player(name: playerName), + ); + if (success) { + loadPlayerList(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Successfully added player $playerName.', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + _searchBarController.clear(); + } else { + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Could not add player $playerName.', + style: TextStyle(color: Colors.white), + ), + ), + ); + } + }, onChanged: (value) { setState(() { if (value.isEmpty) { -- 2.49.1 From 8ff3c014358af8341caf4a0f17d1e95a09cc1e35 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 20 Nov 2025 16:59:49 +0100 Subject: [PATCH 231/563] added missing consts & mounted check --- lib/presentation/views/main_menu/create_group_view.dart | 5 +++-- lib/presentation/widgets/custom_search_bar.dart | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 45f5af8..3beb62a 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -112,6 +112,7 @@ class _CreateGroupViewState extends State { bool success = await db.playerDao.addPlayer( player: Player(name: playerName), ); + if (!context.mounted) return; if (success) { loadPlayerList(); ScaffoldMessenger.of(context).showSnackBar( @@ -120,7 +121,7 @@ class _CreateGroupViewState extends State { content: Center( child: Text( 'Successfully added player $playerName.', - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), ), ), @@ -132,7 +133,7 @@ class _CreateGroupViewState extends State { content: Center( child: Text( 'Could not add player $playerName.', - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), ), ); diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index 61a2154..8a483e5 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -42,7 +42,7 @@ class CustomSearchBar extends StatelessWidget { : Colors.grey.withValues(alpha: 0.2), ), ), - SizedBox(width: 5), + const SizedBox(width: 5), ] : null, backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), -- 2.49.1 From 01117743087c2d74d45d1f514c6682758d166e10 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 20 Nov 2025 21:26:59 +0100 Subject: [PATCH 232/563] Trim whitespace from group and player names in CreateGroupView --- lib/presentation/views/main_menu/create_group_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 3beb62a..057a16f 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -107,7 +107,7 @@ class _CreateGroupViewState extends State { trailingButtonEnabled: _searchBarController.text.isNotEmpty, onTrailingButtonPressed: () async { - String playerName = _searchBarController.text; + String playerName = _searchBarController.text.trim(); if (playerName.isEmpty) return; bool success = await db.playerDao.addPlayer( player: Player(name: playerName), @@ -314,7 +314,7 @@ class _CreateGroupViewState extends State { : () async { bool success = await db.groupDao.addGroup( group: Group( - name: _groupNameController.text, + name: _groupNameController.text.trim(), members: selectedPlayers, ), ); -- 2.49.1 From d16beed490dbb6c84a73c80d1da1a2d435e82e9a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 20 Nov 2025 21:54:43 +0100 Subject: [PATCH 233/563] felix mach jetzt --- .../views/main_menu/create_group_view.dart | 92 ++++++++++++------- .../widgets/custom_search_bar.dart | 31 ++++--- 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 057a16f..590f342 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -37,9 +37,23 @@ class _CreateGroupViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); + _searchBarController.addListener(() { + setState(() {}); + }); + _groupNameController.addListener(() { + setState(() {}); + }); loadPlayerList(); } + @override + void dispose() { + _groupNameController.dispose(); + _searchBarController + .dispose(); // Listener entfernen und Controller aufräumen + super.dispose(); + } + void loadPlayerList() { _allPlayersFuture = db.playerDao.getAllPlayers(); _allPlayersFuture.then((loadedPlayers) { @@ -104,40 +118,16 @@ class _CreateGroupViewState extends State { ), hintText: 'Search for players', trailingButtonShown: true, - trailingButtonEnabled: - _searchBarController.text.isNotEmpty, + trailingButtonEnabled: _searchBarController.text + .trim() + .isNotEmpty, onTrailingButtonPressed: () async { - String playerName = _searchBarController.text.trim(); - if (playerName.isEmpty) return; - bool success = await db.playerDao.addPlayer( - player: Player(name: playerName), + addNewPlayerFromSearch( + context, + _searchBarController, + db, + loadPlayerList, ); - if (!context.mounted) return; - if (success) { - loadPlayerList(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Successfully added player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); - _searchBarController.clear(); - } else { - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Could not add player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ); - } }, onChanged: (value) { setState(() { @@ -347,3 +337,41 @@ class _CreateGroupViewState extends State { ); } } + +void addNewPlayerFromSearch( + context, + searchBarController, + db, + loadPlayerList, +) async { + String playerName = searchBarController.text.trim(); + bool success = await db.playerDao.addPlayer(player: Player(name: playerName)); + if (!context.mounted) return; + if (success) { + loadPlayerList(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Successfully added player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + searchBarController.clear(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Could not add player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index 8a483e5..4b5fd24 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -28,23 +28,24 @@ class CustomSearchBar extends StatelessWidget { constraints: constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: hintText, - onChanged: onChanged, + onChanged: trailingButtonEnabled ? onChanged : null, hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), - trailing: trailingButtonShown - ? [ - GestureDetector( - onTap: onTrailingButtonPressed, - child: Icon( - Icons.add_circle, - color: trailingButtonEnabled - ? null - : Colors.grey.withValues(alpha: 0.2), - ), - ), - const SizedBox(width: 5), - ] - : null, + trailing: [ + Visibility( + visible: trailingButtonShown, + child: GestureDetector( + onTap: onTrailingButtonPressed, + child: Icon( + Icons.add_circle, + color: trailingButtonEnabled + ? null + : Colors.grey.withValues(alpha: 0.2), + ), + ), + ), + const SizedBox(width: 5), + ], backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), shape: WidgetStateProperty.all( -- 2.49.1 From b67f3212761db7080bd30b388eb3c10835efc891 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:05:44 +0100 Subject: [PATCH 234/563] Added name parameters and function doc --- .../views/main_menu/create_group_view.dart | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 590f342..5d53562 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -123,10 +123,10 @@ class _CreateGroupViewState extends State { .isNotEmpty, onTrailingButtonPressed: () async { addNewPlayerFromSearch( - context, - _searchBarController, - db, - loadPlayerList, + context: context, + searchBarController: _searchBarController, + db: db, + loadPlayerList: loadPlayerList, ); }, onChanged: (value) { @@ -338,12 +338,18 @@ class _CreateGroupViewState extends State { } } -void addNewPlayerFromSearch( - context, - searchBarController, - db, - loadPlayerList, -) async { +/// Adds a new player to the database from the search bar input. +/// Shows a snackbar indicating success or failure. +/// [context] - BuildContext to show the snackbar. +/// [searchBarController] - TextEditingController of the search bar. +/// [db] - AppDatabase instance to interact with the database. +/// [loadPlayerList] - Function to reload the player list after adding. +void addNewPlayerFromSearch({ + required BuildContext context, + required TextEditingController searchBarController, + required AppDatabase db, + required Function loadPlayerList, +}) async { String playerName = searchBarController.text.trim(); bool success = await db.playerDao.addPlayer(player: Player(name: playerName)); if (!context.mounted) return; -- 2.49.1 From 01fede29519be32a8ecc166e4e82796ceb110f8f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:09:08 +0100 Subject: [PATCH 235/563] Added Visibility Widget --- .../views/main_menu/create_group_view.dart | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 5d53562..de8b2d2 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -246,46 +246,48 @@ class _CreateGroupViewState extends State { layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, ), - child: - (suggestedPlayers.isEmpty && - allPlayers.isNotEmpty) - ? TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - (selectedPlayers.length == - allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ) - : ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: - (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index] - .name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => a.name - .compareTo(b.name), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); + child: Visibility( + visible: + (suggestedPlayers.isEmpty && + allPlayers.isNotEmpty), + replacement: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: + (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name.compareTo( + b.name, + ), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); }, - ), + ); + }, + ), + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + ), ), ); }, -- 2.49.1 From eb7b247cae739c0d76186b2048b20cf5e60ce4aa Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:11:23 +0100 Subject: [PATCH 236/563] Fixed error adding player with empty name --- lib/presentation/widgets/custom_search_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index 4b5fd24..f506605 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -35,7 +35,7 @@ class CustomSearchBar extends StatelessWidget { Visibility( visible: trailingButtonShown, child: GestureDetector( - onTap: onTrailingButtonPressed, + onTap: trailingButtonEnabled ? onTrailingButtonPressed : null, child: Icon( Icons.add_circle, color: trailingButtonEnabled -- 2.49.1 From 195ebf569ab24c7a2899295cb00d9fc21d1527b4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:17:20 +0100 Subject: [PATCH 237/563] Added icon as parameter for custom search bar --- lib/presentation/views/main_menu/create_group_view.dart | 1 + lib/presentation/widgets/custom_search_bar.dart | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index de8b2d2..c54369e 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -118,6 +118,7 @@ class _CreateGroupViewState extends State { ), hintText: 'Search for players', trailingButtonShown: true, + trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text .trim() .isNotEmpty, diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index f506605..e3fe976 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -6,15 +6,17 @@ class CustomSearchBar extends StatelessWidget { final String hintText; final ValueChanged? onChanged; final BoxConstraints? constraints; - final bool trailingButtonEnabled; final bool trailingButtonShown; + final bool trailingButtonEnabled; final VoidCallback? onTrailingButtonPressed; + final IconData trailingButtonicon; const CustomSearchBar({ super.key, required this.controller, required this.hintText, this.trailingButtonShown = false, + this.trailingButtonicon = Icons.clear, this.trailingButtonEnabled = true, this.onTrailingButtonPressed, this.onChanged, @@ -37,7 +39,7 @@ class CustomSearchBar extends StatelessWidget { child: GestureDetector( onTap: trailingButtonEnabled ? onTrailingButtonPressed : null, child: Icon( - Icons.add_circle, + trailingButtonicon, color: trailingButtonEnabled ? null : Colors.grey.withValues(alpha: 0.2), -- 2.49.1 From a61818dd77613c3ed6294086963a1de23e0dbf78 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 22:40:56 +0100 Subject: [PATCH 238/563] Fixed error in getAllGames method --- lib/data/dao/game_dao.dart | 22 +++++++++++++++++----- lib/data/dao/group_game_dao.dart | 9 +++++++-- lib/data/dao/player_game_dao.dart | 8 ++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 94d010c..20c90c7 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -15,11 +15,23 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { Future> getAllGames() async { final query = select(gameTable); final result = await query.get(); - return result - .map( - (row) => Game(id: row.id, name: row.name, createdAt: row.createdAt), - ) - .toList(); + + return Future.wait( + result.map((row) async { + final group = await db.groupGameDao.getGroupByGameId(gameId: row.id); + final player = await db.playerGameDao.getPlayersByGameId( + gameId: row.id, + ); + return Game( + id: row.id, + name: row.name, + group: group, + players: player, + createdAt: row.createdAt, + winner: row.winnerId, + ); + }), + ); } /// Retrieves a [Game] by its [gameId]. diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index d3b30ca..66ebdec 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -23,10 +23,15 @@ class GroupGameDao extends DatabaseAccessor } /// Retrieves the [Group] associated with the given [gameId]. - Future getGroupByGameId({required String gameId}) async { + /// Returns `null` if no group is found. + Future getGroupByGameId({required String gameId}) async { final result = await (select( groupGameTable, - )..where((g) => g.gameId.equals(gameId))).getSingle(); + )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); + + if (result == null) { + return null; + } final group = await db.groupDao.getGroupById(groupId: result.groupId); return group; diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 8f367f8..05c9e10 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -23,19 +23,19 @@ class PlayerGameDao extends DatabaseAccessor } /// Retrieves a list of [Player]s associated with the given [gameId]. - /// Returns an empty list if no players are found. - Future> getPlayersByGameId({required String gameId}) async { + /// Returns null if no players are found. + Future?> getPlayersByGameId({required String gameId}) async { final result = await (select( playerGameTable, )..where((p) => p.gameId.equals(gameId))).get(); - if (result.isEmpty) return []; + if (result.isEmpty) return null; final futures = result.map( (row) => db.playerDao.getPlayerById(playerId: row.playerId), ); final players = await Future.wait(futures); - return players.whereType().toList(); + return players; } /// Associates a player with a game by inserting a record into the -- 2.49.1 From 72067863c2c7d04f781159411e4b094726a08a72 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:14:44 +0100 Subject: [PATCH 239/563] Updated insert modes --- lib/data/dao/group_game_dao.dart | 2 +- lib/data/dao/player_game_dao.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 66ebdec..8a55b91 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -42,6 +42,6 @@ class GroupGameDao extends DatabaseAccessor Future addGroupToGame(String gameId, String groupId) async { await into( groupGameTable, - ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId)); + ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace); } } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 05c9e10..92937cb 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -46,6 +46,7 @@ class PlayerGameDao extends DatabaseAccessor }) async { await into(playerGameTable).insert( PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, ); } } -- 2.49.1 From 89b3f1ff69cc5e5e8fecad8e5536f602f1edb03a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:40:46 +0100 Subject: [PATCH 240/563] Overhauled tests --- test/db_tests/game_test.dart | 161 ++++++++++++++++++++++++++++++--- test/db_tests/group_test.dart | 64 +++++++++---- test/db_tests/player_test.dart | 81 ++++++++++++----- 3 files changed, 251 insertions(+), 55 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d726425..0b68648 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -15,7 +15,9 @@ void main() { late Player player4; late Player player5; late Group testgroup; + late Group testgroup2; late Game testgame; + late Game testgame2; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -38,19 +40,28 @@ void main() { name: 'Test Group', members: [player1, player2, player3], ); + testgroup2 = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); testgame = Game( name: 'Test Game', group: testgroup, players: [player4, player5], ); + testgame2 = Game( + name: 'Second Test Game', + group: testgroup2, + players: [player1, player2, player3], + ); }); }); tearDown(() async { await database.close(); }); - group('game tests', () { - test('game is added correctly', () async { + group('Game Tests', () { + test('Adding and fetching single game works correclty', () async { await database.gameDao.addGame(game: testgame); final result = await database.gameDao.getGameById(gameId: testgame.id); @@ -83,7 +94,116 @@ void main() { } }); - test('game is deleted correctly', () async { + // TODO: Use upcoming addGames() method + test('Adding and fetching multiple games works correclty', () async { + await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame2); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 2); + + final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame.id); + // game checks + expect(fetchedGame1.id, testgame.id); + expect(fetchedGame1.name, testgame.name); + expect(fetchedGame1.createdAt, testgame.createdAt); + expect(fetchedGame1.winner, testgame.winner); + + // group checks + expect(fetchedGame1.group!.id, testgame.group!.id); + expect(fetchedGame1.group!.name, testgame.group!.name); + expect(fetchedGame1.group!.createdAt, testgame.group!.createdAt); + // group members checks + expect( + fetchedGame1.group!.members.length, + testgame.group!.members.length, + ); + for (int i = 0; i < testgame.group!.members.length; i++) { + expect( + fetchedGame1.group!.members[i].id, + testgame.group!.members[i].id, + ); + expect( + fetchedGame1.group!.members[i].name, + testgame.group!.members[i].name, + ); + expect( + fetchedGame1.group!.members[i].createdAt, + testgame.group!.members[i].createdAt, + ); + } + + // players checks + for (int i = 0; i < fetchedGame1.players!.length; i++) { + expect(fetchedGame1.players![i].id, testgame.players![i].id); + expect(fetchedGame1.players![i].name, testgame.players![i].name); + expect( + fetchedGame1.players![i].createdAt, + testgame.players![i].createdAt, + ); + } + + final fetchedGame2 = allGames.firstWhere((g) => g.id == testgame2.id); + // game checks + expect(fetchedGame2.id, testgame2.id); + expect(fetchedGame2.name, testgame2.name); + expect(fetchedGame2.createdAt, testgame2.createdAt); + expect(fetchedGame2.winner, testgame2.winner); + + // group checks + expect(fetchedGame2.group!.id, testgame2.group!.id); + expect(fetchedGame2.group!.name, testgame2.group!.name); + expect(fetchedGame2.group!.createdAt, testgame2.group!.createdAt); + // group members checks + expect( + fetchedGame2.group!.members.length, + testgame2.group!.members.length, + ); + for (int i = 0; i < testgame2.group!.members.length; i++) { + expect( + fetchedGame2.group!.members[i].id, + testgame2.group!.members[i].id, + ); + expect( + fetchedGame2.group!.members[i].name, + testgame2.group!.members[i].name, + ); + expect( + fetchedGame2.group!.members[i].createdAt, + testgame2.group!.members[i].createdAt, + ); + } + + // players checks + for (int i = 0; i < fetchedGame2.players!.length; i++) { + expect(fetchedGame2.players![i].id, testgame2.players![i].id); + expect(fetchedGame2.players![i].name, testgame2.players![i].name); + expect( + fetchedGame2.players![i].createdAt, + testgame2.players![i].createdAt, + ); + } + }); + + test('Adding the same game twice does not create duplicates', () async { + await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame); + + final gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); + }); + + test('Game existence check works correctly', () async { + var gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, false); + + await database.gameDao.addGame(game: testgame); + + gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, true); + }); + + test('Deleting a game works correclty', () async { await database.gameDao.addGame(game: testgame); final gameDeleted = await database.gameDao.deleteGame( @@ -95,22 +215,35 @@ void main() { expect(gameExists, false); }); - test('get game count works correctly', () async { - final initialCount = await database.gameDao.getGameCount(); - expect(initialCount, 0); + test('Getting the game count works correctly', () async { + var gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 0); await database.gameDao.addGame(game: testgame); - final gameAdded = await database.gameDao.getGameCount(); - expect(gameAdded, 1); + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); - final gameRemoved = await database.gameDao.deleteGame( - gameId: testgame.id, - ); - expect(gameRemoved, true); + await database.gameDao.addGame(game: testgame2); - final finalCount = await database.gameDao.getGameCount(); - expect(finalCount, 0); + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 2); + + await database.gameDao.deleteGame(gameId: testgame.id); + + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 1); + + await database.gameDao.deleteGame(gameId: testgame2.id); + + gameCount = await database.gameDao.getGameCount(); + expect(gameCount, 0); }); + + // TODO: Implement + test('Adding a player to a game works correclty', () async {}); + + // TODO: Implement + test('Adding a group to a game works correclty', () async {}); }); } diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index a076ab0..2572f52 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -45,8 +45,31 @@ void main() { tearDown(() async { await database.close(); }); - group('group tests', () { - test('all groups get fetched correctly', () async { + group('Group Tests', () { + test('Adding and fetching a single group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + + expect(fetchedGroup.id, testgroup.id); + expect(fetchedGroup.name, testgroup.name); + expect(fetchedGroup.createdAt, testgroup.createdAt); + + expect(fetchedGroup.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(fetchedGroup.members[i].id, testgroup.members[i].id); + expect(fetchedGroup.members[i].name, testgroup.members[i].name); + expect( + fetchedGroup.members[i].createdAt, + testgroup.members[i].createdAt, + ); + } + }); + + // TODO: Use upcoming addGroups() method + test('Adding and fetching a single group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); @@ -66,26 +89,27 @@ void main() { expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); }); - test('group and group members gets added correctly', () async { + test('Adding the same group twice does not create duplicates', () async { + await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - - expect(result.id, testgroup.id); - expect(result.name, testgroup.name); - expect(result.createdAt, testgroup.createdAt); - - expect(result.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.members[i].id, testgroup.members[i].id); - expect(result.members[i].name, testgroup.members[i].name); - expect(result.members[i].createdAt, testgroup.members[i].createdAt); - } + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); }); - test('group gets deleted correctly', () async { + test('Group existence check works correctly', () async { + var groupExists = await database.groupDao.groupExists( + groupId: testgroup.id, + ); + expect(groupExists, false); + + await database.groupDao.addGroup(group: testgroup); + + groupExists = await database.groupDao.groupExists(groupId: testgroup.id); + expect(groupExists, true); + }); + + test('Deleting a group works correclty', () async { await database.groupDao.addGroup(group: testgroup); final groupDeleted = await database.groupDao.deleteGroup( @@ -99,7 +123,7 @@ void main() { expect(groupExists, false); }); - test('group name gets updated correcly ', () async { + test('Updating a group name works correcly', () async { await database.groupDao.addGroup(group: testgroup); const newGroupName = 'new group name'; @@ -167,7 +191,7 @@ void main() { expect(playerExists, false); }); - test('get group count works correctly', () async { + test('Getting the group count works correctly', () async { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index fa65f67..d894836 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -31,7 +31,7 @@ void main() { }); group('player tests', () { - test('all players get fetched correctly', () async { + test('Adding and fetching single player works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); @@ -51,18 +51,50 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('players get inserted correcly ', () async { + // TODO: Use upcoming addPlayers() method + test('Adding and fetching multiple players works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); - final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, - ); + await database.playerDao.addPlayer(player: testPlayer2); - expect(result.id, testPlayer.id); - expect(result.name, testPlayer.name); - expect(result.createdAt, testPlayer.createdAt); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer.id, + ); + expect(fetchedPlayer1.name, testPlayer.name); + expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('players get deleted correcly ', () async { + test('Adding the same player twice does not create duplicates', () async { + await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 1); + }); + + test('Player existence check works correctly', () async { + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, false); + + await database.playerDao.addPlayer(player: testPlayer); + + playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, true); + }); + + test('Deleting a player works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); final playerDeleted = await database.playerDao.deletePlayer( playerId: testPlayer.id, @@ -75,7 +107,7 @@ void main() { expect(playerExists, false); }); - test('player name gets updated correcly ', () async { + test('Updating a player name works correcly', () async { await database.playerDao.addPlayer(player: testPlayer); const newPlayerName = 'new player name'; @@ -91,22 +123,29 @@ void main() { expect(result.name, newPlayerName); }); - test('get player count works correctly', () async { - final initialCount = await database.playerDao.getPlayerCount(); - expect(initialCount, 0); + test('Getting the player count works correctly', () async { + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); await database.playerDao.addPlayer(player: testPlayer); - final playerAdded = await database.playerDao.getPlayerCount(); - expect(playerAdded, 1); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); - final playerRemoved = await database.playerDao.deletePlayer( - playerId: testPlayer.id, - ); - expect(playerRemoved, true); + await database.playerDao.addPlayer(player: testPlayer2); - final finalCount = await database.playerDao.getPlayerCount(); - expect(finalCount, 0); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 2); + + await database.playerDao.deletePlayer(playerId: testPlayer.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.deletePlayer(playerId: testPlayer2.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); }); }); } -- 2.49.1 From 8e63a01705868772df469f43ed2afc6f788bb482 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 20 Nov 2025 23:49:57 +0100 Subject: [PATCH 241/563] Added methods for multiple inserts to tests --- test/db_tests/game_test.dart | 4 +--- test/db_tests/group_test.dart | 4 +--- test/db_tests/player_test.dart | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0b68648..477965f 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -94,10 +94,8 @@ void main() { } }); - // TODO: Use upcoming addGames() method test('Adding and fetching multiple games works correclty', () async { - await database.gameDao.addGame(game: testgame); - await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGames(games: [testgame, testgame2]); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 2); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2572f52..62416fd 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -68,10 +68,8 @@ void main() { } }); - // TODO: Use upcoming addGroups() method test('Adding and fetching a single group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); + await database.groupDao.addGroups(groups: [testgroup, testgroup2]); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index d894836..bc95533 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -51,10 +51,8 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - // TODO: Use upcoming addPlayers() method test('Adding and fetching multiple players works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer2); + await database.playerDao.addPlayers(players: [testPlayer, testPlayer2]); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 2); -- 2.49.1 From 31589855f28e6eb8c6f2d402f389df8f9bfcf28b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:06:09 +0100 Subject: [PATCH 242/563] Added methods of todos --- lib/data/dao/group_game_dao.dart | 57 +++++++++++++++++++-------- lib/data/dao/player_game_dao.dart | 64 +++++++++++++++++++++++-------- test/db_tests/game_test.dart | 59 ++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 37 deletions(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 8a55b91..633bb1c 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -10,16 +10,13 @@ class GroupGameDao extends DatabaseAccessor with _$GroupGameDaoMixin { GroupGameDao(super.db); - /// Checks if there is a group associated with the given [gameId]. - /// Returns `true` if there is a group, otherwise `false`. - Future hasGameGroup({required String gameId}) async { - final count = - await (selectOnly(groupGameTable) - ..where(groupGameTable.gameId.equals(gameId)) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; + /// Associates a group with a game by inserting a record into the + /// [GroupGameTable]. + Future addGroupToGame(String gameId, String groupId) async { + await into(groupGameTable).insert( + GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), + mode: InsertMode.insertOrReplace, + ); } /// Retrieves the [Group] associated with the given [gameId]. @@ -37,11 +34,39 @@ class GroupGameDao extends DatabaseAccessor return group; } - /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. - Future addGroupToGame(String gameId, String groupId) async { - await into( - groupGameTable, - ).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace); + /// Checks if there is a group associated with the given [gameId]. + /// Returns `true` if there is a group, otherwise `false`. + Future gameHasGroup({required String gameId}) async { + final count = + await (selectOnly(groupGameTable) + ..where(groupGameTable.gameId.equals(gameId)) + ..addColumns([groupGameTable.groupId.count()])) + .map((row) => row.read(groupGameTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific group is associated with a specific game. + /// Returns `true` if the group is in the game, otherwise `false`. + Future isGroupInGame({ + required String gameId, + required String groupId, + }) async { + final count = + await (selectOnly(groupGameTable) + ..where( + groupGameTable.gameId.equals(gameId) & + groupGameTable.groupId.equals(groupId), + ) + ..addColumns([groupGameTable.groupId.count()])) + .map((row) => row.read(groupGameTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + Future removeGroupFromGame({required String gameId}) async { + final query = delete(groupGameTable)..where((g) => g.gameId.equals(gameId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } } diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 92937cb..87fd1d0 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -10,16 +10,16 @@ class PlayerGameDao extends DatabaseAccessor with _$PlayerGameDaoMixin { PlayerGameDao(super.db); - /// Checks if there are any players associated with the given [gameId]. - /// Returns `true` if there are players, otherwise `false`. - Future gameHasPlayers({required String gameId}) async { - final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) - .getSingle(); - return (count ?? 0) > 0; + /// Associates a player with a game by inserting a record into the + /// [PlayerGameTable]. + Future addPlayerToGame({ + required String gameId, + required String playerId, + }) async { + await into(playerGameTable).insert( + PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + mode: InsertMode.insertOrReplace, + ); } /// Retrieves a list of [Player]s associated with the given [gameId]. @@ -38,15 +38,45 @@ class PlayerGameDao extends DatabaseAccessor return players; } - /// Associates a player with a game by inserting a record into the - /// [PlayerGameTable]. - Future addPlayerToGame({ + /// Checks if there are any players associated with the given [gameId]. + /// Returns `true` if there are players, otherwise `false`. + Future gameHasPlayers({required String gameId}) async { + final count = + await (selectOnly(playerGameTable) + ..where(playerGameTable.gameId.equals(gameId)) + ..addColumns([playerGameTable.playerId.count()])) + .map((row) => row.read(playerGameTable.playerId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific player is associated with a specific game. + /// Returns `true` if the player is in the game, otherwise `false`. + Future isPlayerInGame({ required String gameId, required String playerId, }) async { - await into(playerGameTable).insert( - PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), - mode: InsertMode.insertOrReplace, - ); + final count = + await (selectOnly(playerGameTable) + ..where(playerGameTable.gameId.equals(gameId)) + ..where(playerGameTable.playerId.equals(playerId)) + ..addColumns([playerGameTable.playerId.count()])) + .map((row) => row.read(playerGameTable.playerId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Removes the association of a player with a game by deleting the record + /// from the [PlayerGameTable]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removePlayerFromGame({ + required String gameId, + required String playerId, + }) async { + final query = delete(playerGameTable) + ..where((pg) => pg.gameId.equals(gameId)) + ..where((pg) => pg.playerId.equals(playerId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0b68648..05cf9d0 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -14,10 +14,12 @@ void main() { late Player player3; late Player player4; late Player player5; + late Player player6; late Group testgroup; late Group testgroup2; late Game testgame; late Game testgame2; + late Game testgame3; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -36,6 +38,7 @@ void main() { player3 = Player(name: 'Charlie'); player4 = Player(name: 'Diana'); player5 = Player(name: 'Eve'); + player6 = Player(name: 'Frank'); testgroup = Group( name: 'Test Group', members: [player1, player2, player3], @@ -54,6 +57,7 @@ void main() { group: testgroup2, players: [player1, player2, player3], ); + testgame3 = Game(name: 'Third Test Game', players: [player4, player5]); }); }); tearDown(() async { @@ -240,10 +244,57 @@ void main() { expect(gameCount, 0); }); - // TODO: Implement - test('Adding a player to a game works correclty', () async {}); + test( + 'Adding and removing player to and from a game works correclty', + () async { + database.gameDao.addGame(game: testgame); + database.playerDao.addPlayer(player: player6); + database.playerGameDao.addPlayerToGame( + gameId: testgame.id, + playerId: player6.id, + ); - // TODO: Implement - test('Adding a group to a game works correclty', () async {}); + var playerInGame = await database.playerGameDao.isPlayerInGame( + gameId: testgame.id, + playerId: player6.id, + ); + + expect(playerInGame, true); + + final playerRemoved = await database.playerGameDao.removePlayerFromGame( + gameId: testgame.id, + playerId: player6.id, + ); + + expect(playerRemoved, true); + + playerInGame = await database.playerGameDao.isPlayerInGame( + gameId: testgame.id, + playerId: player6.id, + ); + expect(playerInGame, false); + }, + ); + + test( + 'Adding and removing a group to and from a game works correclty', + () async { + database.gameDao.addGame(game: testgame3); + database.groupDao.addGroup(group: testgroup); + database.groupGameDao.addGroupToGame(testgame3.id, testgroup.id); + var gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: testgame3.id, + ); + expect(gameHasGroup, true); + final groupRemoved = await database.groupGameDao.removeGroupFromGame( + gameId: testgame3.id, + ); + expect(groupRemoved, true); + gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: testgame3.id, + ); + expect(gameHasGroup, false); + }, + ); }); } -- 2.49.1 From 6055eb63a85ffb43143a63f4e9083aff1a718c1d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:07:34 +0100 Subject: [PATCH 243/563] Refactoring --- lib/data/dao/game_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 20c90c7..2024eca 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -44,7 +44,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); } Group? group; - if (await db.groupGameDao.hasGameGroup(gameId: gameId)) { + if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { group = await db.groupGameDao.getGroupByGameId(gameId: gameId); } -- 2.49.1 From b21ca5467232c9e35b341ea5c374353c8ee77210 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 00:10:29 +0100 Subject: [PATCH 244/563] Refactoring --- lib/data/dao/game_dao.dart | 10 +++---- lib/data/dao/group_dao.dart | 4 +-- lib/data/dao/group_game_dao.dart | 2 +- lib/data/dao/player_game_dao.dart | 2 +- lib/data/dao/player_group_dao.dart | 48 +++++++++++++++--------------- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 2024eca..f29d553 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -18,10 +18,8 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return Future.wait( result.map((row) async { - final group = await db.groupGameDao.getGroupByGameId(gameId: row.id); - final player = await db.playerGameDao.getPlayersByGameId( - gameId: row.id, - ); + final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); + final player = await db.playerGameDao.getPlayersOfGame(gameId: row.id); return Game( id: row.id, name: row.name, @@ -41,11 +39,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { List? players; if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) { - players = await db.playerGameDao.getPlayersByGameId(gameId: gameId); + players = await db.playerGameDao.getPlayersOfGame(gameId: gameId); } Group? group; if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { - group = await db.groupGameDao.getGroupByGameId(gameId: gameId); + group = await db.groupGameDao.getGroupOfGame(gameId: gameId); } return Game( diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 3378948..9b16801 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -16,7 +16,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final result = await query.get(); return Future.wait( result.map((groupData) async { - final members = await db.playerGroupDao.getPlayersOfGroupById( + final members = await db.playerGroupDao.getPlayersOfGroup( groupId: groupData.id, ); return Group( @@ -34,7 +34,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final query = select(groupTable)..where((g) => g.id.equals(groupId)); final result = await query.getSingle(); - List members = await db.playerGroupDao.getPlayersOfGroupById( + List members = await db.playerGroupDao.getPlayersOfGroup( groupId: groupId, ); diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 633bb1c..8081c6f 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -21,7 +21,7 @@ class GroupGameDao extends DatabaseAccessor /// Retrieves the [Group] associated with the given [gameId]. /// Returns `null` if no group is found. - Future getGroupByGameId({required String gameId}) async { + Future getGroupOfGame({required String gameId}) async { final result = await (select( groupGameTable, )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index 87fd1d0..ef15a80 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -24,7 +24,7 @@ class PlayerGameDao extends DatabaseAccessor /// Retrieves a list of [Player]s associated with the given [gameId]. /// Returns null if no players are found. - Future?> getPlayersByGameId({required String gameId}) async { + Future?> getPlayersOfGame({required String gameId}) async { final result = await (select( playerGameTable, )..where((p) => p.gameId.equals(gameId))).get(); diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index fe067ae..5484bf7 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -10,8 +10,31 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); + /// Adds a [player] to a group with the given [groupId]. + /// If the player is already in the group, no action is taken. + /// If the player does not exist in the player table, they are added. + /// Returns `true` if the player was added, otherwise `false`. + Future addPlayerToGroup({ + required Player player, + required String groupId, + }) async { + if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { + return false; + } + + if (await db.playerDao.playerExists(playerId: player.id) == false) { + db.playerDao.addPlayer(player: player); + } + + await into(playerGroupTable).insert( + PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + ); + + return true; + } + /// Retrieves all players belonging to a specific group by [groupId]. - Future> getPlayersOfGroupById({required String groupId}) async { + Future> getPlayersOfGroup({required String groupId}) async { final query = select(playerGroupTable) ..where((pG) => pG.groupId.equals(groupId)); final result = await query.get(); @@ -38,29 +61,6 @@ class PlayerGroupDao extends DatabaseAccessor return rowsAffected > 0; } - /// Adds a [player] to a group with the given [groupId]. - /// If the player is already in the group, no action is taken. - /// If the player does not exist in the player table, they are added. - /// Returns `true` if the player was added, otherwise `false`. - Future addPlayerToGroup({ - required Player player, - required String groupId, - }) async { - if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { - return false; - } - - if (await db.playerDao.playerExists(playerId: player.id) == false) { - db.playerDao.addPlayer(player: player); - } - - await into(playerGroupTable).insert( - PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), - ); - - return true; - } - /// Checks if a player with [playerId] is in the group with [groupId]. /// Returns `true` if the player is in the group, otherwise `false`. Future isPlayerInGroup({ -- 2.49.1 From 961c6bb679b77492dcb60cb7a61e83c132fba855 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 01:05:35 +0100 Subject: [PATCH 245/563] Refactored tests in own files --- lib/data/dao/group_game_dao.dart | 11 +- test/db_tests/game_test.dart | 158 ++++++++++----------------- test/db_tests/group_game_test.dart | 110 +++++++++++++++++++ test/db_tests/group_test.dart | 55 +--------- test/db_tests/player_game_test.dart | 126 +++++++++++++++++++++ test/db_tests/player_group_test.dart | 90 +++++++++++++++ test/db_tests/player_test.dart | 3 +- 7 files changed, 394 insertions(+), 159 deletions(-) create mode 100644 test/db_tests/group_game_test.dart create mode 100644 test/db_tests/player_game_test.dart create mode 100644 test/db_tests/player_group_test.dart diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 8081c6f..f3ddcc7 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -64,8 +64,15 @@ class GroupGameDao extends DatabaseAccessor return (count ?? 0) > 0; } - Future removeGroupFromGame({required String gameId}) async { - final query = delete(groupGameTable)..where((g) => g.gameId.equals(gameId)); + /// Removes the association of a group from a game based on [groupId] and + /// [gameId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeGroupFromGame({ + required String gameId, + required String groupId, + }) async { + final query = delete(groupGameTable) + ..where((g) => g.gameId.equals(gameId) & g.groupId.equals(groupId)); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 05cf9d0..d5b0856 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -14,12 +14,12 @@ void main() { late Player player3; late Player player4; late Player player5; - late Player player6; late Group testgroup; late Group testgroup2; - late Game testgame; + late Game testgame1; late Game testgame2; - late Game testgame3; + late Game testgameWithPlayer; + late Game testgameWithGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -38,16 +38,12 @@ void main() { player3 = Player(name: 'Charlie'); player4 = Player(name: 'Diana'); player5 = Player(name: 'Eve'); - player6 = Player(name: 'Frank'); testgroup = Group( - name: 'Test Group', + name: 'Test Group 2', members: [player1, player2, player3], ); - testgroup2 = Group( - name: 'Test Group', - members: [player1, player2, player3], - ); - testgame = Game( + testgroup2 = Group(name: 'Test Group 2', members: [player4, player5]); + testgame1 = Game( name: 'Test Game', group: testgroup, players: [player4, player5], @@ -57,7 +53,11 @@ void main() { group: testgroup2, players: [player1, player2, player3], ); - testgame3 = Game(name: 'Third Test Game', players: [player4, player5]); + testgameWithPlayer = Game( + name: 'Second Test Game', + players: [player1, player2, player3], + ); + testgameWithGroup = Game(name: 'Second Test Game', group: testgroup2); }); }); tearDown(() async { @@ -66,14 +66,14 @@ void main() { group('Game Tests', () { test('Adding and fetching single game works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); - final result = await database.gameDao.getGameById(gameId: testgame.id); + final result = await database.gameDao.getGameById(gameId: testgame1.id); - expect(result.id, testgame.id); - expect(result.name, testgame.name); - expect(result.winner, testgame.winner); - expect(result.createdAt, testgame.createdAt); + expect(result.id, testgame1.id); + expect(result.name, testgame1.name); + expect(result.winner, testgame1.winner); + expect(result.createdAt, testgame1.createdAt); if (result.group != null) { expect(result.group!.members.length, testgroup.members.length); @@ -86,12 +86,12 @@ void main() { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testgame.players!.length); + expect(result.players!.length, testgame1.players!.length); - for (int i = 0; i < testgame.players!.length; i++) { - expect(result.players![i].id, testgame.players![i].id); - expect(result.players![i].name, testgame.players![i].name); - expect(result.players![i].createdAt, testgame.players![i].createdAt); + for (int i = 0; i < testgame1.players!.length; i++) { + expect(result.players![i].id, testgame1.players![i].id); + expect(result.players![i].name, testgame1.players![i].name); + expect(result.players![i].createdAt, testgame1.players![i].createdAt); } } else { fail('Players is null'); @@ -99,51 +99,54 @@ void main() { }); // TODO: Use upcoming addGames() method + // TODO: Iterate through games test('Adding and fetching multiple games works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGame(game: testgameWithGroup); + await database.gameDao.addGame(game: testgameWithPlayer); final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 2); + expect(allGames.length, 4); - final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame.id); + final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame1.id); // game checks - expect(fetchedGame1.id, testgame.id); - expect(fetchedGame1.name, testgame.name); - expect(fetchedGame1.createdAt, testgame.createdAt); - expect(fetchedGame1.winner, testgame.winner); + expect(fetchedGame1.id, testgame1.id); + expect(fetchedGame1.name, testgame1.name); + expect(fetchedGame1.createdAt, testgame1.createdAt); + expect(fetchedGame1.winner, testgame1.winner); // group checks - expect(fetchedGame1.group!.id, testgame.group!.id); - expect(fetchedGame1.group!.name, testgame.group!.name); - expect(fetchedGame1.group!.createdAt, testgame.group!.createdAt); + expect(fetchedGame1.group!.id, testgame1.group!.id); + expect(fetchedGame1.group!.name, testgame1.group!.name); + expect(fetchedGame1.group!.createdAt, testgame1.group!.createdAt); // group members checks expect( fetchedGame1.group!.members.length, - testgame.group!.members.length, + testgame1.group!.members.length, ); - for (int i = 0; i < testgame.group!.members.length; i++) { + for (int i = 0; i < testgame1.group!.members.length; i++) { expect( fetchedGame1.group!.members[i].id, - testgame.group!.members[i].id, + testgame1.group!.members[i].id, ); expect( fetchedGame1.group!.members[i].name, - testgame.group!.members[i].name, + testgame1.group!.members[i].name, ); expect( fetchedGame1.group!.members[i].createdAt, - testgame.group!.members[i].createdAt, + testgame1.group!.members[i].createdAt, ); } // players checks for (int i = 0; i < fetchedGame1.players!.length; i++) { - expect(fetchedGame1.players![i].id, testgame.players![i].id); - expect(fetchedGame1.players![i].name, testgame.players![i].name); + expect(fetchedGame1.players![i].id, testgame1.players![i].id); + expect(fetchedGame1.players![i].name, testgame1.players![i].name); expect( fetchedGame1.players![i].createdAt, - testgame.players![i].createdAt, + testgame1.players![i].createdAt, ); } @@ -190,32 +193,34 @@ void main() { }); test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testgame); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testgame1); final gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); }); test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testgame.id); + var gameExists = await database.gameDao.gameExists(gameId: testgame1.id); expect(gameExists, false); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); - gameExists = await database.gameDao.gameExists(gameId: testgame.id); + gameExists = await database.gameDao.gameExists(gameId: testgame1.id); expect(gameExists, true); }); test('Deleting a game works correclty', () async { - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); final gameDeleted = await database.gameDao.deleteGame( - gameId: testgame.id, + gameId: testgame1.id, ); expect(gameDeleted, true); - final gameExists = await database.gameDao.gameExists(gameId: testgame.id); + final gameExists = await database.gameDao.gameExists( + gameId: testgame1.id, + ); expect(gameExists, false); }); @@ -223,7 +228,7 @@ void main() { var gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); - await database.gameDao.addGame(game: testgame); + await database.gameDao.addGame(game: testgame1); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); @@ -233,7 +238,7 @@ void main() { gameCount = await database.gameDao.getGameCount(); expect(gameCount, 2); - await database.gameDao.deleteGame(gameId: testgame.id); + await database.gameDao.deleteGame(gameId: testgame1.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); @@ -243,58 +248,5 @@ void main() { gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); }); - - test( - 'Adding and removing player to and from a game works correclty', - () async { - database.gameDao.addGame(game: testgame); - database.playerDao.addPlayer(player: player6); - database.playerGameDao.addPlayerToGame( - gameId: testgame.id, - playerId: player6.id, - ); - - var playerInGame = await database.playerGameDao.isPlayerInGame( - gameId: testgame.id, - playerId: player6.id, - ); - - expect(playerInGame, true); - - final playerRemoved = await database.playerGameDao.removePlayerFromGame( - gameId: testgame.id, - playerId: player6.id, - ); - - expect(playerRemoved, true); - - playerInGame = await database.playerGameDao.isPlayerInGame( - gameId: testgame.id, - playerId: player6.id, - ); - expect(playerInGame, false); - }, - ); - - test( - 'Adding and removing a group to and from a game works correclty', - () async { - database.gameDao.addGame(game: testgame3); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame(testgame3.id, testgroup.id); - var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgame3.id, - ); - expect(gameHasGroup, true); - final groupRemoved = await database.groupGameDao.removeGroupFromGame( - gameId: testgame3.id, - ); - expect(groupRemoved, true); - gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgame3.id, - ); - expect(gameHasGroup, false); - }, - ); }); } diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart new file mode 100644 index 0000000..2e208d9 --- /dev/null +++ b/test/db_tests/group_game_test.dart @@ -0,0 +1,110 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Player player5; + late Group testgroup; + late Game gameWithGroup; + late Game gameWithPlayers; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + gameWithPlayers = Game( + name: 'Game with Players', + players: [player4, player5], + ); + gameWithGroup = Game(name: 'Game with Group', group: testgroup); + }); + }); + tearDown(() async { + await database.close(); + }); + group('Group-Game Tests', () { + test('Game has group works correctly', () async { + database.gameDao.addGame(game: gameWithPlayers); + database.groupDao.addGroup(group: testgroup); + + var gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: gameWithPlayers.id, + ); + + expect(gameHasGroup, false); + + database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + + gameHasGroup = await database.groupGameDao.gameHasGroup( + gameId: gameWithPlayers.id, + ); + + expect(gameHasGroup, true); + }); + + test('Adding a group to a game works correctly', () async { + database.gameDao.addGame(game: gameWithPlayers); + database.groupDao.addGroup(group: testgroup); + database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + + var groupAdded = await database.groupGameDao.isGroupInGame( + gameId: gameWithPlayers.id, + groupId: testgroup.id, + ); + expect(groupAdded, true); + + groupAdded = await database.groupGameDao.isGroupInGame( + gameId: gameWithPlayers.id, + groupId: '', + ); + expect(groupAdded, false); + }); + + test('Removing group from game works correctly', () async { + await database.gameDao.addGame(game: gameWithGroup); + + final groupToRemove = gameWithGroup.group!; + + final removed = await database.groupGameDao.removeGroupFromGame( + groupId: groupToRemove.id, + gameId: gameWithGroup.id, + ); + expect(removed, true); + + final result = await database.gameDao.getGameById( + gameId: gameWithGroup.id, + ); + expect(result.group, null); + }); + + // TODO: test getGroupOfGame() + test('Retrieving group of a game works correctly', () async {}); + }); +} diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2572f52..b18942e 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -68,7 +68,6 @@ void main() { } }); - // TODO: Use upcoming addGroups() method test('Adding and fetching a single group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); @@ -89,6 +88,8 @@ void main() { expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); }); + // TODO: Use upcoming addGroups() method + // TODO: An Test in Game Tests orientieren test('Adding the same group twice does not create duplicates', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); @@ -139,58 +140,6 @@ void main() { expect(result.name, newGroupName); }); - test('Adding player to group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - - await database.playerGroupDao.addPlayerToGroup( - player: player4, - groupId: testgroup.id, - ); - - final playerAdded = await database.playerGroupDao.isPlayerInGroup( - playerId: player4.id, - groupId: testgroup.id, - ); - - expect(playerAdded, true); - - final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup( - playerId: '', - groupId: testgroup.id, - ); - - expect(playerNotAdded, true); - - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length + 1); - - final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); - expect(addedPlayer.name, player4.name); - expect(addedPlayer.createdAt, player4.createdAt); - }); - - test('Removing player from group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - - final playerToRemove = testgroup.members[0]; - - final removed = await database.playerGroupDao.removePlayerFromGroup( - playerId: playerToRemove.id, - groupId: testgroup.id, - ); - expect(removed, true); - - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length - 1); - - final playerExists = result.members.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); - test('Getting the group count works correctly', () async { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart new file mode 100644 index 0000000..7df18a1 --- /dev/null +++ b/test/db_tests/player_game_test.dart @@ -0,0 +1,126 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Player player5; + late Player player6; + late Group testgroup; + late Game testgameWithGroup; + late Game testgameWithPlayers; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + player5 = Player(name: 'Eve'); + player6 = Player(name: 'Frank'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + testgameWithGroup = Game(name: 'Test Game', group: testgroup); + testgameWithPlayers = Game( + name: 'Test Game with Players', + players: [player4, player5, player6], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Game Tests', () { + test('Game has player works correctly', () async { + database.gameDao.addGame(game: testgameWithGroup); + database.playerDao.addPlayer(player: player1); + + var gameHasPlayers = await database.playerGameDao.gameHasPlayers( + gameId: testgameWithGroup.id, + ); + + expect(gameHasPlayers, false); + + database.playerGameDao.addPlayerToGame( + gameId: testgameWithGroup.id, + playerId: player1.id, + ); + + gameHasPlayers = await database.playerGameDao.gameHasPlayers( + gameId: testgameWithGroup.id, + ); + + expect(gameHasPlayers, true); + }); + + test('Adding a player to a game works correctly', () async { + database.gameDao.addGame(game: testgameWithGroup); + database.playerDao.addPlayer(player: player5); + database.playerGameDao.addPlayerToGame( + gameId: testgameWithGroup.id, + playerId: player5.id, + ); + + var playerAdded = await database.playerGameDao.isPlayerInGame( + gameId: testgameWithGroup.id, + playerId: player5.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerGameDao.isPlayerInGame( + gameId: testgameWithGroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + test('Removing player from game works correctly', () async { + await database.gameDao.addGame(game: testgameWithPlayers); + + final playerToRemove = testgameWithPlayers.players![0]; + + final removed = await database.playerGameDao.removePlayerFromGame( + playerId: playerToRemove.id, + gameId: testgameWithPlayers.id, + ); + expect(removed, true); + + final result = await database.gameDao.getGameById( + gameId: testgameWithGroup.id, + ); + expect(result.players!.length, testgameWithGroup.players!.length - 1); + + final playerExists = result.players!.any( + (p) => p.id == playerToRemove.id, + ); + expect(playerExists, false); + }); + + //TODO: test getPlayersOfGame() + test('Retrieving players of a game works correctly', () async {}); + }); +} diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart new file mode 100644 index 0000000..74d7658 --- /dev/null +++ b/test/db_tests/player_group_test.dart @@ -0,0 +1,90 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + player1 = Player(name: 'Alice'); + player2 = Player(name: 'Bob'); + player3 = Player(name: 'Charlie'); + player4 = Player(name: 'Diana'); + testgroup = Group( + name: 'Test Group', + members: [player1, player2, player3], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Group Tests', () { + test('Adding a player to a group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + await database.playerDao.addPlayer(player: player4); + await database.playerGroupDao.addPlayerToGroup( + groupId: testgroup.id, + player: player4, + ); + + var playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testgroup.id, + playerId: player4.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testgroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final playerToRemove = testgroup.members[0]; + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testgroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById( + groupId: testgroup.id, + ); + expect(result.members.length, testgroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); + + //TODO: test getPlayersOfGroup() + test('Retrieving players of a group works correctly', () async {}); + }); +} diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index d894836..9430433 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -23,7 +23,7 @@ void main() { withClock(fakeClock, () { testPlayer = Player(name: 'Test Player'); - testPlayer2 = Player(name: 'Second Group'); + testPlayer2 = Player(name: 'Second Player'); }); }); tearDown(() async { @@ -52,6 +52,7 @@ void main() { }); // TODO: Use upcoming addPlayers() method + // TODO: An Tests in Game orientieren test('Adding and fetching multiple players works correclty', () async { await database.playerDao.addPlayer(player: testPlayer); await database.playerDao.addPlayer(player: testPlayer2); -- 2.49.1 From fe9239ee02138fdf0751c069a928b1b41e61700d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:45:48 +0100 Subject: [PATCH 246/563] Added missing test --- test/db_tests/group_game_test.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 2e208d9..6621d9a 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -104,7 +104,25 @@ void main() { expect(result.group, null); }); - // TODO: test getGroupOfGame() - test('Retrieving group of a game works correctly', () async {}); + test('Retrieving group of a game works correctly', () async { + await database.gameDao.addGame(game: gameWithGroup); + final group = await database.groupGameDao.getGroupOfGame( + gameId: gameWithGroup.id, + ); + + if (group == null) { + fail('Group should not be null'); + } + + expect(group.id, testgroup.id); + expect(group.name, testgroup.name); + expect(group.createdAt, testgroup.createdAt); + expect(group.members.length, testgroup.members.length); + for (int i = 0; i < group.members.length; i++) { + expect(group.members[i].id, testgroup.members[i].id); + expect(group.members[i].name, testgroup.members[i].name); + expect(group.members[i].createdAt, testgroup.members[i].createdAt); + } + }); }); } -- 2.49.1 From 229750ffcffda1c68ff2974b48c06fe138c30517 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:46:04 +0100 Subject: [PATCH 247/563] Fixed test --- test/db_tests/player_game_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 7df18a1..d6f282e 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -110,9 +110,9 @@ void main() { expect(removed, true); final result = await database.gameDao.getGameById( - gameId: testgameWithGroup.id, + gameId: testgameWithPlayers.id, ); - expect(result.players!.length, testgameWithGroup.players!.length - 1); + expect(result.players!.length, testgameWithPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, -- 2.49.1 From 32f3f68da9083c2f60a9f664146d4b180de007bc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 12:46:18 +0100 Subject: [PATCH 248/563] Annotation for missing test & method --- lib/data/dao/player_group_dao.dart | 3 +++ test/db_tests/player_group_test.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 5484bf7..4024629 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -10,6 +10,9 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); + /// No need for a groupHasPlayers method since the members attribute is + /// not nullable + /// Adds a [player] to a group with the given [groupId]. /// If the player is already in the group, no action is taken. /// If the player does not exist in the player table, they are added. diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 74d7658..1181eda 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -41,6 +41,9 @@ void main() { }); group('Player-Group Tests', () { + /// No need to test if group has players since the members attribute is + /// not nullable + test('Adding a player to a group works correctly', () async { await database.groupDao.addGroup(group: testgroup); await database.playerDao.addPlayer(player: player4); -- 2.49.1 From e15f5d163db0b40c3bf1c5198e71d147e6f99035 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:12:36 +0100 Subject: [PATCH 249/563] Added missing methods --- test/db_tests/group_game_test.dart | 42 ++++++++++++++++------------ test/db_tests/player_game_test.dart | 18 ++++++++++-- test/db_tests/player_group_test.dart | 14 ++++++++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 6621d9a..e231284 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -15,8 +15,8 @@ void main() { late Player player4; late Player player5; late Group testgroup; - late Game gameWithGroup; - late Game gameWithPlayers; + late Game testgameWithGroup; + late Game testgameWithPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -39,11 +39,11 @@ void main() { name: 'Test Group', members: [player1, player2, player3], ); - gameWithPlayers = Game( + testgameWithPlayers = Game( name: 'Game with Players', players: [player4, player5], ); - gameWithGroup = Game(name: 'Game with Group', group: testgroup); + testgameWithGroup = Game(name: 'Game with Group', group: testgroup); }); }); tearDown(() async { @@ -51,63 +51,69 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: gameWithPlayers); + database.gameDao.addGame(game: testgameWithPlayers); database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, ); expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + database.groupGameDao.addGroupToGame( + testgameWithPlayers.id, + testgroup.id, + ); gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, ); expect(gameHasGroup, true); }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: gameWithPlayers); + database.gameDao.addGame(game: testgameWithPlayers); database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame(gameWithPlayers.id, testgroup.id); + database.groupGameDao.addGroupToGame( + testgameWithPlayers.id, + testgroup.id, + ); var groupAdded = await database.groupGameDao.isGroupInGame( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, groupId: testgroup.id, ); expect(groupAdded, true); groupAdded = await database.groupGameDao.isGroupInGame( - gameId: gameWithPlayers.id, + gameId: testgameWithPlayers.id, groupId: '', ); expect(groupAdded, false); }); test('Removing group from game works correctly', () async { - await database.gameDao.addGame(game: gameWithGroup); + await database.gameDao.addGame(game: testgameWithGroup); - final groupToRemove = gameWithGroup.group!; + final groupToRemove = testgameWithGroup.group!; final removed = await database.groupGameDao.removeGroupFromGame( groupId: groupToRemove.id, - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); expect(removed, true); final result = await database.gameDao.getGameById( - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); expect(result.group, null); }); test('Retrieving group of a game works correctly', () async { - await database.gameDao.addGame(game: gameWithGroup); + await database.gameDao.addGame(game: testgameWithGroup); final group = await database.groupGameDao.getGroupOfGame( - gameId: gameWithGroup.id, + gameId: testgameWithGroup.id, ); if (group == null) { diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index d6f282e..1fd0128 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -120,7 +120,21 @@ void main() { expect(playerExists, false); }); - //TODO: test getPlayersOfGame() - test('Retrieving players of a game works correctly', () async {}); + test('Retrieving players of a game works correctly', () async { + await database.gameDao.addGame(game: testgameWithPlayers); + final players = await database.playerGameDao.getPlayersOfGame( + gameId: testgameWithPlayers.id, + ); + + if (players == null) { + fail('Players should not be null'); + } + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testgameWithPlayers.players![i].id); + expect(players[i].name, testgameWithPlayers.players![i].name); + expect(players[i].createdAt, testgameWithPlayers.players![i].createdAt); + } + }); }); } diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 1181eda..9e367e0 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -87,7 +87,17 @@ void main() { expect(playerExists, false); }); - //TODO: test getPlayersOfGroup() - test('Retrieving players of a group works correctly', () async {}); + test('Retrieving players of a group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: testgroup.id, + ); + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testgroup.members[i].id); + expect(players[i].name, testgroup.members[i].name); + expect(players[i].createdAt, testgroup.members[i].createdAt); + } + }); }); } -- 2.49.1 From d948f2f13d6888996783f40b9761b87510c668f0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:42:27 +0100 Subject: [PATCH 250/563] Added iteration for multiple items test --- test/db_tests/game_test.dart | 130 +++++++++++++-------------------- test/db_tests/group_test.dart | 47 ++++++++---- test/db_tests/player_test.dart | 77 ++++++++++--------- 3 files changed, 129 insertions(+), 125 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d5b0856..71b7573 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -98,9 +98,8 @@ void main() { } }); - // TODO: Use upcoming addGames() method - // TODO: Iterate through games - test('Adding and fetching multiple games works correclty', () async { + test('Adding and fetching multiple games works correctly', () async { + // TODO: Use upcoming addGames() method await database.gameDao.addGame(game: testgame1); await database.gameDao.addGame(game: testgame2); await database.gameDao.addGame(game: testgameWithGroup); @@ -109,86 +108,61 @@ void main() { final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); - final fetchedGame1 = allGames.firstWhere((g) => g.id == testgame1.id); - // game checks - expect(fetchedGame1.id, testgame1.id); - expect(fetchedGame1.name, testgame1.name); - expect(fetchedGame1.createdAt, testgame1.createdAt); - expect(fetchedGame1.winner, testgame1.winner); + final testGames = { + testgame1.id: testgame1, + testgame2.id: testgame2, + testgameWithGroup.id: testgameWithGroup, + testgameWithPlayer.id: testgameWithPlayer, + }; - // group checks - expect(fetchedGame1.group!.id, testgame1.group!.id); - expect(fetchedGame1.group!.name, testgame1.group!.name); - expect(fetchedGame1.group!.createdAt, testgame1.group!.createdAt); - // group members checks - expect( - fetchedGame1.group!.members.length, - testgame1.group!.members.length, - ); - for (int i = 0; i < testgame1.group!.members.length; i++) { - expect( - fetchedGame1.group!.members[i].id, - testgame1.group!.members[i].id, - ); - expect( - fetchedGame1.group!.members[i].name, - testgame1.group!.members[i].name, - ); - expect( - fetchedGame1.group!.members[i].createdAt, - testgame1.group!.members[i].createdAt, - ); - } + for (final game in allGames) { + final expectedGame = testGames[game.id]!; - // players checks - for (int i = 0; i < fetchedGame1.players!.length; i++) { - expect(fetchedGame1.players![i].id, testgame1.players![i].id); - expect(fetchedGame1.players![i].name, testgame1.players![i].name); - expect( - fetchedGame1.players![i].createdAt, - testgame1.players![i].createdAt, - ); - } + // Game-Checks + expect(game.id, expectedGame.id); + expect(game.name, expectedGame.name); + expect(game.createdAt, expectedGame.createdAt); + expect(game.winner, expectedGame.winner); - final fetchedGame2 = allGames.firstWhere((g) => g.id == testgame2.id); - // game checks - expect(fetchedGame2.id, testgame2.id); - expect(fetchedGame2.name, testgame2.name); - expect(fetchedGame2.createdAt, testgame2.createdAt); - expect(fetchedGame2.winner, testgame2.winner); + // Group-Checks + if (expectedGame.group != null) { + expect(game.group!.id, expectedGame.group!.id); + expect(game.group!.name, expectedGame.group!.name); + expect(game.group!.createdAt, expectedGame.group!.createdAt); - // group checks - expect(fetchedGame2.group!.id, testgame2.group!.id); - expect(fetchedGame2.group!.name, testgame2.group!.name); - expect(fetchedGame2.group!.createdAt, testgame2.group!.createdAt); - // group members checks - expect( - fetchedGame2.group!.members.length, - testgame2.group!.members.length, - ); - for (int i = 0; i < testgame2.group!.members.length; i++) { - expect( - fetchedGame2.group!.members[i].id, - testgame2.group!.members[i].id, - ); - expect( - fetchedGame2.group!.members[i].name, - testgame2.group!.members[i].name, - ); - expect( - fetchedGame2.group!.members[i].createdAt, - testgame2.group!.members[i].createdAt, - ); - } + // Group Members-Checks + expect( + game.group!.members.length, + expectedGame.group!.members.length, + ); + for (int i = 0; i < expectedGame.group!.members.length; i++) { + expect( + game.group!.members[i].id, + expectedGame.group!.members[i].id, + ); + expect( + game.group!.members[i].name, + expectedGame.group!.members[i].name, + ); + expect( + game.group!.members[i].createdAt, + expectedGame.group!.members[i].createdAt, + ); + } + } - // players checks - for (int i = 0; i < fetchedGame2.players!.length; i++) { - expect(fetchedGame2.players![i].id, testgame2.players![i].id); - expect(fetchedGame2.players![i].name, testgame2.players![i].name); - expect( - fetchedGame2.players![i].createdAt, - testgame2.players![i].createdAt, - ); + // Players-Checks + if (expectedGame.players != null) { + expect(game.players!.length, expectedGame.players!.length); + for (int i = 0; i < expectedGame.players!.length; i++) { + expect(game.players![i].id, expectedGame.players![i].id); + expect(game.players![i].name, expectedGame.players![i].name); + expect( + game.players![i].createdAt, + expectedGame.players![i].createdAt, + ); + } + } } }); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index b18942e..189e4c3 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -14,6 +14,8 @@ void main() { late Player player4; late Group testgroup; late Group testgroup2; + late Group testgroup3; + late Group testgroup4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -40,6 +42,16 @@ void main() { name: 'Second Group', members: [player2, player3, player4], ); + testgroup3 = Group( + id: 'gr2', + name: 'Second Group', + members: [player2, player4], + ); + testgroup4 = Group( + id: 'gr2', + name: 'Second Group', + members: [player1, player2, player3, player4], + ); }); }); tearDown(() async { @@ -68,28 +80,37 @@ void main() { } }); - test('Adding and fetching a single group works correctly', () async { + test('Adding and fetching multiple groups works correctly', () async { + // TODO: Use upcoming addGroups() method await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup2); + await database.groupDao.addGroup(group: testgroup3); + await database.groupDao.addGroup(group: testgroup4); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); - final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id); - expect(fetchedGroup1.name, testgroup.name); - expect(fetchedGroup1.members.length, testgroup.members.length); - expect(fetchedGroup1.members.elementAt(0).id, player1.id); - expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt); + final testGroups = {testgroup.id: testgroup, testgroup2.id: testgroup2}; - final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id); - expect(fetchedGroup2.name, testgroup2.name); - expect(fetchedGroup2.members.length, testgroup2.members.length); - expect(fetchedGroup2.members.elementAt(0).id, player2.id); - expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt); + for (final group in allGroups) { + final expectedGroup = testGroups[group.id]!; + + expect(group.id, expectedGroup.id); + expect(group.name, expectedGroup.name); + expect(group.createdAt, expectedGroup.createdAt); + + expect(group.members.length, expectedGroup.members.length); + for (int i = 0; i < expectedGroup.members.length; i++) { + expect(group.members[i].id, expectedGroup.members[i].id); + expect(group.members[i].name, expectedGroup.members[i].name); + expect( + group.members[i].createdAt, + expectedGroup.members[i].createdAt, + ); + } + } }); - // TODO: Use upcoming addGroups() method - // TODO: An Test in Game Tests orientieren test('Adding the same group twice does not create duplicates', () async { await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testgroup); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 9430433..aa5d09e 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -7,8 +7,10 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player testPlayer; + late Player testPlayer1; late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -22,27 +24,29 @@ void main() { ); withClock(fakeClock, () { - testPlayer = Player(name: 'Test Player'); + testPlayer1 = Player(name: 'Test Player'); testPlayer2 = Player(name: 'Second Player'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); }); }); tearDown(() async { await database.close(); }); - group('player tests', () { + group('Player Tests', () { test('Adding and fetching single player works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 2); final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, + (g) => g.id == testPlayer1.id, ); - expect(fetchedPlayer1.name, testPlayer.name); - expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + expect(fetchedPlayer1.name, testPlayer1.name); + expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); final fetchedPlayer2 = allPlayers.firstWhere( (g) => g.id == testPlayer2.id, @@ -51,31 +55,36 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - // TODO: Use upcoming addPlayers() method - // TODO: An Tests in Game orientieren test('Adding and fetching multiple players works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + // TODO: Use upcoming addPlayers() method + await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); + await database.playerDao.addPlayer(player: testPlayer3); + await database.playerDao.addPlayer(player: testPlayer4); final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); + expect(allPlayers.length, 4); - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer.id, - ); - expect(fetchedPlayer1.name, testPlayer.name); - expect(fetchedPlayer1.createdAt, testPlayer.createdAt); + // Map for connencting fetched players with expected players + final testPlayer = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + testPlayer3.id: testPlayer3, + testPlayer4.id: testPlayer4, + }; - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); - expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); + for (final player in allPlayers) { + final expectedPlayer = testPlayer[player.id]!; + + expect(player.id, expectedPlayer.id); + expect(player.name, expectedPlayer.name); + expect(player.createdAt, expectedPlayer.createdAt); + } }); test('Adding the same player twice does not create duplicates', () async { - await database.playerDao.addPlayer(player: testPlayer); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer1); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 1); @@ -83,43 +92,43 @@ void main() { test('Player existence check works correctly', () async { var playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, false); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, true); }); test('Deleting a player works correclty', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); final playerDeleted = await database.playerDao.deletePlayer( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerDeleted, true); final playerExists = await database.playerDao.playerExists( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(playerExists, false); }); test('Updating a player name works correcly', () async { - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); const newPlayerName = 'new player name'; await database.playerDao.updatePlayername( - playerId: testPlayer.id, + playerId: testPlayer1.id, newName: newPlayerName, ); final result = await database.playerDao.getPlayerById( - playerId: testPlayer.id, + playerId: testPlayer1.id, ); expect(result.name, newPlayerName); }); @@ -128,7 +137,7 @@ void main() { var playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 0); - await database.playerDao.addPlayer(player: testPlayer); + await database.playerDao.addPlayer(player: testPlayer1); playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 1); @@ -138,7 +147,7 @@ void main() { playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 2); - await database.playerDao.deletePlayer(playerId: testPlayer.id); + await database.playerDao.deletePlayer(playerId: testPlayer1.id); playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 1); -- 2.49.1 From 8c0538520353ba9e42ff893a7f800d32591acf97 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 13:47:27 +0100 Subject: [PATCH 251/563] Renamed variables to be consistent --- test/db_tests/game_test.dart | 133 ++++++++++++++------------- test/db_tests/group_game_test.dart | 28 +++--- test/db_tests/group_test.dart | 96 +++++++++---------- test/db_tests/player_game_test.dart | 82 ++++++++--------- test/db_tests/player_group_test.dart | 24 ++--- 5 files changed, 183 insertions(+), 180 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 71b7573..a7163a3 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -9,17 +9,17 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; - late Group testgroup; - late Group testgroup2; - late Game testgame1; - late Game testgame2; - late Game testgameWithPlayer; - late Game testgameWithGroup; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Group testGroup1; + late Group testGroup2; + late Game testGame1; + late Game testGame2; + late Game testGameOnlyPlayers; + late Game testGameOnlyGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -33,31 +33,34 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); - testgroup = Group( + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); + testGroup1 = Group( name: 'Test Group 2', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgroup2 = Group(name: 'Test Group 2', members: [player4, player5]); - testgame1 = Game( - name: 'Test Game', - group: testgroup, - players: [player4, player5], + testGroup2 = Group( + name: 'Test Group 2', + members: [testPlayer4, testPlayer5], ); - testgame2 = Game( + testGame1 = Game( + name: 'First Test Game', + group: testGroup1, + players: [testPlayer4, testPlayer5], + ); + testGame2 = Game( name: 'Second Test Game', - group: testgroup2, - players: [player1, player2, player3], + group: testGroup2, + players: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithPlayer = Game( - name: 'Second Test Game', - players: [player1, player2, player3], + testGameOnlyPlayers = Game( + name: 'Test Game with Players', + players: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithGroup = Game(name: 'Second Test Game', group: testgroup2); + testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); }); }); tearDown(() async { @@ -66,32 +69,32 @@ void main() { group('Game Tests', () { test('Adding and fetching single game works correclty', () async { - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); - final result = await database.gameDao.getGameById(gameId: testgame1.id); + final result = await database.gameDao.getGameById(gameId: testGame1.id); - expect(result.id, testgame1.id); - expect(result.name, testgame1.name); - expect(result.winner, testgame1.winner); - expect(result.createdAt, testgame1.createdAt); + expect(result.id, testGame1.id); + expect(result.name, testGame1.name); + expect(result.winner, testGame1.winner); + expect(result.createdAt, testGame1.createdAt); if (result.group != null) { - expect(result.group!.members.length, testgroup.members.length); + expect(result.group!.members.length, testGroup1.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(result.group!.members[i].id, testgroup.members[i].id); - expect(result.group!.members[i].name, testgroup.members[i].name); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(result.group!.members[i].id, testGroup1.members[i].id); + expect(result.group!.members[i].name, testGroup1.members[i].name); } } else { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testgame1.players!.length); + expect(result.players!.length, testGame1.players!.length); - for (int i = 0; i < testgame1.players!.length; i++) { - expect(result.players![i].id, testgame1.players![i].id); - expect(result.players![i].name, testgame1.players![i].name); - expect(result.players![i].createdAt, testgame1.players![i].createdAt); + for (int i = 0; i < testGame1.players!.length; i++) { + expect(result.players![i].id, testGame1.players![i].id); + expect(result.players![i].name, testGame1.players![i].name); + expect(result.players![i].createdAt, testGame1.players![i].createdAt); } } else { fail('Players is null'); @@ -100,19 +103,19 @@ void main() { test('Adding and fetching multiple games works correctly', () async { // TODO: Use upcoming addGames() method - await database.gameDao.addGame(game: testgame1); - await database.gameDao.addGame(game: testgame2); - await database.gameDao.addGame(game: testgameWithGroup); - await database.gameDao.addGame(game: testgameWithPlayer); + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame2); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.gameDao.addGame(game: testGameOnlyPlayers); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); final testGames = { - testgame1.id: testgame1, - testgame2.id: testgame2, - testgameWithGroup.id: testgameWithGroup, - testgameWithPlayer.id: testgameWithPlayer, + testGame1.id: testGame1, + testGame2.id: testGame2, + testGameOnlyGroup.id: testGameOnlyGroup, + testGameOnlyPlayers.id: testGameOnlyPlayers, }; for (final game in allGames) { @@ -167,33 +170,33 @@ void main() { }); test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testgame1); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame1); final gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); }); test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testgame1.id); + var gameExists = await database.gameDao.gameExists(gameId: testGame1.id); expect(gameExists, false); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); - gameExists = await database.gameDao.gameExists(gameId: testgame1.id); + gameExists = await database.gameDao.gameExists(gameId: testGame1.id); expect(gameExists, true); }); test('Deleting a game works correclty', () async { - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); final gameDeleted = await database.gameDao.deleteGame( - gameId: testgame1.id, + gameId: testGame1.id, ); expect(gameDeleted, true); final gameExists = await database.gameDao.gameExists( - gameId: testgame1.id, + gameId: testGame1.id, ); expect(gameExists, false); }); @@ -202,22 +205,22 @@ void main() { var gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); - await database.gameDao.addGame(game: testgame1); + await database.gameDao.addGame(game: testGame1); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); - await database.gameDao.addGame(game: testgame2); + await database.gameDao.addGame(game: testGame2); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 2); - await database.gameDao.deleteGame(gameId: testgame1.id); + await database.gameDao.deleteGame(gameId: testGame1.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 1); - await database.gameDao.deleteGame(gameId: testgame2.id); + await database.gameDao.deleteGame(gameId: testGame2.id); gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index e231284..49d3cdb 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -9,11 +9,11 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; late Group testgroup; late Game testgameWithGroup; late Game testgameWithPlayers; @@ -30,20 +30,20 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); testgameWithPlayers = Game( - name: 'Game with Players', - players: [player4, player5], + name: 'Test Game with Players', + players: [testPlayer4, testPlayer5], ); - testgameWithGroup = Game(name: 'Game with Group', group: testgroup); + testgameWithGroup = Game(name: 'Test Game with Group', group: testgroup); }); }); tearDown(() async { diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 189e4c3..2cf9bba 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -8,14 +8,14 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Group testgroup; - late Group testgroup2; - late Group testgroup3; - late Group testgroup4; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Group testGroup1; + late Group testGroup2; + late Group testGroup3; + late Group testGroup4; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -29,28 +29,28 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - testgroup = Group( + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testGroup1 = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgroup2 = Group( + testGroup2 = Group( id: 'gr2', name: 'Second Group', - members: [player2, player3, player4], + members: [testPlayer2, testPlayer3, testPlayer4], ); - testgroup3 = Group( + testGroup3 = Group( id: 'gr2', name: 'Second Group', - members: [player2, player4], + members: [testPlayer2, testPlayer4], ); - testgroup4 = Group( + testGroup4 = Group( id: 'gr2', name: 'Second Group', - members: [player1, player2, player3, player4], + members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); }); }); @@ -59,38 +59,38 @@ void main() { }); group('Group Tests', () { test('Adding and fetching a single group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final fetchedGroup = await database.groupDao.getGroupById( - groupId: testgroup.id, + groupId: testGroup1.id, ); - expect(fetchedGroup.id, testgroup.id); - expect(fetchedGroup.name, testgroup.name); - expect(fetchedGroup.createdAt, testgroup.createdAt); + expect(fetchedGroup.id, testGroup1.id); + expect(fetchedGroup.name, testGroup1.name); + expect(fetchedGroup.createdAt, testGroup1.createdAt); - expect(fetchedGroup.members.length, testgroup.members.length); - for (int i = 0; i < testgroup.members.length; i++) { - expect(fetchedGroup.members[i].id, testgroup.members[i].id); - expect(fetchedGroup.members[i].name, testgroup.members[i].name); + expect(fetchedGroup.members.length, testGroup1.members.length); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(fetchedGroup.members[i].id, testGroup1.members[i].id); + expect(fetchedGroup.members[i].name, testGroup1.members[i].name); expect( fetchedGroup.members[i].createdAt, - testgroup.members[i].createdAt, + testGroup1.members[i].createdAt, ); } }); test('Adding and fetching multiple groups works correctly', () async { // TODO: Use upcoming addGroups() method - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup2); - await database.groupDao.addGroup(group: testgroup3); - await database.groupDao.addGroup(group: testgroup4); + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup2); + await database.groupDao.addGroup(group: testGroup3); + await database.groupDao.addGroup(group: testGroup4); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); - final testGroups = {testgroup.id: testgroup, testgroup2.id: testgroup2}; + final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; for (final group in allGroups) { final expectedGroup = testGroups[group.id]!; @@ -112,8 +112,8 @@ void main() { }); test('Adding the same group twice does not create duplicates', () async { - await database.groupDao.addGroup(group: testgroup); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup1); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 1); @@ -121,42 +121,42 @@ void main() { test('Group existence check works correctly', () async { var groupExists = await database.groupDao.groupExists( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupExists, false); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); - groupExists = await database.groupDao.groupExists(groupId: testgroup.id); + groupExists = await database.groupDao.groupExists(groupId: testGroup1.id); expect(groupExists, true); }); test('Deleting a group works correclty', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final groupDeleted = await database.groupDao.deleteGroup( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupDeleted, true); final groupExists = await database.groupDao.groupExists( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupExists, false); }); test('Updating a group name works correcly', () async { - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); const newGroupName = 'new group name'; await database.groupDao.updateGroupname( - groupId: testgroup.id, + groupId: testGroup1.id, newName: newGroupName, ); final result = await database.groupDao.getGroupById( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(result.name, newGroupName); }); @@ -165,13 +165,13 @@ void main() { final initialCount = await database.groupDao.getGroupCount(); expect(initialCount, 0); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); final groupAdded = await database.groupDao.getGroupCount(); expect(groupAdded, 1); final groupRemoved = await database.groupDao.deleteGroup( - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupRemoved, true); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 1fd0128..0eca9ff 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -9,15 +9,15 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; - late Player player5; - late Player player6; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Player testPlayer6; late Group testgroup; - late Game testgameWithGroup; - late Game testgameWithPlayers; + late Game testGameOnlyGroup; + late Game testGameOnlyPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -31,20 +31,20 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); - player5 = Player(name: 'Eve'); - player6 = Player(name: 'Frank'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); + testPlayer5 = Player(name: 'Eve'); + testPlayer6 = Player(name: 'Frank'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); - testgameWithGroup = Game(name: 'Test Game', group: testgroup); - testgameWithPlayers = Game( + testGameOnlyGroup = Game(name: 'Test Game with Group', group: testgroup); + testGameOnlyPlayers = Game( name: 'Test Game with Players', - players: [player4, player5, player6], + players: [testPlayer4, testPlayer5, testPlayer6], ); }); }); @@ -54,44 +54,44 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testgameWithGroup); - database.playerDao.addPlayer(player: player1); + database.gameDao.addGame(game: testGameOnlyGroup); + database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, ); expect(gameHasPlayers, false); database.playerGameDao.addPlayerToGame( - gameId: testgameWithGroup.id, - playerId: player1.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer1.id, ); gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, ); expect(gameHasPlayers, true); }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithGroup); - database.playerDao.addPlayer(player: player5); + database.gameDao.addGame(game: testGameOnlyGroup); + database.playerDao.addPlayer(player: testPlayer5); database.playerGameDao.addPlayerToGame( - gameId: testgameWithGroup.id, - playerId: player5.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer5.id, ); var playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testgameWithGroup.id, - playerId: player5.id, + gameId: testGameOnlyGroup.id, + playerId: testPlayer5.id, ); expect(playerAdded, true); playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testgameWithGroup.id, + gameId: testGameOnlyGroup.id, playerId: '', ); @@ -99,20 +99,20 @@ void main() { }); test('Removing player from game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + await database.gameDao.addGame(game: testGameOnlyPlayers); - final playerToRemove = testgameWithPlayers.players![0]; + final playerToRemove = testGameOnlyPlayers.players![0]; final removed = await database.playerGameDao.removePlayerFromGame( playerId: playerToRemove.id, - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); expect(removed, true); final result = await database.gameDao.getGameById( - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); - expect(result.players!.length, testgameWithPlayers.players!.length - 1); + expect(result.players!.length, testGameOnlyPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, @@ -121,9 +121,9 @@ void main() { }); test('Retrieving players of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + await database.gameDao.addGame(game: testGameOnlyPlayers); final players = await database.playerGameDao.getPlayersOfGame( - gameId: testgameWithPlayers.id, + gameId: testGameOnlyPlayers.id, ); if (players == null) { @@ -131,9 +131,9 @@ void main() { } for (int i = 0; i < players.length; i++) { - expect(players[i].id, testgameWithPlayers.players![i].id); - expect(players[i].name, testgameWithPlayers.players![i].name); - expect(players[i].createdAt, testgameWithPlayers.players![i].createdAt); + expect(players[i].id, testGameOnlyPlayers.players![i].id); + expect(players[i].name, testGameOnlyPlayers.players![i].name); + expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); } }); }); diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart index 9e367e0..2783430 100644 --- a/test/db_tests/player_group_test.dart +++ b/test/db_tests/player_group_test.dart @@ -8,10 +8,10 @@ import 'package:game_tracker/data/dto/player.dart'; void main() { late AppDatabase database; - late Player player1; - late Player player2; - late Player player3; - late Player player4; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; late Group testgroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -26,13 +26,13 @@ void main() { ); withClock(fakeClock, () { - player1 = Player(name: 'Alice'); - player2 = Player(name: 'Bob'); - player3 = Player(name: 'Charlie'); - player4 = Player(name: 'Diana'); + testPlayer1 = Player(name: 'Alice'); + testPlayer2 = Player(name: 'Bob'); + testPlayer3 = Player(name: 'Charlie'); + testPlayer4 = Player(name: 'Diana'); testgroup = Group( name: 'Test Group', - members: [player1, player2, player3], + members: [testPlayer1, testPlayer2, testPlayer3], ); }); }); @@ -46,15 +46,15 @@ void main() { test('Adding a player to a group works correctly', () async { await database.groupDao.addGroup(group: testgroup); - await database.playerDao.addPlayer(player: player4); + await database.playerDao.addPlayer(player: testPlayer4); await database.playerGroupDao.addPlayerToGroup( groupId: testgroup.id, - player: player4, + player: testPlayer4, ); var playerAdded = await database.playerGroupDao.isPlayerInGroup( groupId: testgroup.id, - playerId: player4.id, + playerId: testPlayer4.id, ); expect(playerAdded, true); -- 2.49.1 From 51722eb7fd25dbfe321df3f853f705e46df89d0f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:01:45 +0100 Subject: [PATCH 252/563] Added batch insert methods to tests --- test/db_tests/game_test.dart | 8 +++----- test/db_tests/group_test.dart | 8 +++----- test/db_tests/player_test.dart | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index a7163a3..86ca34b 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -102,11 +102,9 @@ void main() { }); test('Adding and fetching multiple games works correctly', () async { - // TODO: Use upcoming addGames() method - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); - await database.gameDao.addGame(game: testGameOnlyGroup); - await database.gameDao.addGame(game: testGameOnlyPlayers); + await database.gameDao.addGames( + games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], + ); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 4); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2cf9bba..b2e63eb 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -81,11 +81,9 @@ void main() { }); test('Adding and fetching multiple groups works correctly', () async { - // TODO: Use upcoming addGroups() method - await database.groupDao.addGroup(group: testGroup1); - await database.groupDao.addGroup(group: testGroup2); - await database.groupDao.addGroup(group: testGroup3); - await database.groupDao.addGroup(group: testGroup4); + await database.groupDao.addGroups( + groups: [testGroup1, testGroup2, testGroup3, testGroup4], + ); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 2); diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index aa5d09e..0d72ed5 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -56,11 +56,9 @@ void main() { }); test('Adding and fetching multiple players works correclty', () async { - // TODO: Use upcoming addPlayers() method - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer2); - await database.playerDao.addPlayer(player: testPlayer3); - await database.playerDao.addPlayer(player: testPlayer4); + await database.playerDao.addPlayers( + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], + ); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers.length, 4); -- 2.49.1 From c56663d15e1a4caef27193254846c507f52be363 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:06:11 +0100 Subject: [PATCH 253/563] Added missing await --- test/db_tests/group_game_test.dart | 12 ++++++------ test/db_tests/player_game_test.dart | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 49d3cdb..1733243 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -51,8 +51,8 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( gameId: testgameWithPlayers.id, @@ -60,7 +60,7 @@ void main() { expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame( + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); @@ -73,9 +73,9 @@ void main() { }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame( + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 0eca9ff..e8fd707 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -54,8 +54,8 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( gameId: testGameOnlyGroup.id, @@ -63,7 +63,7 @@ void main() { expect(gameHasPlayers, false); - database.playerGameDao.addPlayerToGame( + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer1.id, ); @@ -76,9 +76,9 @@ void main() { }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer5); - database.playerGameDao.addPlayerToGame( + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer5); + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer5.id, ); -- 2.49.1 From dbb52cfc48546f69245297a0bf3b883d5bd8ba29 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:20:42 +0100 Subject: [PATCH 254/563] Added missing awaits --- test/db_tests/group_game_test.dart | 12 ++++++------ test/db_tests/player_game_test.dart | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 49d3cdb..1733243 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -51,8 +51,8 @@ void main() { }); group('Group-Game Tests', () { test('Game has group works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); var gameHasGroup = await database.groupGameDao.gameHasGroup( gameId: testgameWithPlayers.id, @@ -60,7 +60,7 @@ void main() { expect(gameHasGroup, false); - database.groupGameDao.addGroupToGame( + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); @@ -73,9 +73,9 @@ void main() { }); test('Adding a group to a game works correctly', () async { - database.gameDao.addGame(game: testgameWithPlayers); - database.groupDao.addGroup(group: testgroup); - database.groupGameDao.addGroupToGame( + await database.gameDao.addGame(game: testgameWithPlayers); + await database.groupDao.addGroup(group: testgroup); + await database.groupGameDao.addGroupToGame( testgameWithPlayers.id, testgroup.id, ); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 0eca9ff..f50afc1 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -54,8 +54,8 @@ void main() { group('Player-Game Tests', () { test('Game has player works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer1); var gameHasPlayers = await database.playerGameDao.gameHasPlayers( gameId: testGameOnlyGroup.id, @@ -76,9 +76,9 @@ void main() { }); test('Adding a player to a game works correctly', () async { - database.gameDao.addGame(game: testGameOnlyGroup); - database.playerDao.addPlayer(player: testPlayer5); - database.playerGameDao.addPlayerToGame( + await database.gameDao.addGame(game: testGameOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer5); + await database.playerGameDao.addPlayerToGame( gameId: testGameOnlyGroup.id, playerId: testPlayer5.id, ); -- 2.49.1 From ab250e2df43d1880d6d0e0c190a2ca00b7be76d2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 21 Nov 2025 14:24:57 +0100 Subject: [PATCH 255/563] Typo --- test/db_tests/game_test.dart | 4 ++-- test/db_tests/group_test.dart | 2 +- test/db_tests/player_test.dart | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index a7163a3..311e30f 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -68,7 +68,7 @@ void main() { }); group('Game Tests', () { - test('Adding and fetching single game works correclty', () async { + test('Adding and fetching single game works correctly', () async { await database.gameDao.addGame(game: testGame1); final result = await database.gameDao.getGameById(gameId: testGame1.id); @@ -187,7 +187,7 @@ void main() { expect(gameExists, true); }); - test('Deleting a game works correclty', () async { + test('Deleting a game works correctly', () async { await database.gameDao.addGame(game: testGame1); final gameDeleted = await database.gameDao.deleteGame( diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 2cf9bba..9104b74 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -131,7 +131,7 @@ void main() { expect(groupExists, true); }); - test('Deleting a group works correclty', () async { + test('Deleting a group works correctly', () async { await database.groupDao.addGroup(group: testGroup1); final groupDeleted = await database.groupDao.deleteGroup( diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index aa5d09e..2ec57e5 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -35,7 +35,7 @@ void main() { }); group('Player Tests', () { - test('Adding and fetching single player works correclty', () async { + test('Adding and fetching single player works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); @@ -55,7 +55,7 @@ void main() { expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); }); - test('Adding and fetching multiple players works correclty', () async { + test('Adding and fetching multiple players works correctly', () async { // TODO: Use upcoming addPlayers() method await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer2); @@ -104,7 +104,7 @@ void main() { expect(playerExists, true); }); - test('Deleting a player works correclty', () async { + test('Deleting a player works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); final playerDeleted = await database.playerDao.deletePlayer( playerId: testPlayer1.id, -- 2.49.1 From 95f0861a7967e1194f8ede4d7cbfa36a7642860e Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:44:27 +0100 Subject: [PATCH 256/563] add basic came history tile --- .../views/main_menu/game_history_view.dart | 10 ++-- .../widgets/game_history_tile.dart | 56 ++++++++++++++----- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b14244b..a0ea4a9 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -134,16 +134,16 @@ class _GameHistoryViewState extends State { children: [ Column( children: [ - Container(margin: EdgeInsets.only(bottom: 75)), + Container(margin: const EdgeInsets.only(bottom: 75)), Expanded( child: gameHistoryListView(allGameData, suggestedGameData), ), ], ), Container( - margin: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), + margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), child: SearchBar( - leading: Icon(Icons.search), + leading: const Icon(Icons.search), onChanged: (value) { if (value.isEmpty) { setState(() { @@ -195,9 +195,9 @@ Widget gameHistoryListView(allGameData, suggestedGameData) { return GameHistoryTile( gameTitle: currentGame['title'], gameType: currentGame['game'], - ruleset: currentGame['date'], + date: currentGame['date'], groupName: currentGame['group'], - winner: "ich", + winner: 'ich', ); }, ); diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/game_history_tile.dart index e461f04..c21b2c2 100644 --- a/lib/presentation/widgets/game_history_tile.dart +++ b/lib/presentation/widgets/game_history_tile.dart @@ -5,7 +5,7 @@ import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryTile extends StatefulWidget { final String gameTitle; final String gameType; - final String ruleset; + final String date; final String groupName; final String winner; @@ -13,7 +13,7 @@ class GameHistoryTile extends StatefulWidget { super.key, required this.gameTitle, required this.gameType, - required this.ruleset, + required this.date, required this.groupName, required this.winner, }); @@ -29,18 +29,46 @@ class _GameHistoryTileState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - widget.gameTitle, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(width: 5), - Text( - widget.gameType, - style: const TextStyle(fontSize: 14, color: Colors.grey), - ), - ], + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Row( + children: [ + Text( + widget.gameTitle, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + ), + ], + ), + Row( + children: [ + Text( + widget.date, + style: const TextStyle(fontSize: 14, color: Colors.grey), + textAlign: TextAlign.left, + ), + const SizedBox(width: 5), + const Text('·'), + const SizedBox(width: 5), + Text( + widget.gameType, + style: const TextStyle(fontSize: 14, color: Colors.grey), + textAlign: TextAlign.left, + ), + ], + ), + const SizedBox(height: 15), + + ] + ) ), ], ); -- 2.49.1 From 1b3334f3e0c7439d74931ce6554c393cbecd4079 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:24 +0100 Subject: [PATCH 257/563] Fixed addGroups method --- lib/data/dao/group_dao.dart | 53 +++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 3489f5c..fbb4d6f 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -87,10 +87,17 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { Future addGroups({required List groups}) async { if (groups.isEmpty) return; await db.transaction(() async { + // Deduplicate groups by id - keep first occurrence + final Map uniqueGroups = {}; + for (final g in groups) { + uniqueGroups.putIfAbsent(g.id, () => g); + } + + // Insert unique groups in batch await db.batch( (b) => b.insertAll( groupTable, - groups + uniqueGroups.values .map( (group) => GroupTableCompanion.insert( id: group.id, @@ -103,17 +110,24 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ); - for (final group in groups) { - await db.playerDao.addPlayers(players: group.members); + // Collect unique players from all groups + final uniquePlayers = {}; + for (final g in uniqueGroups.values) { + for (final m in g.members) { + uniquePlayers[m.id] = m; + } + } + if (uniquePlayers.isNotEmpty) { await db.batch( (b) => b.insertAll( - db.playerGroupTable, - group.members + db.playerTable, + uniquePlayers.values .map( - (member) => PlayerGroupTableCompanion.insert( - playerId: member.id, - groupId: group.id, + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, ), ) .toList(), @@ -121,6 +135,29 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ); } + + // Prepare all player-group associations in one list (unique pairs) + final Set seenPairs = {}; + final List pgRows = []; + for (final g in uniqueGroups.values) { + for (final m in g.members) { + final key = '${m.id}|${g.id}'; + if (!seenPairs.contains(key)) { + seenPairs.add(key); + pgRows.add( + PlayerGroupTableCompanion.insert(playerId: m.id, groupId: g.id), + ); + } + } + } + + if (pgRows.isNotEmpty) { + await db.batch((b) { + for (final pg in pgRows) { + b.insert(db.playerGroupTable, pg, mode: InsertMode.insertOrReplace); + } + }); + } }); } -- 2.49.1 From 70307016b36581f9e7495a8c149a74776ad5f6a2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:32 +0100 Subject: [PATCH 258/563] Fixed addGames method --- lib/data/dao/game_dao.dart | 78 +++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 12893db..47b5848 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -107,6 +107,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { mode: InsertMode.insertOrReplace, ), ); + // Add all groups of the games in batch await db.batch( (b) => b.insertAll( @@ -125,8 +126,42 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ), ); - // Add all players of the games in batch - await db.batch((b) async { + // Add all players of the games in batch (unique) + final uniquePlayers = {}; + for (final game in games) { + if (game.players != null) { + for (final p in game.players!) { + uniquePlayers[p.id] = p; + } + } + // Also include members of groups + if (game.group != null) { + for (final m in game.group!.members) { + uniquePlayers[m.id] = m; + } + } + } + + if (uniquePlayers.isNotEmpty) { + await db.batch( + (b) => b.insertAll( + db.playerTable, + uniquePlayers.values + .map( + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + + // Add all player-game associations in batch + await db.batch((b) { for (final game in games) { if (game.players != null) { for (final p in game.players ?? []) { @@ -143,8 +178,26 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } }); + // Add all player-group associations in batch + await db.batch((b) { + for (final game in games) { + if (game.group != null) { + for (final m in game.group!.members) { + b.insert( + db.playerGroupTable, + PlayerGroupTableCompanion.insert( + playerId: m.id, + groupId: game.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + // Add all group-game associations in batch - await db.batch((b) async { + await db.batch((b) { for (final game in games) { if (game.group != null) { b.insert( @@ -158,25 +211,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } } }); - - // Add all player-game associations in batch - await db.batch((b) async { - for (final game in games) { - if (game.players != null) { - for (final p in game.players ?? []) { - b.insert( - db.playerTable, - PlayerTableCompanion.insert( - id: p.id, - name: p.name, - createdAt: p.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); }); } -- 2.49.1 From 2ebd4274f05b22bde0dcb9f927de2e55d49f3f1a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 00:47:44 +0100 Subject: [PATCH 259/563] Moved method validateJsonSchema() --- .../views/main_menu/settings_view.dart | 25 ----------------- lib/services/data_transfer_service.dart | 28 +++++++++++++++++-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 70f7663..6ebb7fb 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,39 +1,14 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; -import 'package:json_schema/json_schema.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @override State createState() => _SettingsViewState(); - - static Future validateJsonSchema(String jsonString) async { - final String schemaString; - - schemaString = await rootBundle.loadString('assets/schema.json'); - - try { - final schema = JsonSchema.create(json.decode(schemaString)); - final jsonData = json.decode(jsonString); - final result = schema.validate(jsonData); - - if (result.isValid) { - return true; - } - return false; - } catch (e, stack) { - print('[validateJsonSchema] $e'); - print(stack); - return false; - } - } } class _SettingsViewState extends State { diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 13eb658..eaa9633 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -1,15 +1,15 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; class DataTransferService { @@ -85,7 +85,7 @@ class DataTransferService { return ImportResult.fileReadError; } - if (await SettingsView.validateJsonSchema(jsonString)) { + if (await _validateJsonSchema(jsonString)) { final Map jsonData = json.decode(jsonString) as Map; @@ -136,4 +136,26 @@ class DataTransferService { if (file.path != null) return await File(file.path!).readAsString(); return null; } + + /// Validates the given JSON string against the predefined schema. + static Future _validateJsonSchema(String jsonString) async { + final String schemaString; + + schemaString = await rootBundle.loadString('assets/schema.json'); + + try { + final schema = JsonSchema.create(json.decode(schemaString)); + final jsonData = json.decode(jsonString); + final result = schema.validate(jsonData); + + if (result.isValid) { + return true; + } + return false; + } catch (e, stack) { + print('[validateJsonSchema] $e'); + print(stack); + return false; + } + } } -- 2.49.1 From 893eb91143a67ff175a1ee8fed77d7a3b4c7ba2b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 01:12:39 +0100 Subject: [PATCH 260/563] Schema corrected --- assets/schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 69f889b..1883122 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -19,7 +19,7 @@ }, "players": { "type": [ - "object", + "array", "null" ], "properties": { @@ -88,7 +88,7 @@ ] }, "winner": { - "type": "string" + "type": ["string","null"] }, "required": [ "id", -- 2.49.1 From 62eea086144522acde8a8396aa584f3dbd24b9b2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:12:41 +0100 Subject: [PATCH 261/563] Renamed variable --- lib/data/dao/game_dao.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index f29d553..283c02f 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -19,12 +19,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return Future.wait( result.map((row) async { final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); - final player = await db.playerGameDao.getPlayersOfGame(gameId: row.id); + final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); return Game( id: row.id, name: row.name, group: group, - players: player, + players: players, createdAt: row.createdAt, winner: row.winnerId, ); -- 2.49.1 From 24f18f5c655088ecfa127495d8e34e8592aa1b9d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:13:15 +0100 Subject: [PATCH 262/563] Removed false comparison --- lib/data/dao/player_group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 4024629..8cf96c2 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -25,7 +25,7 @@ class PlayerGroupDao extends DatabaseAccessor return false; } - if (await db.playerDao.playerExists(playerId: player.id) == false) { + if (!await db.playerDao.playerExists(playerId: player.id)) { db.playerDao.addPlayer(player: player); } -- 2.49.1 From bef812502cbe4ca7ed4a38974fb443bfc913232d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:20:51 +0100 Subject: [PATCH 263/563] Renamed variable and added null checks --- test/db_tests/game_test.dart | 53 ++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 311e30f..7590a97 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -119,52 +119,47 @@ void main() { }; for (final game in allGames) { - final expectedGame = testGames[game.id]!; + final testGame = testGames[game.id]!; // Game-Checks - expect(game.id, expectedGame.id); - expect(game.name, expectedGame.name); - expect(game.createdAt, expectedGame.createdAt); - expect(game.winner, expectedGame.winner); + expect(game.id, testGame.id); + expect(game.name, testGame.name); + expect(game.createdAt, testGame.createdAt); + expect(game.winner, testGame.winner); // Group-Checks - if (expectedGame.group != null) { - expect(game.group!.id, expectedGame.group!.id); - expect(game.group!.name, expectedGame.group!.name); - expect(game.group!.createdAt, expectedGame.group!.createdAt); + if (testGame.group != null) { + expect(game.group!.id, testGame.group!.id); + expect(game.group!.name, testGame.group!.name); + expect(game.group!.createdAt, testGame.group!.createdAt); // Group Members-Checks - expect( - game.group!.members.length, - expectedGame.group!.members.length, - ); - for (int i = 0; i < expectedGame.group!.members.length; i++) { - expect( - game.group!.members[i].id, - expectedGame.group!.members[i].id, - ); + expect(game.group!.members.length, testGame.group!.members.length); + for (int i = 0; i < testGame.group!.members.length; i++) { + expect(game.group!.members[i].id, testGame.group!.members[i].id); expect( game.group!.members[i].name, - expectedGame.group!.members[i].name, + testGame.group!.members[i].name, ); expect( game.group!.members[i].createdAt, - expectedGame.group!.members[i].createdAt, + testGame.group!.members[i].createdAt, ); } + } else { + expect(game.group, null); } // Players-Checks - if (expectedGame.players != null) { - expect(game.players!.length, expectedGame.players!.length); - for (int i = 0; i < expectedGame.players!.length; i++) { - expect(game.players![i].id, expectedGame.players![i].id); - expect(game.players![i].name, expectedGame.players![i].name); - expect( - game.players![i].createdAt, - expectedGame.players![i].createdAt, - ); + if (testGame.players != null) { + expect(game.players!.length, testGame.players!.length); + for (int i = 0; i < testGame.players!.length; i++) { + expect(game.players![i].id, testGame.players![i].id); + expect(game.players![i].name, testGame.players![i].name); + expect(game.players![i].createdAt, testGame.players![i].createdAt); } + } else { + expect(game.players, null); } } }); -- 2.49.1 From 9346f61d141c1658e33101527e04ff6e88689f56 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:21:28 +0100 Subject: [PATCH 264/563] Renamed variable --- test/db_tests/group_test.dart | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index 9104b74..6c8e6c9 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -93,20 +93,17 @@ void main() { final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; for (final group in allGroups) { - final expectedGroup = testGroups[group.id]!; + final testGroup = testGroups[group.id]!; - expect(group.id, expectedGroup.id); - expect(group.name, expectedGroup.name); - expect(group.createdAt, expectedGroup.createdAt); + expect(group.id, testGroup.id); + expect(group.name, testGroup.name); + expect(group.createdAt, testGroup.createdAt); - expect(group.members.length, expectedGroup.members.length); - for (int i = 0; i < expectedGroup.members.length; i++) { - expect(group.members[i].id, expectedGroup.members[i].id); - expect(group.members[i].name, expectedGroup.members[i].name); - expect( - group.members[i].createdAt, - expectedGroup.members[i].createdAt, - ); + expect(group.members.length, testGroup.members.length); + for (int i = 0; i < testGroup.members.length; i++) { + expect(group.members[i].id, testGroup.members[i].id); + expect(group.members[i].name, testGroup.members[i].name); + expect(group.members[i].createdAt, testGroup.members[i].createdAt); } } }); -- 2.49.1 From 30645f06f82c2c7cf20f9f79d1bb1f22aef3d59f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 14:21:55 +0100 Subject: [PATCH 265/563] Renamed variable --- test/db_tests/player_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 2ec57e5..30d9a14 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -66,7 +66,7 @@ void main() { expect(allPlayers.length, 4); // Map for connencting fetched players with expected players - final testPlayer = { + final testPlayers = { testPlayer1.id: testPlayer1, testPlayer2.id: testPlayer2, testPlayer3.id: testPlayer3, @@ -74,11 +74,11 @@ void main() { }; for (final player in allPlayers) { - final expectedPlayer = testPlayer[player.id]!; + final testPlayer = testPlayers[player.id]!; - expect(player.id, expectedPlayer.id); - expect(player.name, expectedPlayer.name); - expect(player.createdAt, expectedPlayer.createdAt); + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); } }); -- 2.49.1 From 63d9ed400d1fc61614dee1df4934ebfaf9cddee6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 16:18:37 +0100 Subject: [PATCH 266/563] Adjust padding in CustomNavigationBar - Add vertical minimum padding to SafeArea - Remove bottom padding in bottomNavigationBar - replaced left/right padding in bottomNavigationBar with EdgeInsets.symmetric - removed bottom padding from bottomNavigationBar --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 28331b8..2805689 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -32,6 +32,7 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { return SafeArea( + minimum: EdgeInsets.symmetric(vertical: 30), child: Scaffold( appBar: AppBar( centerTitle: true, @@ -56,7 +57,7 @@ class _CustomNavigationBarState extends State body: tabs[currentIndex], extendBody: true, bottomNavigationBar: Padding( - padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 18.0), + padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Material( elevation: 10, borderRadius: BorderRadius.circular(24), -- 2.49.1 From b668f6b9ae54375b6e5b59a9f984dd6a63a5d2a0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 16:31:15 +0100 Subject: [PATCH 267/563] fix black top/bottom bar when wrapping scaffold in safearea --- .../main_menu/custom_navigation_bar.dart | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2805689..709d541 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -31,73 +31,76 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { - return SafeArea( - minimum: EdgeInsets.symmetric(vertical: 30), - child: Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - _currentTabTitle(), - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + return Container( + decoration: BoxDecoration(color: CustomTheme.backgroundColor), + child: SafeArea( + minimum: EdgeInsets.symmetric(vertical: 30), + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + _currentTabTitle(), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), + ), + icon: const Icon(Icons.settings), + ), + ], + elevation: 0, ), backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - actions: [ - IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsView()), - ), - icon: const Icon(Icons.settings), - ), - ], - elevation: 0, - ), - backgroundColor: CustomTheme.backgroundColor, - body: tabs[currentIndex], - extendBody: true, - bottomNavigationBar: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Material( - elevation: 10, - borderRadius: BorderRadius.circular(24), - color: CustomTheme.primaryColor, - child: ClipRRect( + body: tabs[currentIndex], + extendBody: true, + bottomNavigationBar: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Material( + elevation: 10, borderRadius: BorderRadius.circular(24), - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - NavbarItem( - index: 0, - isSelected: currentIndex == 0, - icon: Icons.home_rounded, - label: 'Home', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 1, - isSelected: currentIndex == 1, - icon: Icons.gamepad_rounded, - label: 'Games', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 2, - isSelected: currentIndex == 2, - icon: Icons.group_rounded, - label: 'Groups', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 3, - isSelected: currentIndex == 3, - icon: Icons.bar_chart_rounded, - label: 'Stats', - onTabTapped: onTabTapped, - ), - ], + color: CustomTheme.primaryColor, + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: 'Home', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: 'Games', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: 'Groups', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: 'Stats', + onTabTapped: onTabTapped, + ), + ], + ), ), ), ), -- 2.49.1 From 28ed22ce732bdfe9ee1fabf6c0e9f59c6733df89 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 17:40:00 +0100 Subject: [PATCH 268/563] wrap navbar in SafeArea and replaced Material with Container --- .../main_menu/custom_navigation_bar.dart | 129 +++++++++--------- 1 file changed, 63 insertions(+), 66 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 709d541..9727bc0 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -31,76 +31,73 @@ class _CustomNavigationBarState extends State @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration(color: CustomTheme.backgroundColor), - child: SafeArea( - minimum: EdgeInsets.symmetric(vertical: 30), - child: Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - _currentTabTitle(), - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + _currentTabTitle(), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsView()), ), - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - actions: [ - IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsView()), - ), - icon: const Icon(Icons.settings), - ), - ], - elevation: 0, + icon: const Icon(Icons.settings), ), - backgroundColor: CustomTheme.backgroundColor, - body: tabs[currentIndex], - extendBody: true, - bottomNavigationBar: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Material( - elevation: 10, + ], + elevation: 0, + ), + backgroundColor: CustomTheme.backgroundColor, + body: tabs[currentIndex], + extendBody: true, + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Container( + decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), color: CustomTheme.primaryColor, - child: ClipRRect( - borderRadius: BorderRadius.circular(24), - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - NavbarItem( - index: 0, - isSelected: currentIndex == 0, - icon: Icons.home_rounded, - label: 'Home', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 1, - isSelected: currentIndex == 1, - icon: Icons.gamepad_rounded, - label: 'Games', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 2, - isSelected: currentIndex == 2, - icon: Icons.group_rounded, - label: 'Groups', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 3, - isSelected: currentIndex == 3, - icon: Icons.bar_chart_rounded, - label: 'Stats', - onTabTapped: onTabTapped, - ), - ], - ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: 'Home', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: 'Games', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: 'Groups', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: 'Stats', + onTabTapped: onTabTapped, + ), + ], ), ), ), -- 2.49.1 From df3215ef761e4264a31ec1441bf2e05ddd6731d6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 17:40:24 +0100 Subject: [PATCH 269/563] Made bottom padding adaptive to screen --- lib/presentation/views/main_menu/groups_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index c45cf21..5a7a2eb 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -103,7 +103,7 @@ class _GroupsViewState extends State { ), Positioned( - bottom: 80, + bottom: MediaQuery.paddingOf(context).bottom + 15, child: CustomWidthButton( text: 'Create Group', sizeRelativeToWidth: 0.90, -- 2.49.1 From 5062196463e0df16477d2901f01db291e377bc30 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 17:57:58 +0100 Subject: [PATCH 270/563] Adjust safeareas minimum padding for custom navigation bar --- .../main_menu/custom_navigation_bar.dart | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 9727bc0..7176505 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -55,50 +55,49 @@ class _CustomNavigationBarState extends State body: tabs[currentIndex], extendBody: true, bottomNavigationBar: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - color: CustomTheme.primaryColor, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(24), - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - NavbarItem( - index: 0, - isSelected: currentIndex == 0, - icon: Icons.home_rounded, - label: 'Home', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 1, - isSelected: currentIndex == 1, - icon: Icons.gamepad_rounded, - label: 'Games', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 2, - isSelected: currentIndex == 2, - icon: Icons.group_rounded, - label: 'Groups', - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 3, - isSelected: currentIndex == 3, - icon: Icons.bar_chart_rounded, - label: 'Stats', - onTabTapped: onTabTapped, - ), - ], - ), + minimum: const EdgeInsets.only(bottom: 30), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: CustomTheme.primaryColor, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: 'Home', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: 'Games', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: 'Groups', + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: 'Stats', + onTabTapped: onTabTapped, + ), + ], ), ), ), -- 2.49.1 From fd13fe6e903bc61d4843200385203bdbe9210da4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 17:58:33 +0100 Subject: [PATCH 271/563] made CustomWidthButton's position adaptive to bottom padding of safearea --- lib/presentation/views/main_menu/groups_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 5a7a2eb..4bf9cba 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -103,7 +103,7 @@ class _GroupsViewState extends State { ), Positioned( - bottom: MediaQuery.paddingOf(context).bottom + 15, + bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( text: 'Create Group', sizeRelativeToWidth: 0.90, -- 2.49.1 From 310b9aa43bf356731e3a5188e03c91ee66306c9d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 22:10:02 +0100 Subject: [PATCH 272/563] Implemented StatisticsWidget tile --- .../widgets/tiles/statistics_tile.dart | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 lib/presentation/widgets/tiles/statistics_tile.dart diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart new file mode 100644 index 0000000..0d01159 --- /dev/null +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -0,0 +1,100 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; + +class StatisticsTile extends StatelessWidget { + const StatisticsTile({ + super.key, + required this.icon, + required this.title, + required this.width, + required this.values, + required this.itemCount, + required this.barColor, + }); + + final IconData icon; + final String title; + final double width; + final List<(String, int)> values; + final int itemCount; + final Color barColor; + + @override + Widget build(BuildContext context) { + final maxBarWidth = MediaQuery.of(context).size.width * 0.7; + + return InfoTile( + width: width, + title: title, + icon: icon, + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Visibility( + visible: values.isNotEmpty, + replacement: const Center( + heightFactor: 4, + child: Text('No data available.'), + ), + child: Column( + children: List.generate(min(values.length, itemCount), (index) { + /// The maximum wins among all players + final maxGames = values.isNotEmpty ? values[0].$2 : 0; + + /// Fraction of wins + final double fraction = (maxGames > 0) + ? (values[index].$2 / maxGames) + : 0.0; + + /// Calculated width for current the bar + final double barWidth = maxBarWidth * fraction; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Stack( + children: [ + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barColor, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + values[index].$1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const Spacer(), + Center( + child: Text( + values[index].$2.toString(), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + }), + ), + ), + ), + ); + } +} -- 2.49.1 From b2036e4e6811439da75dc5d0b3a15af2b41fb553 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 22:10:16 +0100 Subject: [PATCH 273/563] Implemented first version of statistics view --- .../views/main_menu/statistics_view.dart | 199 +++++++++++++++++- 1 file changed, 196 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 84ccf77..fc7b262 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,10 +1,203 @@ -import 'package:flutter/cupertino.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/player.dart'; +import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; -class StatisticsView extends StatelessWidget { +class StatisticsView extends StatefulWidget { const StatisticsView({super.key}); + @override + State createState() => _StatisticsViewState(); +} + +class _StatisticsViewState extends State { + late Future> _gamesFuture; + late Future> _playersFuture; + List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 5)); + List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 5)); + + bool isLoading = true; + + @override + void initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _gamesFuture = db.gameDao.getAllGames(); + _playersFuture = db.playerDao.getAllPlayers(); + + Future.wait([_gamesFuture, _playersFuture]).then((results) async { + await Future.delayed(const Duration(milliseconds: 500)); + final games = results[0] as List; + final players = results[1] as List; + winCounts = _calculateWinsForAllPlayers(games, players); + gameCounts = _calculateGameAmountsForAllPlayers(games, players); + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + @override Widget build(BuildContext context) { - return const Center(child: Text('Statistics View')); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: constraints.maxHeight * 0.01), + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins per Player', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 6, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Games per Player', + width: constraints.maxWidth * 0.95, + values: gameCounts, + itemCount: 6, + barColor: Colors.green, + ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), + ], + ), + ), + ), + ); + }, + ); + } + + /// Calculates the number of wins for each player + /// and returns a sorted list of tuples (playerName, winCount) + List<(String, int)> _calculateWinsForAllPlayers( + List games, + List players, + ) { + List<(String, int)> winCounts = []; + + // Getting the winners + for (var game in games) { + final winner = game.winner; + print('Game: ${game.id}, Winner: $winner'); + if (winner != null && winner.isNotEmpty) { + final index = winCounts.indexWhere((entry) => entry.$1 == winner); + if (index != -1) { + final current = winCounts[index].$2; + winCounts[index] = (winner, current + 1); + } else { + winCounts.add((winner, 1)); + } + } + } + + // Adding all players with zero wins + for (var player in players) { + final index = winCounts.indexWhere((entry) => entry.$1 == player.id); + if (index == -1) { + winCounts.add((player.id, 0)); + } + } + + // Replace player IDs with names + for (int i = 0; i < winCounts.length; i++) { + final playerId = winCounts[i].$1; + final player = players.firstWhere( + (p) => p.id == playerId, + orElse: () => Player(id: playerId, name: 'N.a.'), + ); + winCounts[i] = (player.name, winCounts[i].$2); + } + + winCounts.sort((a, b) => b.$2.compareTo(a.$2)); + + return winCounts; + } + + /// Calculates the number of games played for each player + /// and returns a sorted list of tuples (playerName, gameCount) + List<(String, int)> _calculateGameAmountsForAllPlayers( + List games, + List players, + ) { + List<(String, int)> gameCounts = []; + + // Counting games for each player + for (var game in games) { + if (game.group != null) { + final members = game.group!.members.map((p) => p.id).toList(); + for (var playerId in members) { + final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + if (index != -1) { + final current = gameCounts[index].$2; + gameCounts[index] = (playerId, current + 1); + } else { + gameCounts.add((playerId, 1)); + } + } + } + if (game.players != null) { + final members = game.players!.map((p) => p.id).toList(); + for (var playerId in members) { + final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + if (index != -1) { + final current = gameCounts[index].$2; + gameCounts[index] = (playerId, current + 1); + } else { + gameCounts.add((playerId, 1)); + } + } + } + } + + // Adding all players with zero games + for (var player in players) { + final index = gameCounts.indexWhere((entry) => entry.$1 == player.id); + if (index == -1) { + gameCounts.add((player.id, 0)); + } + } + + // Replace player IDs with names + for (int i = 0; i < gameCounts.length; i++) { + final playerId = gameCounts[i].$1; + final player = players.firstWhere( + (p) => p.id == playerId, + orElse: () => Player(id: playerId, name: 'N.a.'), + ); + gameCounts[i] = (player.name, gameCounts[i].$2); + } + + gameCounts.sort((a, b) => b.$2.compareTo(a.$2)); + + return gameCounts; } } -- 2.49.1 From 546a3e37174b4187e4e2d9c48b55e6bbba69f0d4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 22:49:17 +0100 Subject: [PATCH 274/563] implemented feature to automatically add newly created player to selected players --- .../views/main_menu/create_group_view.dart | 85 +++++++++---------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c54369e..75fdb83 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -49,8 +49,7 @@ class _CreateGroupViewState extends State { @override void dispose() { _groupNameController.dispose(); - _searchBarController - .dispose(); // Listener entfernen und Controller aufräumen + _searchBarController.dispose(); super.dispose(); } @@ -123,12 +122,7 @@ class _CreateGroupViewState extends State { .trim() .isNotEmpty, onTrailingButtonPressed: () async { - addNewPlayerFromSearch( - context: context, - searchBarController: _searchBarController, - db: db, - loadPlayerList: loadPlayerList, - ); + addNewPlayerFromSearch(context: context); }, onChanged: (value) { setState(() { @@ -339,48 +333,47 @@ class _CreateGroupViewState extends State { ), ); } -} -/// Adds a new player to the database from the search bar input. -/// Shows a snackbar indicating success or failure. -/// [context] - BuildContext to show the snackbar. -/// [searchBarController] - TextEditingController of the search bar. -/// [db] - AppDatabase instance to interact with the database. -/// [loadPlayerList] - Function to reload the player list after adding. -void addNewPlayerFromSearch({ - required BuildContext context, - required TextEditingController searchBarController, - required AppDatabase db, - required Function loadPlayerList, -}) async { - String playerName = searchBarController.text.trim(); - bool success = await db.playerDao.addPlayer(player: Player(name: playerName)); - if (!context.mounted) return; - if (success) { - loadPlayerList(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Successfully added player $playerName.', - style: const TextStyle(color: Colors.white), + /// Adds a new player to the database from the search bar input. + /// Shows a snackbar indicating success or failure. + /// [context] - BuildContext to show the snackbar. + void addNewPlayerFromSearch({required BuildContext context}) async { + String playerName = _searchBarController.text.trim(); + Player createdPlayer = Player(name: playerName); + bool success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { + selectedPlayers.add(createdPlayer); + allPlayers.add(createdPlayer); + setState(() { + _searchBarController.clear(); + suggestedPlayers = allPlayers.where((player) { + return !selectedPlayers.contains(player); + }).toList(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Successfully added player $playerName.', + style: const TextStyle(color: Colors.white), + ), ), ), - ), - ); - searchBarController.clear(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Could not add player $playerName.', - style: const TextStyle(color: Colors.white), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Could not add player $playerName.', + style: const TextStyle(color: Colors.white), + ), ), ), - ), - ); + ); + } } } -- 2.49.1 From cc04e0555768396560f28b20f108a6a0a5d29d02 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 22:49:28 +0100 Subject: [PATCH 275/563] Adjust bottom padding in GroupsView list based on media query padding --- lib/presentation/views/main_menu/groups_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 4bf9cba..a23a615 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -93,7 +93,9 @@ class _GroupsViewState extends State { itemCount: groups.length + 1, itemBuilder: (BuildContext context, int index) { if (index == groups.length) { - return const SizedBox(height: 60); + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, + ); } return GroupTile(group: groups[index]); }, -- 2.49.1 From 692b412fe2e3d7d727717aa36bd24ad29a5cd349 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 22:52:09 +0100 Subject: [PATCH 276/563] Fix black bars on the screens bottom and top by not wrapping scaffold in safearea, but scaffolds body children --- .../views/main_menu/create_group_view.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 75fdb83..b077bbc 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -66,19 +66,19 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: const Text( - 'Create new group', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + scrolledUnderElevation: 0, + title: const Text( + 'Create new group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), - body: Column( + centerTitle: true, + ), + body: SafeArea( + child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( -- 2.49.1 From c170aa17752f808954b85d6270e33df46d91cad8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 22:55:19 +0100 Subject: [PATCH 277/563] sort groups by creation date in GroupsView --- lib/presentation/views/main_menu/groups_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index a23a615..e9af8b9 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -69,9 +69,9 @@ class _GroupsViewState extends State { } final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List groups = isLoading - ? skeletonData - : (snapshot.data ?? []); + final List groups = + isLoading ? skeletonData : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); return Skeletonizer( effect: PulseEffect( from: Colors.grey[800]!, -- 2.49.1 From 59c041699dc3edd983959f207b33470b9a6673af Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:20:31 +0100 Subject: [PATCH 278/563] Changed values attribute & maxBarWidth --- lib/presentation/widgets/tiles/statistics_tile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 0d01159..279c492 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -17,13 +17,13 @@ class StatisticsTile extends StatelessWidget { final IconData icon; final String title; final double width; - final List<(String, int)> values; + final List<(String, num)> values; final int itemCount; final Color barColor; @override Widget build(BuildContext context) { - final maxBarWidth = MediaQuery.of(context).size.width * 0.7; + final maxBarWidth = MediaQuery.of(context).size.width * 0.65; return InfoTile( width: width, -- 2.49.1 From e60961730f64ec78f2ab17da15297990bbdca4d0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:20:58 +0100 Subject: [PATCH 279/563] Added new metric & changed layout builder of Skeletonizer --- .../views/main_menu/statistics_view.dart | 92 +++++++++++++++---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index fc7b262..2a8dedf 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -16,9 +16,9 @@ class StatisticsView extends StatefulWidget { class _StatisticsViewState extends State { late Future> _gamesFuture; late Future> _playersFuture; - List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 5)); - List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 5)); - + List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); bool isLoading = true; @override @@ -29,11 +29,12 @@ class _StatisticsViewState extends State { _playersFuture = db.playerDao.getAllPlayers(); Future.wait([_gamesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 200)); final games = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(games, players); gameCounts = _calculateGameAmountsForAllPlayers(games, players); + winRates = computeWinRatePercent(wins: winCounts, games: gameCounts); if (mounted) { setState(() { isLoading = false; @@ -46,22 +47,31 @@ class _StatisticsViewState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), - child: SingleChildScrollView( + return SingleChildScrollView( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: SwitchAnimationConfig( + duration: const Duration(milliseconds: 1000), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: + (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + ), child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Column( @@ -78,6 +88,15 @@ class _StatisticsViewState extends State { barColor: Colors.blue, ), SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Winrate per Player', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 6, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, title: 'Games per Player', @@ -86,6 +105,7 @@ class _StatisticsViewState extends State { itemCount: 6, barColor: Colors.green, ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), @@ -200,4 +220,36 @@ class _StatisticsViewState extends State { return gameCounts; } + + // dart + List<(String, double)> computeWinRatePercent({ + required List<(String, int)> wins, // [(name, wins)] + required List<(String, int)> games, // [(name, games)] + }) { + final Map winsMap = {for (var e in wins) e.$1: e.$2}; + final Map gamesMap = {for (var e in games) e.$1: e.$2}; + + final names = {...winsMap.keys, ...gamesMap.keys}; + + final result = names.map((name) { + final int w = winsMap[name] ?? 0; + final int g = gamesMap[name] ?? 0; + final double percent = (g > 0) + ? double.parse(((w / g)).toStringAsFixed(2)) + : 0; + return (name, percent); + }).toList(); + + // Sort the result: first by winrate descending, + // then by wins descending in case of a tie + result.sort((a, b) { + final cmp = b.$2.compareTo(a.$2); + if (cmp != 0) return cmp; + final wa = winsMap[a.$1] ?? 0; + final wb = winsMap[b.$1] ?? 0; + return wb.compareTo(wa); + }); + + return result; + } } -- 2.49.1 From fba35521cbcfddfc401c91c67f344ba4b6203ab5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:23:02 +0100 Subject: [PATCH 280/563] changed skeletonizer transition duration back to normal --- lib/presentation/views/main_menu/statistics_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 2a8dedf..a365b2e 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -57,7 +57,7 @@ class _StatisticsViewState extends State { enabled: isLoading, enableSwitchAnimation: true, switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 1000), + duration: const Duration(milliseconds: 200), switchInCurve: Curves.linear, switchOutCurve: Curves.linear, transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, -- 2.49.1 From feb5fa061557bc8b4fa9d206bde076fc7e559095 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 22 Nov 2025 23:30:24 +0100 Subject: [PATCH 281/563] Docs, small changes --- .../views/main_menu/statistics_view.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index a365b2e..8830118 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -84,7 +84,7 @@ class _StatisticsViewState extends State { title: 'Wins per Player', width: constraints.maxWidth * 0.95, values: winCounts, - itemCount: 6, + itemCount: 3, barColor: Colors.blue, ), SizedBox(height: constraints.maxHeight * 0.02), @@ -93,7 +93,7 @@ class _StatisticsViewState extends State { title: 'Winrate per Player', width: constraints.maxWidth * 0.95, values: winRates, - itemCount: 6, + itemCount: 5, barColor: Colors.orange[700]!, ), SizedBox(height: constraints.maxHeight * 0.02), @@ -102,7 +102,7 @@ class _StatisticsViewState extends State { title: 'Games per Player', width: constraints.maxWidth * 0.95, values: gameCounts, - itemCount: 6, + itemCount: 10, barColor: Colors.green, ), @@ -127,7 +127,6 @@ class _StatisticsViewState extends State { // Getting the winners for (var game in games) { final winner = game.winner; - print('Game: ${game.id}, Winner: $winner'); if (winner != null && winner.isNotEmpty) { final index = winCounts.indexWhere((entry) => entry.$1 == winner); if (index != -1) { @@ -223,17 +222,21 @@ class _StatisticsViewState extends State { // dart List<(String, double)> computeWinRatePercent({ - required List<(String, int)> wins, // [(name, wins)] - required List<(String, int)> games, // [(name, games)] + required List<(String, int)> wins, + required List<(String, int)> games, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; final Map gamesMap = {for (var e in games) e.$1: e.$2}; + // Get all unique player names final names = {...winsMap.keys, ...gamesMap.keys}; + // Calculate win rates final result = names.map((name) { final int w = winsMap[name] ?? 0; final int g = gamesMap[name] ?? 0; + // Calculate percentage and round to 2 decimal places + // Avoid division by zero final double percent = (g > 0) ? double.parse(((w / g)).toStringAsFixed(2)) : 0; -- 2.49.1 From f658a88849caee39d07de784807722905308bd08 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 22 Nov 2025 23:52:00 +0100 Subject: [PATCH 282/563] Implemented displaying real recent games in home view --- .../views/main_menu/home_view.dart | 158 +++++++++++++----- 1 file changed, 115 insertions(+), 43 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 34e4be3..194849e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,9 +1,13 @@ 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/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -17,23 +21,43 @@ class HomeView extends StatefulWidget { class _HomeViewState extends State { late Future _gameCountFuture; late Future _groupCountFuture; + late Future> _recentGamesFuture; bool isLoading = true; + late final List skeletonData = List.filled( + 2, + Game( + name: 'Skeleton Game', + group: Group( + name: 'Skeleton Group', + members: [ + Player(name: 'Skeleton Player 1'), + Player(name: 'Skeleton Player 2'), + ], + ), + winner: + "Winner ID", //TODO: Should be player object, but isnt yet, waiting for pr + ), + ); + @override initState() { super.initState(); final db = Provider.of(context, listen: false); _gameCountFuture = db.gameDao.getGameCount(); _groupCountFuture = db.groupDao.getGroupCount(); + _recentGamesFuture = db.gameDao.getAllGames(); - Future.wait([_gameCountFuture, _groupCountFuture]).then((_) async { - await Future.delayed(const Duration(milliseconds: 50)); - if (mounted) { - setState(() { - isLoading = false; - }); - } - }); + Future.wait([_gameCountFuture, _groupCountFuture, _recentGamesFuture]).then( + (_) async { + await Future.delayed(const Duration(milliseconds: 50)); + if (mounted) { + setState(() { + isLoading = false; + }); + } + }, + ); } @override @@ -48,12 +72,21 @@ class _HomeViewState extends State { ), enabled: isLoading, enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( + switchAnimationConfig: SwitchAnimationConfig( duration: Duration(milliseconds: 200), switchInCurve: Curves.linear, switchOutCurve: Curves.linear, transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + layoutBuilder: + (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, ), child: SingleChildScrollView( child: Column( @@ -97,41 +130,70 @@ class _HomeViewState extends State { ), ], ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: InfoTile( - width: constraints.maxWidth * 0.95, - title: 'Recent Games', - icon: Icons.timer, - content: const Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GameTile( - gameTitle: 'Gamenight', - gameType: 'Cabo', - ruleset: 'Lowest Points', - players: '5 Players', - winner: 'Leonard', + FutureBuilder( + future: _recentGamesFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Group data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No games created yet.', + ), + ); + } + final List games = + isLoading ? skeletonData : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Recent Games', + icon: Icons.timer, + content: Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GameTile( + gameTitle: games[0].name, + gameType: "Gametype", + ruleset: 'Ruleset', + players: _getPlayerText(games[0]), + winner: + 'Leonard', //TODO: Replace Winner with real Winner + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + GameTile( + gameTitle: games[1].name, + gameType: 'Gametype', + ruleset: 'Ruleset', + players: _getPlayerText(games[1]), + winner: + 'Lina', //TODO: Replace Winner with real Winner + ), + SizedBox(height: 8), + ], ), - Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - GameTile( - gameTitle: 'Schoolbreak', - gameType: 'Uno', - ruleset: 'Highest Points', - players: 'The Gang', - winner: 'Lina', - ), - SizedBox(height: 8), - ], + ), ), - ), - ), + ); + }, ), InfoTile( width: constraints.maxWidth * 0.95, @@ -189,4 +251,14 @@ class _HomeViewState extends State { }, ); } + + String _getPlayerText(Game game) { + if (game.group == null) { + return game.players?.map((p) => p.name).join(', ') ?? 'No Players'; + } + if (game.players == null || game.players!.isEmpty) { + return game.group!.name; + } + return '${game.group!.name} + ${game.players!.length}'; + } } -- 2.49.1 From fa841e328ec63a13655e43a22b7c24bf11a1eac1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:11 +0100 Subject: [PATCH 283/563] Altered game class --- lib/data/dto/game.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart index 4188bc4..48ef902 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/game.dart @@ -9,7 +9,7 @@ class Game { final String name; final List? players; final Group? group; - final String? winner; + final Player? winner; Game({ String? id, @@ -17,7 +17,7 @@ class Game { required this.name, this.players, this.group, - this.winner = '', + this.winner, }) : id = id ?? const Uuid().v4(), createdAt = createdAt ?? clock.now(); @@ -37,7 +37,7 @@ class Game { .toList() : null, group = json['group'] != null ? Group.fromJson(json['group']) : null, - winner = json['winner'] ?? ''; + winner = json['winner'] != null ? Player.fromJson(json['winner']) : null; /// Converts the Game instance to a JSON object. Map toJson() => { @@ -46,6 +46,6 @@ class Game { 'name': name, 'players': players?.map((player) => player.toJson()).toList(), 'group': group?.toJson(), - 'winner': winner, + 'winner': winner?.toJson(), }; } -- 2.49.1 From cfed05595ca9cbec81213b8128c52c9d442f923e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:27 +0100 Subject: [PATCH 284/563] Updated methods in gameDao --- lib/data/dao/game_dao.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index a211849..18792b5 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -20,13 +20,16 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { result.map((row) async { final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); + final winner = row.winnerId != null + ? await db.playerDao.getPlayerById(playerId: row.winnerId!) + : null; return Game( id: row.id, name: row.name, group: group, players: players, createdAt: row.createdAt, - winner: row.winnerId, + winner: winner, ); }), ); @@ -45,13 +48,17 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { group = await db.groupGameDao.getGroupOfGame(gameId: gameId); } + Player? winner; + if (result.winnerId != null) { + winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + } return Game( id: result.id, name: result.name, players: players, group: group, - winner: result.winnerId, + winner: winner, createdAt: result.createdAt, ); } @@ -64,7 +71,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: Value(game.winner), + winnerId: Value(game.winner?.id), createdAt: game.createdAt, ), mode: InsertMode.insertOrReplace, @@ -100,7 +107,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { id: game.id, name: game.name, createdAt: game.createdAt, - winnerId: Value(game.winner), + winnerId: Value(game.winner?.id), ), ) .toList(), -- 2.49.1 From 338f4294dc17ee4f2e1288a3bcc07b654ef5a073 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:31 +0100 Subject: [PATCH 285/563] Updated json schema --- assets/schema.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 1883122..c80915c 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -88,13 +88,12 @@ ] }, "winner": { - "type": ["string","null"] + "type": ["object","null"] }, "required": [ "id", "createdAt", - "name", - "winner" + "name" ] } ] -- 2.49.1 From 82b344a145f829e7be92f42f6c05a140975fc79f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:17:48 +0100 Subject: [PATCH 286/563] Changed winner access in statistics view --- lib/presentation/views/main_menu/statistics_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8830118..13e9aae 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -127,13 +127,13 @@ class _StatisticsViewState extends State { // Getting the winners for (var game in games) { final winner = game.winner; - if (winner != null && winner.isNotEmpty) { - final index = winCounts.indexWhere((entry) => entry.$1 == winner); + if (winner != null) { + final index = winCounts.indexWhere((entry) => entry.$1 == winner.id); if (index != -1) { final current = winCounts[index].$2; - winCounts[index] = (winner, current + 1); + winCounts[index] = (winner.id, current + 1); } else { - winCounts.add((winner, 1)); + winCounts.add((winner.id, 1)); } } } -- 2.49.1 From 4ff131770e205f4c112dc703330c7ec69b25ea8d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 00:22:53 +0100 Subject: [PATCH 287/563] Adjust tests --- test/db_tests/game_test.dart | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 0d33c1e..4cf6982 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -50,15 +50,18 @@ void main() { name: 'First Test Game', group: testGroup1, players: [testPlayer4, testPlayer5], + winner: testPlayer4, ); testGame2 = Game( name: 'Second Test Game', group: testGroup2, players: [testPlayer1, testPlayer2, testPlayer3], + winner: testPlayer2, ); testGameOnlyPlayers = Game( name: 'Test Game with Players', players: [testPlayer1, testPlayer2, testPlayer3], + winner: testPlayer3, ); testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); }); @@ -75,9 +78,16 @@ void main() { expect(result.id, testGame1.id); expect(result.name, testGame1.name); - expect(result.winner, testGame1.winner); expect(result.createdAt, testGame1.createdAt); + if (result.winner != null && testGame1.winner != null) { + expect(result.winner!.id, testGame1.winner!.id); + expect(result.winner!.name, testGame1.winner!.name); + expect(result.winner!.createdAt, testGame1.winner!.createdAt); + } else { + expect(result.winner, testGame1.winner); + } + if (result.group != null) { expect(result.group!.members.length, testGroup1.members.length); @@ -123,7 +133,13 @@ void main() { expect(game.id, testGame.id); expect(game.name, testGame.name); expect(game.createdAt, testGame.createdAt); - expect(game.winner, testGame.winner); + if (game.winner != null && testGame.winner != null) { + expect(game.winner!.id, testGame.winner!.id); + expect(game.winner!.name, testGame.winner!.name); + expect(game.winner!.createdAt, testGame.winner!.createdAt); + } else { + expect(game.winner, testGame.winner); + } // Group-Checks if (testGame.group != null) { -- 2.49.1 From fee5c57207f9f1c77039a0111e117698e957f239 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 12:13:30 +0100 Subject: [PATCH 288/563] Added comments for return value -1 --- lib/presentation/views/main_menu/statistics_view.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8830118..58dec3a 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -129,6 +129,7 @@ class _StatisticsViewState extends State { final winner = game.winner; if (winner != null && winner.isNotEmpty) { final index = winCounts.indexWhere((entry) => entry.$1 == winner); + // -1 means winner not found in winCounts if (index != -1) { final current = winCounts[index].$2; winCounts[index] = (winner, current + 1); @@ -141,6 +142,7 @@ class _StatisticsViewState extends State { // Adding all players with zero wins for (var player in players) { final index = winCounts.indexWhere((entry) => entry.$1 == player.id); + // -1 means player not found in winCounts if (index == -1) { winCounts.add((player.id, 0)); } @@ -175,6 +177,7 @@ class _StatisticsViewState extends State { final members = game.group!.members.map((p) => p.id).toList(); for (var playerId in members) { final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + // -1 means player not found in gameCounts if (index != -1) { final current = gameCounts[index].$2; gameCounts[index] = (playerId, current + 1); @@ -187,6 +190,7 @@ class _StatisticsViewState extends State { final members = game.players!.map((p) => p.id).toList(); for (var playerId in members) { final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + // -1 means player not found in gameCounts if (index != -1) { final current = gameCounts[index].$2; gameCounts[index] = (playerId, current + 1); @@ -200,6 +204,7 @@ class _StatisticsViewState extends State { // Adding all players with zero games for (var player in players) { final index = gameCounts.indexWhere((entry) => entry.$1 == player.id); + // -1 means player not found in gameCounts if (index == -1) { gameCounts.add((player.id, 0)); } -- 2.49.1 From d411f58134f256bfbb1e6aed73e10a9bc9ae8840 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 12:18:05 +0100 Subject: [PATCH 289/563] Changed icon for second statistics tile --- lib/presentation/views/main_menu/statistics_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 58dec3a..96e2203 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -89,7 +89,7 @@ class _StatisticsViewState extends State { ), SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( - icon: Icons.casino, + icon: Icons.percent, title: 'Winrate per Player', width: constraints.maxWidth * 0.95, values: winRates, -- 2.49.1 From e9b041e43ac89fdf775e36704fddeac8700b96c7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 12:33:13 +0100 Subject: [PATCH 290/563] Changed double depiction --- lib/presentation/views/main_menu/statistics_view.dart | 3 +-- lib/presentation/widgets/tiles/statistics_tile.dart | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 7a6f861..56bdcf5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -93,7 +93,7 @@ class _StatisticsViewState extends State { title: 'Winrate per Player', width: constraints.maxWidth * 0.95, values: winRates, - itemCount: 5, + itemCount: 115, barColor: Colors.orange[700]!, ), SizedBox(height: constraints.maxHeight * 0.02), @@ -105,7 +105,6 @@ class _StatisticsViewState extends State { itemCount: 10, barColor: Colors.green, ), - SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 279c492..3692167 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -80,7 +80,9 @@ class StatisticsTile extends StatelessWidget { const Spacer(), Center( child: Text( - values[index].$2.toString(), + values[index].$2 <= 1 + ? values[index].$2.toStringAsFixed(2) + : values[index].$2.toString(), textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, -- 2.49.1 From 7cda25a380af58b4714bb537ac59d0ec696d11df Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 12:34:42 +0100 Subject: [PATCH 291/563] Changed item count back to normal --- lib/presentation/views/main_menu/statistics_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 56bdcf5..02eab46 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -93,7 +93,7 @@ class _StatisticsViewState extends State { title: 'Winrate per Player', width: constraints.maxWidth * 0.95, values: winRates, - itemCount: 115, + itemCount: 5, barColor: Colors.orange[700]!, ), SizedBox(height: constraints.maxHeight * 0.02), -- 2.49.1 From 8ae85c925d4795338dba1d3656fb22abd4d56b95 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 14:29:23 +0100 Subject: [PATCH 292/563] Fixed double --- lib/presentation/widgets/tiles/statistics_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 3692167..6e3b9b2 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -80,7 +80,7 @@ class StatisticsTile extends StatelessWidget { const Spacer(), Center( child: Text( - values[index].$2 <= 1 + values[index].$2 <= 1 && values[index].$2 is double ? values[index].$2.toStringAsFixed(2) : values[index].$2.toString(), textAlign: TextAlign.center, -- 2.49.1 From def37aa6402b8054472da849755d3a709034484f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 14:56:53 +0100 Subject: [PATCH 293/563] Refactor Recent Games tile in HomeView - Move FutureBuilder inside InfoTile content - Replace hardcoded winner and game type strings with actual game data - Limit displayed recent games to 2 items and handle cases with fewer games - Update player text generation to show player count instead of names - Remove TopCenteredMessage usage and replace with simple text for empty/error states - Update skeleton data to use Player object for winner --- .../views/main_menu/home_view.dart | 120 ++++++++++-------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 194849e..f0f1483 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -7,7 +7,6 @@ import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.da import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -35,8 +34,7 @@ class _HomeViewState extends State { Player(name: 'Skeleton Player 2'), ], ), - winner: - "Winner ID", //TODO: Should be player object, but isnt yet, waiting for pr + winner: Player(name: 'Skeleton Player 1'), ), ); @@ -73,7 +71,7 @@ class _HomeViewState extends State { enabled: isLoading, enableSwitchAnimation: true, switchAnimationConfig: SwitchAnimationConfig( - duration: Duration(milliseconds: 200), + duration: const Duration(milliseconds: 200), switchInCurve: Curves.linear, switchOutCurve: Curves.linear, transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, @@ -130,70 +128,79 @@ class _HomeViewState extends State { ), ], ), - FutureBuilder( - future: _recentGamesFuture, - builder: (context, snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Group data couldn\'t\nbe loaded.', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No games created yet.', - ), - ); - } - final List games = - isLoading ? skeletonData : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: InfoTile( - width: constraints.maxWidth * 0.95, - title: 'Recent Games', - icon: Icons.timer, - content: Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Column( + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: 'Recent Games', + icon: Icons.timer, + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40.0), + child: FutureBuilder( + future: _recentGamesFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return const Center( + heightFactor: 4, + child: Text('Error while loading recent games.'), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + heightFactor: 4, + child: Text('No recent games available.'), + ); + } + final List games = + (isLoading ? skeletonData : (snapshot.data ?? []) + ..sort( + (a, b) => + b.createdAt.compareTo(a.createdAt), + )) + .take(2) + .toList(); + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ GameTile( gameTitle: games[0].name, - gameType: "Gametype", + gameType: 'Winner', ruleset: 'Ruleset', players: _getPlayerText(games[0]), - winner: - 'Leonard', //TODO: Replace Winner with real Winner + winner: games[0].winner == null + ? 'No winner set.' + : games[0].winner!.name, ), - Padding( + const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - GameTile( - gameTitle: games[1].name, - gameType: 'Gametype', - ruleset: 'Ruleset', - players: _getPlayerText(games[1]), - winner: - 'Lina', //TODO: Replace Winner with real Winner - ), - SizedBox(height: 8), + if (games.length >= 2) ...[ + GameTile( + gameTitle: games[1].name, + gameType: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(games[1]), + winner: games[1].winner == null + ? 'No winner set.' + : games[1].winner!.name, + ), + const SizedBox(height: 8), + ] else ...[ + const Center( + heightFactor: 4, + child: Text('No second game available.'), + ), + ], ], - ), - ), + ); + }, ), - ); - }, + ), + ), ), InfoTile( width: constraints.maxWidth * 0.95, @@ -254,7 +261,8 @@ class _HomeViewState extends State { String _getPlayerText(Game game) { if (game.group == null) { - return game.players?.map((p) => p.name).join(', ') ?? 'No Players'; + final playerCount = game.players?.length ?? 0; + return '$playerCount Player(s)'; } if (game.players == null || game.players!.isEmpty) { return game.group!.name; -- 2.49.1 From 9e8bab1a6055be5732047074fc64e49a20203613 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 15:21:40 +0100 Subject: [PATCH 294/563] add artificial delay to group list loading --- lib/presentation/views/main_menu/groups_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index e9af8b9..7d852bc 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -34,7 +34,10 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = db.groupDao.getAllGroups(); + _allGroupsFuture = Future.delayed( + const Duration(milliseconds: 400), + () => db.groupDao.getAllGroups(), + ); } @override -- 2.49.1 From 26fadf5093ee49ef0575fdfe7bba1d23042137fc Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 15:22:59 +0100 Subject: [PATCH 295/563] add artificial delay to loadPlayerList for skeleton loading --- lib/presentation/views/main_menu/create_group_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index b077bbc..11f241c 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -54,7 +54,11 @@ class _CreateGroupViewState extends State { } void loadPlayerList() { - _allPlayersFuture = db.playerDao.getAllPlayers(); + _allPlayersFuture = Future.delayed( + const Duration(milliseconds: 400), + () => db.playerDao.getAllPlayers(), + ); + suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); -- 2.49.1 From 604a541392c0bfb30b19e4bca62be7268f22ce93 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 17:09:52 +0100 Subject: [PATCH 296/563] set delay in all future builders to 250ms --- lib/presentation/views/main_menu/create_group_view.dart | 2 +- lib/presentation/views/main_menu/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 2 +- lib/presentation/views/main_menu/statistics_view.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 11f241c..59f72ed 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -55,7 +55,7 @@ class _CreateGroupViewState extends State { void loadPlayerList() { _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 400), + const Duration(milliseconds: 250), () => db.playerDao.getAllPlayers(), ); suggestedPlayers = skeletonData; diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7d852bc..aaef1a5 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -35,7 +35,7 @@ class _GroupsViewState extends State { super.initState(); db = Provider.of(context, listen: false); _allGroupsFuture = Future.delayed( - const Duration(milliseconds: 400), + const Duration(milliseconds: 250), () => db.groupDao.getAllGroups(), ); } diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 34e4be3..2230a91 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -27,7 +27,7 @@ class _HomeViewState extends State { _groupCountFuture = db.groupDao.getGroupCount(); Future.wait([_gameCountFuture, _groupCountFuture]).then((_) async { - await Future.delayed(const Duration(milliseconds: 50)); + await Future.delayed(const Duration(milliseconds: 250)); if (mounted) { setState(() { isLoading = false; diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 02eab46..6107586 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -29,7 +29,7 @@ class _StatisticsViewState extends State { _playersFuture = db.playerDao.getAllPlayers(); Future.wait([_gamesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 200)); + await Future.delayed(const Duration(milliseconds: 250)); final games = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(games, players); -- 2.49.1 From 963edaf1d1be0444de244399d7561648b0809510 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 17:51:55 +0100 Subject: [PATCH 297/563] Update game status text and player count label in HomeView --- lib/presentation/views/main_menu/home_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f0f1483..aa21f76 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -171,7 +171,7 @@ class _HomeViewState extends State { ruleset: 'Ruleset', players: _getPlayerText(games[0]), winner: games[0].winner == null - ? 'No winner set.' + ? 'Game in progress...' : games[0].winner!.name, ), const Padding( @@ -185,7 +185,7 @@ class _HomeViewState extends State { ruleset: 'Ruleset', players: _getPlayerText(games[1]), winner: games[1].winner == null - ? 'No winner set.' + ? 'Game in progress...' : games[1].winner!.name, ), const SizedBox(height: 8), @@ -262,7 +262,7 @@ class _HomeViewState extends State { String _getPlayerText(Game game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return '$playerCount Player(s)'; + return '$playerCount Players'; } if (game.players == null || game.players!.isEmpty) { return game.group!.name; -- 2.49.1 From 73c55868746055b3272335a6c28eb2a8c5514c00 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 17:54:33 +0100 Subject: [PATCH 298/563] Added icons for android --- android/app/src/main/ic_launcher-playstore.png | Bin 0 -> 6223 bytes .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++++ .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++++ .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 700 bytes .../res/mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 276 bytes .../main/res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2164 bytes .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 588 bytes .../res/mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 210 bytes .../main/res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1400 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 904 bytes .../res/mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 334 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3060 bytes .../src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 1228 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 430 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 4656 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 1656 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.webp | Bin 0 -> 520 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 6724 bytes .../main/res/values/ic_launcher_background.xml | 4 ++++ 24 files changed, 14 insertions(+) create mode 100644 android/app/src/main/ic_launcher-playstore.png create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/values/ic_launcher_background.xml diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..e7244c6c07cf5749b8e233a7f2291a14fdbbce1f GIT binary patch literal 6223 zcmeI1X;hQh8OLt|BOn3_7LYA~b}}sjR@usups46%W@2@YiY#H#K{^OV1j3S#!39T_ zxEzRxBvgijQ-sK3Si>?7Ngb9TvIi0%Y_cST07*zl8dTcT)35zv51-!q;eVIsdGGx_ z_x-;=p8CdDZ@bxc0D#`f6Gu-2097}kKzp0|v5f0k1HjbyG2`nyII4hvHDN9Q|k3afk2Nw13`ieFoTEr0BT#7)o`yjnfxa)1Lgj zFR#9$;%pN;ZMtp!>T2mqA6FPHtYl9kr1R?#&^{Rxu7m)o3JpN>O-%s4ILw5+c=G?;(9*MU`hRZ_%M^WZM9bD|lZ#G;SnvGmXZ&Mr|( zMfYvypntpgHoPn#lc*jz@Uhp6I4FQxAo155aME|ZzWo9zJF_()dW5im6LhWiCQxg) zH4kY6?H7_l2~)^b^wvbSndG=Qj0e;mpH!HUr=91=h{!Y1BQg$d!(wj=MNluY{G^eh^*;dEug-eZR|3bFik+h)P>-ED3|4 zAQmS!d)b0ABk+f^-+`S+fH#2L^99~JAONE2Z3O{;(gd12L4qdubtl;A4JOn9`)rA} zQn0lPKIwztw5Tj*G4G$zVkt?I3@_M%SrQ_d?96fA0Gow-ZAvU9yc5UxhyZV_FB)bQ zjbB{Djpoalmh55A#+8{%{JUvQ2g?S2Lq=W-I(LQR7DbqSEE4w%tZ0@g2^LxMKsh`$wK83AhKN5|?Jn$69oA9Bw|zN6{7H8dYf$vjwf6VMMjWJeCh`!seJI z5+4~kvNBq;THZ!>AU@|YJ4~`LO`=MybmE7iL8{(IP@U>&^QFS_;)Z8BRTgz>Zk}^_ z06!Z#H`A@?|MDZSc2^DD6C|u7nUnra4cA3``|{Z%2Uk-X;v@9>{;ip7Qh5pzm8)2h zkBLK|;?7E@uggY>sS{wfX%ju6AXJ-}8iZ69W93=qo=Wsp(UdL=AppZ;osWJ)+Qc*&(F_vhyl<7lMSePABI|!!OXD@(w@2p{_%T| z_@1o0_kn9V#GoztR%W&~!TH=JCU0N`*8;` zteBLcN}O}Gi!XIi#Z0cj%PdF>vR61&Wf)34tuO>17)X8w+(hq0qE#&qRxigP$bB2u zfiUwF+aHt16d3UQE@*T*{%h3j#MZo71BN__CxO?DiR@Ts56HR?5xF zJL_6}2)!>fux?V9pIzvfA?e=(9IXg(!-^hPGJ?9{3UqvVb>aMl8!z4K3;~ibmb3v) zJD74-y3k2={6N0T?6i7jQ^Vr=h7V$&f8y7*)k2JUGSLdL{~Ph;@l< z3|1@`!Ll@9LVxvIh9qwUz0URO+dDbS=Sgd_a731_c6lT@eZi&1ZzSFK!&%w>O%>NI z#sd^xw@QQ;_S?$Z-vj=bJy%0I7E_&xs#b`*b&h33dnFFe#!Faenoy+rW!&y2Ss&@) zvQQHeIjoQsg_RT1Yq|9GHId?PmUDz~ezbnR=K%phmR6G2lgkQQH%6apl+6_JCDE$y zEIjbI2<1kJc-H!vK*TU0`B!7$&{TVYf|fAxpf|ny{zEPDcPVszLPd&ao!o%Wgg1ux z=+GZctIeR7Yo@YM_*_b;*cFcOK*UjSSB4w;$EW>Xv?kDTcV$$Xl7pLejJEHfh|yp- z9fy*=#*;EUsEpVEMny;6ZElMqZc(l;Ze<{y@5|;j zUaL+Vln&)MCf<$7QN~f!^{}nPCYe2_LU5I%nMDa_Zh$YMMEgcdjcB9};V_c0^owN) z(jcJWic-CT5WOfFznW!0Sr^nyXHqkf_S`ijqmissI;NA`)N#hH=)4L&Q+rAN5~VUo z;e~b>JDyc4OEEM;N^%*@Mktpu%2ya#EHtJrCUS_t5YO*VmXLU6@t#RyY3uj#H08I4 zmLl{-n%+imgcp6Svd9N6A!$(OFJ&5=Jl$KYPkEcE`YN5=Zj8nnvk+p{m{87h_Mdii z>I)TRH`_Yt<15_Q&p8+D`{oy4Yn1Fto{Ie0cgyVk#LQl`ORv + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..8049fd623b9a7c3441e6926651bf80c20e86a244 GIT binary patch literal 700 zcmV;t0z>^$Nk&Gr0ssJ4MM6+kP&iDd0ssInN5ByfKjgTPBt^>M`R(qJOXt6Io-j@x zIN>&uBt^=*duGtytoL6>oqwm@Mv|mRHM7qW+VB>=|0)@9yi;&%+g4SreR20f+*=^l zBFN~1R;&ghkDvxZ_b;ao03aYBAP@xsARr*%KnVDOfY-PO|GJYGPJYEAtp3i|%g(=?ek-uma=f@vY5P(>PRS1Y2PA^{{G1?uUr)hI- z)$zd(6uPj`;h>$1&N4p?QrB9s&syly%4m<+Ne3he%}NAN%98-fT+|Zfzz{#%X33Z@Tow6 z4vCQFKO literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..a3131a17d34303d3abda58d4d5a158bbbf437bf2 GIT binary patch literal 276 zcmV+v0qg!!Nk&Et0RRA3MM6+kP&iBg0RR9mp+G1QXMw1Vq;&5E{$*eajIcHkM6!+j zC}0#&N`FC&$Eb}YN8X5INgaO(_sn(4<85GsHK=s)ZqnJ<+e_Jw0%}bC(Yp9T^lnmcqsuD?*&GV+z3=8hXRM& aA7Cq4pz}J(>m;C_j(ko7y6ighzd;&|czuQd literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..358841497c4bcbb8692e6c7a010b20ea44b26573 GIT binary patch literal 2164 zcmV-)2#fbpNk&F&2mkQ-z1gR!0YW5~?RnOM4OpZh>hRT3o0Hf<}8 z@oazFwr$(CZTnBbwry()&$j*E+56T6K(xDnVs`;GN81Jy+g(8G0=f$tUB|Ue5jX*a zcX{>G${q<9dQ6aKYx%p-W5TuHKzQH;Mspni$Ue0&scU%l<{m|FD*9W|6h(_=t&z3i zp~qz{?tfj+Y~9_b9{@P6ksKS~0xK$ebEOhrC|V#ZR8llk>M8O0S8oE?j#1nIAp6w% z%atpNmQsq6N-+^kGD1{oN$T@`-|F`QV?H8%`t1Oq3#hIk8~~{^Owo^uLh%J=VZ;`D z0g-M^A9@bz^Nk3t;I_s~hvbQ-hJfL07;O&2|MhPZBBKnKAZ`!kK9 zL1QRsedqLX;OGRV&MQ42sYA1E^m!xrX?7pS<9R;>?5-z+{)Q>u4{fSZ#8A-zNyngv;+k#!TC90F*u z86jygTIR8MJ5zE}4e=0!InXJtp#9K1Xk{eSNj8EL=6a#wGs?`w@Vt>X1XLFQY$ykz zLXwA;nJnHaDcA>Mzy@%k<;ZYZ@zG3p`+!n&ng~@v2XLTm+)bIeBo(2ATFDX50dSKY zt8Svnn5dlAbwBBXZ4=Nkhd(2bY=pKWnWqEl!e|(UHZp$+n6_=h>|lpt686*7^7B6` z-hN2ATlY~dfAc=|9=#&#bif$>c(Y2%cn|*XiD)>!W5J<|Vf zF^0ukC54+nJOlw0?Xj6yRzz^t%a53$U8VAXHMb2bve`z|TJ+83^LDA)gGRc971(Th zVX(i|HSE+LTL`fdEpu6l(l%D@u;vaB*svqSggOQ0Ygs8=9-4t|!_W|rVUkc(X|8^} zaVj+rgo16`1pGS1Uy{Pi#bo8o_@6+aZG%g|X=QGkE>R=>(Z?=;^UP^ghp5RU6&VRd zdpPrq1K<+S3$H$rqG5#XMAqqux-d>?5Eb50Idlzk{z(DY$t9s_rskq$kc_`OYTtiG z^*fJHulw)?S=$LU(MaAnl|L7>N!kEV6?6hYEmAL$8Kl+ce-yd8;cJa)zgMWSM78c8 z2n44902~5trP;S&KZE?Isq>#TYO=~S<&-t=d;Byo!n%M$z*qv>r&kKmGLL8^Z`@Tp zJ9PWP4ub+<5(qu3aub<(Bo$ePGF1=n?Fl({0qyZarjvS5-z=i~c-w?+*D&kxs-Wit zn}9_ix{>*e%q$G6cKsv}a}lVf2m~Cs=GmDE)HlcbyiLLmYhL{B31Fzf>R=OyZIC<{ z6*1+RLrQr)%5;Bo)9PS@#sZ+dyUei+YL`?}%M7BCiqgnbE_5uz-`yQ_EwDS--{kFn zEImP1VSeLDNi7T|zpZzc#J(4uzgeUIE6&VKcoLYLQQD1Yk#8GpB6>FamU za^{PVVCygc&{Fu~iu6AlBn1<)@p_c*{+U3_e?4vRJ%NysICniSNx@iH#@`){-hShp z*Pl^u^YuRoyyTqMUx=%o`}zx+|8}4-8jaumAg#K#bA)sniiT_k3erFd))( z!6gv2{P{Amc~zyNFf=sd=Ok52z*GWJ-$CRnu@58=zso+ZXq`w!VMb!SX(G4Yr?mtU zay=cq#W@BOXtw{HHn&VkE~UJLQ4%qJ>8{7T{lkSgg?ON6a%Ko2&|>?2T-8J?a8SY+ zi9z(7q;kuBTzmNp5Sj#ilXGDhf#%DcFH;&;Pt8n_Ri=!Px;K?anEpuLBzXT>f6ks!w^K&wy`}puaaa1QCcM(CO$kpFX#$Je0tt zl=pr#-p}QsgmbFO51-RH0-Xmv=s`5SslXi#VhFUE1;!Td{;E}ipm0e=O0QIci%P46 zqSar|e7=n9`A=^TqD(-a7QzTLA<%K-^LcssuJ8ZHxS~>0k$OLp_fza8p8Nl&8?Jd} z-)pCZuzw)>Uhgp=>T&{I)>!kR!c`B>PV(=(hI3hkq=NMm5}8V+QqT5d3FQ2mPD-y= zR$uete=i@{+XF)q(58dH(IDpXzT?60gVubw^z84?tjcbikf!vI6s}5pq$}+b&Z^2T zGyA)5uj_mHKY!i=hP`Db2$&e6o+Qxr)-jG{@J9Q2)v^0@vFIEhSMUD!v{d4GNp92h zk`|SU=bNUNG)i($PbCg4Uc=+#=Wy|%F^*+0fi6E@d(%u1KtKaSC&vcssxcsnK>Tk6 z+VvRi*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@U-2eayAPHoE1ds$$Kuf&_0uWxmXQWaAG4QNJU=2uvNd!s-vI(R@ zDNrC4N`=xvWgB{~YH3`%|M*kWPc0SV*T3~rDSG+)^&d4?HGX~mFSbDk*8yact?RYx zrLlE2SI_IccmJGVMsv3kycnbR{*x1d4P zP#Uua7Q2CYz6Lel&4`eKh%q6#A1tzu?%K#X%Am>c|Bu;=dQ9YOU&dmw*ja>}y$qV( z`ny@ZsDq%xbM(|f&PfK1yZg$@a@AtJ a#JrxX00012MM6+kP&iD!0000lYrq;1_n@F{BgelNe-cl!G!(RL4X8ezHQmMgTeo MXhJ!asU7T literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..0d0468ecc8eae0b6ededfdd8ee53faf2ddad7fa4 GIT binary patch literal 1400 zcmV-;1&8`lNk&F+1pok7MM6+kP&iCu1pojqFTe{B*XOFXZB<$O3QqqvBp``?y@%d= zHl9IpB*~2=c@?nyzxs=@imVo#9|4e%BimN3tiA93ifjI1kOV0(XXbv-*-&g-Teh70 zejo1c?heW6ZkYj)83cnNQOP9t=I-wBegFSI>i_@(0uBf`pg%x75D*Y>!2b+@2LuG< zkk8+OcL5}g4qJJvgk)(lKLWc2&6afdNtB40aNCE-^asn`5hCxQ+ z5P*OLj4vaJ#1k-HDIUSA7?N$L6(QY84n-%(cY+g0h$J8hj7UYM3SupsfDo(jF5(oi z3le;q{_l_2g%c3kpKqK%f)jiS0zO5YhJ?NrK1KWa!oa6Nh*fwmVl8eTuUtP~A;G8d zX%J!+2(gRpxX6A?4fWk_d3P3+%k8BuHfs}1lri@E_vg#{{=PVuz8NEns|B<5*@Vd^1=)XC*GKF#gRbUK0dXX1W3vD_m)wcbn5v~n>sRG^xdPFBF;t$n|0 zRzQR@g0IGbPp>T0%k6)j8g%GO?H9FkRFL^yLxI|VL@`jMB3YEA7Eq!Jsbmqp)QYKO z!a3;x$`=a3oYNV3&L+h%4VuM;DoA6b2}wo@@I**M0Kr4uKI$s%L<))O)t&_c(xD;d z46k^I(^lqm?frH7^8aHcow4*-wcK}a(zP{iug`Cuw0k6eXcyH>;_E#AU4K9335>ih=&NZKmyZ zQtw7=dl1{1Z_S>)4rAL+>QmchQY&@4YumPB?UmH+u45;^F0Y9GPXIzo-kHOfcjgi? z3kLU3UAAxU@)O)l#32}fZ9RMS8!&Ltpn(JW_3F6|027gQ27c<+XRxB6gZp&*0q7i! zur``#bXS0f;z0Kyq;nKuPScRh01v@|EsBgq zky?gmEWiT^ptsUCMQ&>vmjVwUficR~6r;B3%M_#^!zQIQ#j1x^e-7r;85@R>O+go* z>v{T|0A#E0|3GIC0gNI;fls^4^MZvz$k0b=2p7XfrLFj^p=*8z10e9@NXk)slvAJV zh5#6TR+^GPLsvfs1__KOlMF+zry)6hsU)2Y?M(XzyfZ#i7A1p*t$r4rIldq}DR%ku zDUQ@ve#6&$u8o0S%>aLW<9E_|p6AYtPW^g4@YuJ0XI|!c;j9=wyb<_!M3ScO0scYw^J9 zt$B(uzjfk&E%r%2h-~WzCpuNpRXG#yZcEtpb7j-u*GZ$G^9ShHFsL@i5onzrA+V$~Bbz4`w8 zZ@&EGsulY?G_6y*1~CejZP>I`$D;=i95{HiW2>eOvrHgnS@?5jwobhU4eHg&?(AHU G2n_&;$GCI= literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391482be68e9e4a07fab769b5de337d16eb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy!iOI#yLg7ec#$`gxH85~pclTsBt za}(23gHjVyDhp4h+5i=O3-AeX1=1l$e`s#|#^}+&7(N@w0CIr{$Oe+Uk^K-ZP~83C zcc@hG6rikF&NPT(23>y!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..8aa87cc45b48978136c981269ed293717498fb24 GIT binary patch literal 904 zcmV;319$vVNk&G10{{S5MM6+kP&iC<0{{RoU%(d-U*#y0ZC9CdFFCEi0UD5xCssdk z?;)6Q9obfcocq9>wJ!Vb!LJ;FcVS1ittwjQoQu1qKt2Rc7W})-+{^~1%wl#5MlR{3GgEi z2web@4B)M^JDplxmH2<#`qAp^Oy8)HK4*4qVFM%6EBYOXCF~QFmphAMHrm_3@CGh(S@IQ^)BEiBDLI5AAZkJ3L@hkd zr&bn~Kqd_jBitY1l1!2*OZ-Q;B=ha&vdeb%ZO;2?3&yl>;GJP_w4mkTzl<-^W09SxF8=v z-^m-kXC=$%%d9RbMbRs2S}P%iAN1xGmLjQX)dF39NbR9^me07(G1|fF7?$=urTC zvf&RT^S6xwA2%XYf5bpfKg8%5$n5wX?+NTg1C7JRss1QzOoasp4nudA)8rus$dgpI z-GLsQrvgz0l!&O@1sWnE7g|PYsQW9(QYp7kF_&aqYsF;Qd9f7#{ZL*Z7TQ_=F8GcJ zPeiY)%dNzrmPc+zPgr7+pI5cyc2w0UPX(+ueT=#wW`B!0w_#3cCW3jRTh7LOwZ%AO zCicUP&1+UHH%e=>+PnVApANAk+F`SP;)^dozAH}r(uexbQb;RpUKC87BkZXNTJL1$!VV# z0002~kd_HhEMmbIPH|W7XjQz1791*CFs(?ce|k!F;7iOuPcvQ_t41llp`WUKl>PWR zM%=&3sf;Rr)&6I{dYJ8=^*zsTUO!vU`@oeX$)|MJ*h&+kn{|0jTv|H=R4 z|6k6XA3*)??i&>Wtc>?wF2M79FT-Wzzkd+;VGdnx@4meufYs*S8wI#^@AbHp{Pz`u zTet}{ZtuQIKr5?z&w~l&y%*sYp57!{r{wYkHnFeMj%X?$S6Am*}xqD<*WsQh5+O|!-X+B%Q7w1!K+g5Gcwr$(CZU2gG+ji2v z_r5&=aF^p0pfd%~(o+Dv+;rN+O?nDo*e z`|n;Ysk#T>=T{3Z^yq2;Fl|>~whI75&u@%HGiA^^UzJq0r1~WVQGqc*K`CSGiIU2C z$&N$MZ;S!Jwe{n=0L35_qY0ApV@b7e#t0EKDa1rPo=6CaG7KY;h{xjzLI`8K&9cI8 z09;oet_vWJNhPiGUP+a5#z-Pz2ue#-dqyHb2xI9LcLi`=?U^=!IA*n!`GTanIrD^7 z{|%3X-7_jY!e;-@ZvbplV>W)`wTrU~k7(ShV5~IWjX(!%4cOoc`aWl&gdjSsB& zHw>an!(b)qdutFwEJ8L2SN9TeRSJ)LM5kVy6|YL|ay`kwj))E2K`5$ZUP*LZ$qIGK z7$5;V0xr0s`pPSbt}Dp^_5H@c1UGDH072d3aZg{KsELq5e)%DVbhun7iFb0tBRcm) zMU`mu=-LZzm|{?xIfgTXggvxO7@<;BcYgNeUkcm)l9(-gK5%G{DfDM2sU~07NZ{RKxIV)h>#Ga z3O%b{BA}V3Iva{QQ#kP=(d6-4sHu&F#|so%bJy=Ko4A1 zw79aL2Lu;HRAf<%cB37qYFx-4i%L{oETkxW_&iT{kGrk5u(L9omZ-BlAow7HA}ia+ zSE;SYhHl*zqUvBiPJEUdciv$vHqaWFN?cl~c5#E?gh-0|L(BLubrqzc-oGnBNEfmI zF9GS1D#^N@WRNb;rURm}oOpN%DJ}1>(4ah>+U1z0Q>Y)ZP(l#Akkud1*1SMKH%+-T zv^0i-YG6KYeBL{1x@LC;j0|+S7|mZakrNL$NuHS0%%NTQ$&z!Of$=5g6WkD^I7aiX z+m0)fhGPP=M5p7(C!Le*0{{PIX@E36JqU_) z4pCKfeTT^&zJ!XebbS8{x(Gw!cb>pgc+;f#V+#H!vx7A6|eYna_`mZ{Nw@ma-b2#Jc%5(Tc zWp1^t_8a1Ak<0I!hurnXVpS5i;HrD2?Lu=n%bgcYi{fe$;3(jos%lRUnrJ;>0HmEG zf~-L0;HjFliwh10hR&}6Ob3CePx%MKlhw5I7lF*l0vdps1q=j&-tiqQS)w9eKk2Ba z2!!7@0br%!ZBx6M=iw(QDeXXoQL7yvz7mu{z?q=?G)4^k7`#u6>vvxij0~(YQvhSR zE6(9wsyd0Ui-OB*4ETQm#RJYSP;zq*H9qgIyU#gC;BqUKF;fWi`^V|}m~Y~H&oKVk zBPbsK>@ixu`ijzJf*R7SDi?uCYRNKi9bhAnc1f-=U3B~M8(&d5`}0&RUKnGX%A-xU z0on2S&HnvZ-RCeOC@pS6O3U+NTx%{Xe}793IC3Kc9Rc_Btab&uj`+CodAp?X6b!eI zd|M7q32X!go#Q8@5;K;liHDC{^%jAAH7GX=SO_GyzwNUv;^8I9b7EY1F3W$iO?kf_ zeqd1N>p~KWNxW2(W=ZM(!FKq!%Y(B4Z=QPb5lc{%hm+(QeKl@f?fg7jfx`KhJWl_Pti=;|*!n#L#-E}};O?8AY2W&~%!fC4x05YiOyk>Cn*Z20#nK}6L+=0G4r zpsrtjRTM*1Bo-`Dk)Il6)Q4ZqngP}Hw7^4Pctr5*lvG(lLdcaa_h|C;Ek-@wg|g94 z_R#D5KM>OP&3BA`ya#3XT9=UA-^Uo^bRQ}jJbH!V>pRF;t6L3|n!|zbS5F24st%kJ zffRvx*4F1{Cj|3l$iT~M^!olEJ-;77arVyxl&%v9DZ0AB+;xiQUZ0TAIwR-jA?g(G zJmCBiMAqjWtBCX8jwLgn2&7R6XX+?mfIxG*(T=~H8!|fHh4+ea{n5_8{>b?&y(0I`atNQokzcFSSYiBx@7nbft~}?lbQcg|O4i(Lo=9Vh`Xx>8Rv{ zq$oo#yR0$)*xk~04_@#8R)&TTXzD0nfIx}Bb-Zpr-pzeW50=lLG;+7!2s~c&mlTCa z(&df>as(;_&ZCU>ymt(~dPqTg*6O@xf?IFx6#|#XK6NVTlTS5P9bzwjIj8&#_2=c`*j=&R8@Zn@rEEQGDuf$s-FP+hw?=iK^B?-)(1YKA>`1n_>w_hWCLCPP7oPHLLEgMp%I^AW)Lu=^Hz z$+4`z&FBv&>B?*yxA#h@J~19u93!N_xGb|uGzwzOmK{^=5DGm>b7ahx*>y38mj@6!xpZZh3lii|R(Yn&u zF>dYD>G;Fl+f*9O29@mbyZ+p%+S411cIskdS-tdJo4@w^^Ta+oJcWP;I;K$sYy^T0 z4gux|1~mc=0-M)vHv+hf0PY_<@V*5d4I)o^ct||eVIA|h&%lBAE%0MsciqO-)T*bk+||^O5B;4h5`I>Np7K(M%?jiAEiVQqU}v CIniPO literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..71193874ff1e2cd9bb09b8af3de77c7e3ae27d1c GIT binary patch literal 1228 zcmV;-1T*_mNk&G*1ONb6MM6+kP&iDu1ONapkH8}kcLzC=Bt=qWHSHd={<__p8M-Tv zWq{Z=k|Rk~J#*gVAKWEj-p*|o#72@7Nm(^`@c;hX_Es=Y7ZlspmM!Obe|PtvDH`;`n?U5Uh~k{TdtOp;mhg zs}(lI4hyl+L?+Q;Pxr(45uS~5A#B2qOA0_DiI84ol>`Y%gpDMC08mgw5}7>JvhGqn z?!pOW4rC)4BxDoS0D%Mo0U!{}fNUfo2}uNj1Sfyfh0~`QP5KU@12t~wmCr=zVcy0+q zqz;jQ^yrcO`?7Nko}1PYCq?D}438byiBPi~QUalP zJhd0IIG8(e%_WHv$%~f{?ln1}JNfL!QeG1A@|nG<9Ms(NBV<7$k9v4-GAyak9vGs_RlMyS0(aWx-ic9 z2VIsM%8n;LwEcX&C6Ukhu7k-FIe+>?n|QI=Q25dtcQxN$oX9!9f5)lSVw`inKIbV0ZT~t{w8v#_@{7Xc)95iWl=3WTbPVV0#x`e+_B!bND6F zWcxmYM~xqA@Z&}g>D#tcZ1G?(yG#NVSKqKQG5PXWtW~GB!DGz|1@f6?!hBs7iKwi) z`GyuQT_i3afC72pQ0At}WRM9r@4)cLf<=oNJ}V?l>g}e=NSX;34{!gV;1J7c=$6Hq qzYm(KERknS*j?Q{JWVe{H%+A6E*O&>k}-t~06Aw29hd=#e<}r4S5sX8 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..d62b6730ab7352635ef1ebc231be8c96592e690f GIT binary patch literal 430 zcmV;f0a5-^Nk&Gd0RRA3MM6+kP&iDP0RR9mL%~oG|G_AdBq_PK3j9A%fguzSG=~XB zZ6rDJ#(N)z!*u+4q-n-&+XkZ3-%WK(-2RVSBT3l?09YnTZM*+}&`Ydcvl}HqfcyO+ z2u_-bI?(kVFGWaImRuns1(Pe3R$>H|2AA?#p!Q$ME@s1(f{;6{ZIeX|MWlo&rTZt*;IJuH=Z7wRtB)Od3MY) zkh5pU1Y`2-=wbBeKl+dU`-No&eA1ru^TSci$d|m&j$Jc@ShhYnEIEiw@X4{tV20>F z`j5p)&Ii&%xAye#7BlkM(bJ=zK`hvx99kA46Mc4MFkSQ?{r4}*rht*!*T?7|C}KDt z1FZ#7GxjYo7W-5iqPuzwRICF~bE*CMI4_qU>j&ZufJ#I?irZ9Jdbw6d+8kkFhfN)+ YC&JPptD^tufBK*Pr~m1H`v2c@06aL?9smFU literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..ab5d33715c7cf2e26c09cc29374e35444ad5d9cb GIT binary patch literal 4656 zcmV-063^{YNk&E}5&!^KMM6+kP&iB*5&!@%kH8}k61{P;S}1mTV7d}T?X5sLwO>R z11p8LZD6IY0=ANaH@8!{DOeXM`Nc}1RT#FeQqO@())t(ojyS2MTWQ}|fxlaof+R_b zB+35&q+@1g=DTI;3G;}otU93}Ns=N-vj0D3;h343nU{Gt-90igtLuaUwr$7T>{w&3 zLG3nnGTYvM+qP}nwr#WKY+JP*e7Gn`k{d^oA_*gy05P|u-6yacz10vVG=vOiLr^Hm z>C{0!&)N`_2CGixPe%T8aq3{K8LY}bgYRmbUhxm;H9_m=;H29I1HzO=(`E`*-~7+ zjH^xCDcr@BB#VVunHEhlC2=w(D~aPwrr20c;p%oRJKlX0%d!S1)B6qBcmZDHOyEPM}R(tSF*?H{XwmiF^)b7UAlDgpkW5&@5A|h|uPk zSA3n!o;d^!$RST+8r*3H%NlfyfyiXjCCw4SmFyBUrr0J3H8-=Y zfibQQQ+MNzA%rY~Mr9IFyI<*;VOm-C2Q5|4(bO1RZ6+>RHBLjmRIw~xdK{s~oWKBa zE`&B`*;bl<^0L@;DVEd|uD}ox<{c-p8ht7guNX=DY+MbZ#twloa=6fcf+pv%tW%RS z8mMPUTn3p^^&Ef+8XI9?8-Dkb8dE=`@MwWyGBpu{9lyB${JVxVXlTCwZCLTrTGBQ~ zb%m7HmA#XlZKaXC{&Weh_9DEL78og6DUE{lzA9^#vjz=>r0mB_?ebvi3MsYMG$Gno zD^^nW;i?B=rL-V0cLjEOtC}#)MFU%Jx)_hz5VsebE~hoMvc4~#W#W$A@?i85 z>!`ii%MKcUP$yfBAJv#zOWa_%P7wT}4e#ZKjgTwK{4*?MZ1-z>9X=^2+5QT0}fPAQGWV7T83L$@97d;_0jmvC#^xq?I|I* zCs5G2Kc(^8&{y?mTg$~->1dM0^~gnEl>6~}3Viwj`9FCd3i|y)^etD2re>!FfI|Hq zgIy9oMrV`fzyGrO%U4;*unrf0@V>uCMZs`kfkP{ly!z%6Pdn*pd;oqC?6#;vHa024yvx{5ODezh#8 z-SgR#B24uo4lX#+Jsw!?PENa1jk;M@`VhGUxDZ?cG3CuyW~&-^{8LlnkW9(2;Xt0T zwC?mg>Q|g*S?L$#5@17cMV3$g(K{*cb;|QmxBwqQxJOJbvx@5GG)tpDkW7g%5))Zw zQmJ=R+`Up$5OTnX1JzDgeR@8%&1#mE-Y0e#5i9|U;+?N34RP8lrt*nr9-MGuwPbMq zwJPkZfP%s+!Cc>Q2eJroB30l@^oz+e^{BZls8qi(?EsY0(k3L+BBThGX64Kd09w0F zb=`9`3yBU`aUl1YH73i2*7gg;(bVCr4{Nh;6tKYC!4>fMNg7mhD) z_CM)1tahEMvqyP85(Rh>&=#wt)~GuEH?y948pXhjrLH1(*PEH-=0??5ko#sMoIWaXPI$*|usw5-tgoyb@^4lKfTof(93?Jtiv-UjjW)12q*w(f^{|Q#B5Ql)0`BlBdfd~>{4|?x#^+Nicdx_adp1 z)cBtjzV~lZ*jighzoky6$G`uv_UkWj z6(!|-_jLlFKZ0QhmQUvF`MH*a*Ml-Cp!Ay>bSwM)H(W(gKKF0JG-85d3M7>P^j(yh z3Y13%#_Ppfs8ja4uc3<1z5ieuxA4VgZUbOC)UL?uAp?MNI53Ckz3T&rrIJGffg|$( zMQ+S|3FJuBNl~UG*x^vQr2QdKn%PAO0IK&*mq}Ic`T>>W{-^IFU{l7pWZ5HABccO# z&P7!^qc2az$yoB(K};pq52&6bU$KwrMkQ|0F1e2grc&z%RF8#MoR^u}*MnM_#r6?h zU!Z!DCZUUVfn$d-HNJj8wK(zoeN5Lr6;#T6@UTy&hvB~_)e^3#Vn&{~Ycd(HUdt&} zJ?jU9Dkgo8-bYxOQ0>E^FhreLKcGyfeQI!Prvjxi0Bpf(&-%f8s@T*a0J++SfI<=T zP@n~14JfUJA5NkunA!@!ajsbr`&6I>km;*`MH#RY6PayhWmI!zo(g&Z6W!ytp@+9z zxcbbmQg5&;@dD5Ru$1fEk8XjTXrGv+reA1E1^{!8>GKfm&tV={a;=1So|o&|knkMP zB!1wWGq4&7(E&G7C8J7l@_E-M6BD`K%;r-E0ll78!u$|00Y-;n*SS6J_@m!Y6+Z@xsH_us_l-VLaC z*68q#wQi1TOM-o7&;YPJXCG0C>JvF;5%BCGYrb*~ia(_i-+Y0zoqTkTB~D32OLm+>Q(57FFN)GDN}*bOon|p(4+z=@r$>M zx4;XxMUFN10dS^XX(t7Max%ztmFW30EKof!a`Z>8iNTslKw1Eh_3pKnOKYSsg#!vG zp38NmpS_QBB8UnpW^M0a`;~`YE8YSt(UI$RZ39sL2d$YLyKQ2yy4Ib#uURyt@NqRd zyl?7R*92g*5ohw_s9=nk1S0;bL*(Fu6AE^^$Azg8BZ7%It2&Mi4gkf);*Xliln5of z#Vcm<>F@3s1G2PcRoF9c1CaNEwU*V5Q^)}$=c3}B^y9C6`WJ=)i~P2#BLLmdkPV=$ z^we+7VjzUKbXtB@<@R?aDV$X`j|x@*1+PB#sdcsRvBB`+Kqb4xryu)*3r7N*%}xRR zkwZc*fQlkrKL^LeKnHL6+%i-Do|Ac`o&v1dI2c&>e9Cn$_fcb_12!C}NPGOD*LlXC zgM!USbW_;?7)F5nb8^QY2gJlc25)hXS!m7g&KV90c2Vk9J@}G6$AZZ>K5?pW+ zMebDRs4#QSLBOs&S@qyuhJ%8%#n-&QQyf(AaTOEZyw_7{$~&cUYR^yzz*DTN;bvJP zKm?Czq0>q;HvsTPrz+Pe4H*h@wtwt(uBs7_^Bg>ILV>Qtyw`cg_KH)eZnicM2wV^uB+S_1<_9YLWMYw+VXj82K+Y+gmR~ zjSPVx+VX{UUpKgdlR38t6sYKIS_^Uo-=5&};1(_l^8W8X>OQ%Lv+u~>TMKCb`MkWkuG@s2uH5&+v^ zP_gk6JzuDmfGOF*e56VaFWcc=YrV1YNuf!Ko_bb9A5Z}xu=?bCkC+&SLhe?q=4e_65a%0uafyYI7${ z3p}I4Ky>$e+p04niY4odjMDmgt899?PGf0sRR4CcqWH$%xW_a4|85-b#O+J zG^yQlWXeh3nGojZ?;PpmVsfm0%;RIjqVEg_&XgnNP4q0=E&v=SZG3jlnfsB2u{S(p zR_i+S-^FHU>5|z7z!;(1c-Vc+T&L$|_ZSN!f6#L+H99Q2*7l*#%j}JH5$QHF z%hI?Kvjznh0KXf=R+(8-J*G)C&p4OEm0USyiJ1*4^vi79B?UmyK;Q=u{ND1bsy}xIQyw_Hi!ak*@l|z0otL>`gR=vBrtG40gZ9*R zWGH~bfxrhK;0BR*O+R07YSb$xoZ}hsqoLegqiKlT|vtRp^6<0Tv>u|Frn%8t@IipO6rB=7hbgt8ryLI_O$D~a4t7?6xwgHg+s{tVp z<_6I>?Da^Fo+m#kQCJ)rOn8R{Q`CrAXI0xP&;589fZ&0@%4(zV9jd`)Ob-Q6&^x$$ zg<$gF=VkT+M}JYEGh91Cz_h^en(A=j3Xhn4UBmA``m4dt%iQGly@Th%&~zhtXl4We zUFy|@=?0ORuYKyG$NatURMI_0z?8^I6|@^XF$a0Y6q-sdy!y}SuYH=_y*f2Xd4@(! zZDPuWJ%Vch2;Jib(T8xJzxe9E3#G;mI3t`GP4nzHiK1qQf~m2^R{t|E=Y?VSB(%LS zB~h#FYTN{1$=dT913>t)uvxEl`ux*hWS?rSm{3=NDWaq4GWc`3Xmoq!nE!up`pX%w zeMZ=22lWn~JF=RpbTvB?fT82nck~9{Zuf)@05jjP){`ss+)yMLJ0L)TqAlFHyxKKO zGzyG~ESik9e$BC;>K|r>-P5h>9aneUg=8ipOaMv%n)E0Dxxe~G!ab+CL42<3Jn!9m zy|?=G_99b>>M} zW-7Q0ORm17?SK&M6~f73ZZPQ~92dQ7t+!TKvpH|4t6YaPQ!|9(EEWqVRnjfo{|L!{ zfEbG<4k7+__m~PD_FHjP(}fS+$0cqsIV`!+$J^dnb&d4NU-#>u+#JjZMVNLq9aKM0~6zsZBkC%6F_;}ght6k$qlAWb)Ba^ z<6Jkr?%dDKfAnXIt@YQ+U2E4%?anzlns;Vv;ZAqaPG9k!10|DzVx7Jso$mZot-0Dq zvZZ#f(zSMpwf@@o_2+&zYx@Vo^n1ciOKz0VHf_&SQvg_wG%97xOaLfs832q;Q%*e= zKxvQQ?i&ILeM1O9NZxwSWuNIj=ylE?5tfa6ohv80&edPJwAFXJyTOzn-Xre?5NemO z#WxQ-x>!z}vNL+pb-|*&{g9wmCM-Nl8a4C@CpJYqVt&z2r66T8VLT=?R(TvfDM?%K`vIngcEX literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..ae5a179a7dea0e707310319706535417ab8c0c91 GIT binary patch literal 1656 zcmV-;28a1lNk&F+1^@t8MM6+kP&iCu1^@srzrZgL)rQ)(Z5aCh&r{ckP?)p(G+sUKJHZT zqfe}N2wx9u0{|@Z|4X&qId*OC3<3Zw)4$AZqriCEW`X`s0ROhluOYsM`WpIcSB&FV zM!f;!RYqA7M=Rby@G8vAGwaJ~#`z7MdH#eu{&2|5T=B?#4H+|wX^vh0XYJ(5PQHk$ z#y&CFiQeId?L_-uZlc5-$HYdNXIy_WzB>O;>;yk35;(p2e(rbXQBtGKR&~r6V<#q& zn5@(!fmWi_>rG=!syfa*N+@QYFvd>wB2ldb(r6{BNc5)panC#;I40x8x@GJ{9dAly zz3^ofugx~2V~k1K%0Vz&Sjh*PN1kzdlJi+tn4PdlK zqoP0A$v0-MeDuQCdT`TsR_mu>Xxs-4sB~q)j5j58Jb&NJvcH6cr@EQStr3YyNUz|vEa#9xwp0Dc8 zm}YlRgrjh)YYwpYcarTBgUKd}jP%X(J;oDf3To$h;PtPkCjfs>Cq*O%kw8+O?J*v^ zQxqNRNgx4)+d66Ta^z0Q2#NgSn>*?9a`>*&#rt~|38*}fZkT4s%c1vK<_2mOD3F#5$Y!&N z(caemX!zWv!eMw@<@?d#z6!bRfhp35|853#k6j^@0|5LE_N1Vg+YDYVz480*N40r~ zdjc+OF@x*ek7{Ed^F$o$U+2SrXL5~Yk`NXk#0m&Hx){Chp48das3$OV*k%O;_at2; z44=!aJxOPg2f4&}_-~e}J`HhC#D#5caGgXnysu*6cwnh~#t^9o`OMlCLAh|l7J~}e zY^4py?@L6J@8cm1I~*(>s?BwoY3*y=mtYZ_^LGVxj!mVJj};(#J!y9IJeJG_1bwWG zP-7ma-#0_`sh1G;u_A>3(evz#9sN*&NR4v01493*X}U7vWY4mR-%TF^x%N!=xA(=6$L;f zYaszU;PuMM=QR8{z=mPk2`S>pfVMO%%xp zM53fq+%*e(Jx6DCeH=%8646RnuZlbHziRfym#Rot)qWroL9zE>4^EkVtK@Yf3ao0t zVs%`A4+y(**4{Hc_@ashy&#Z-Am}YfRWWw@?)xBY;cB>+Yl;M5+;A;?Kp==X``*_d z>cJZ|SX3+)twiZN{(=R?qDb&o6}QjA@%O$1A}3;C=K@ACLG1$`SOTZj2>>C}h}>hh zt}A=JAFF?qB2`7xPT=?=K{~}J30|$@{$99nw+}n*)}y%qoaRS9ASkfvy1*8=tw7Ln zgij-K8u7cnXaAYI?O(au=bLBy|6q^)kzS^UdyEeMa4*w?vkbTQ{J&!N&!;{6!H)kq zIE@6o9N~Wew>fTCF-brG3UI)@QC*6FHHid~(}-FZB`)*WAlxFH$D?b5DEtiWNK9{R?6JKe~15j(FlrMowDXn?nY6;}oT?7E^ Czc)?* literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..85dcbb73f50e37f8c5dc54eaba500b6fe6acbe45 GIT binary patch literal 520 zcmWIYbaP{1Vqge&bqWXzu<-f9$iSe#{$RF1{-G7QDxUB1Bo6;K)tGiZ=*C=Omc<^E zM4leHJA2FAdRdFND<-PAcbP@ScqjebKdoh__VtN5v#)K<{eRa)PPlsKt-9;g?^%o! zewP-OwQ+7*c5aGMkk^)Jg;%&r3_fjl|7xOj=|yGetCfeOw=Ll2U}-dH`f$91kylND zjY09_5-F3#4W!;5>HUAa-|g4FdAvs!x-1E@kGZse zk;w{|uHQFO@=xsiV8?&$|KsA{pWp78eY}&6W0K13ZRa-U-rkn`w%+yEXVdwA{apU5 zI~@4`!Tt~b|HEk1i+2|mEX%4IDPUuZ?W<_@KHJpP9~^?dL5z+t1!HFP(HgX2yC4{RL_JZ1z6q{MY>d zf&CBuf4|SEho7`~crEQn-SH`tE6yLR^;Z8W^gZ?E(H-kO^_if?t>40Bd;Z<6N1`XB zFMauyW3YI-MZ9sv3D3j4s#TU|5~VA5E_<|Vlg~Px{0*%ue}2vRa7E?rl$eP-AGIxC z^-THcYR|&e>)tMV@=A|IInF2Skl5B8m)C8*G_|bod1Zp?8cU8J^8ecZKdeWi{>z^L E08)Y(Pyhe` literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..7da3e2afb5a3f852ab740e6d50cda6d6efc9ba9d GIT binary patch literal 6724 zcmV-K8oT9ENk&FI8UO%SMM6+kP&iC48UO$6uwEGcz;e zGBY#tFf;QkHFpm-<1jZ2b2AilW#$>MJ9P5o$vm0o{4GI7bQ@yyIw};)marx&Ld>k( z@s8axhb)c3jEoh_uC!5wlE|u%e7yTg+t&yp3{?E!LE?V9BZ#bGh9SbrBh2 zS(_1ZYsg!9IbP=oELndI*+L4EY@4>N>VK1-^=;esKXOYmspmjC#Z@eBGt+E9nBI!<&5AvdVXO{1<(c7a(c}CaXIwMve;~FckF7 zL!h)6s8nr+A|a3<3WyK$cdzkA z@4V2s^j=Z5 z6-a7`i?`h1o)~!M_Z5-}L^UB1R>bzraFwBOaC3qEaa;Ng)=fq})Wyu~G?+;oAya>7w`h z*8lVZxSOYMZ_M{!s5DF9N^56KsgmyPwd#eQH40Pz^cjNoAG6`lA z3xx2y-)&VD9b3C&P_cTRU+%%GyjMO1PXPZ@$r z6$C4)EW#l?89F>zC0C`~ABTHJB^iXR1QIOu zViO2Yf6u=lm&Z_DqDhB5WYeVrNuP4oS$|@hb}{yp=Bu>j0M79`FFWI zjk-48@E~+ZAnp`35B<@nQq-kNlixoo9Tazpx`!4T0F6kxC2E=`Zgn0h;m{N@c^lVZpa< zL73rW=@aWfr;mMmmP=Oh7z(9d53iHK3^PlsU^1Nk^7DCGzEl~i7`eYI64m%2>_M~O zT0uYS=e+3{YCJ+!Y4T>3wy0DC&+cUD7lI)<^s{T`?{uWXbyaRT|56WB65^7U!`~Jz zc&U%vt&;f~fj0c%-uETJC24d;KUp%rGQc70WsHLtb;l=dhmS3pJMQa&GduO*pu98IuzBL*`GYf=ZoQ))Ebxm=^$RL}#qEV>{X0U(=i>|KSJWV=D>LX1) z1DFUjo{Kfn`+EL1^VqM)0}Dr*bWJJ;Z|I=xl6T!$;Flkm?dwme{q-kE9&>#AIgS7R z1O6DuYBZ6kt(XbuoWvK{BHkJ^^NE0NGeL*yN_AF`3#v!&1B}b|ChPlJRH?Q zON!S*K!YcplcCAyzrU&U+B1-&V~sC9M0QV~4mFdjYPm{!9sMc;Av_{WQEGhg0kV7g zuyt&vt~uHfBQ>p#Vk?k7V;W_VWy3j~Z$;D2(vmJ_3XpbSAL9TItfisM%=RJE#iXS9 zWW=J95Sm#4!30RmcvfhF}Z1bQm zsL0e8C?%dAYQYAhn>~>i->V~;CKprt+52Dy3pUt{dN$RnPpdCRqCQjZocYpkXaOCT zW7cr~kJiJ-G&X2UjJnD21PD3M!8zx3D4U+ImelMvA)p-~J;dBJK=$ToQrQUTo7|Gl zbNd!4keZsBO27TQftl?b@D3${ZNZfe;9NM%}0$CqS3_3h@tm;QhfOVG@YV5qr+CE2d+ncwOMAp=Gj=bSc0 zs>XT5zxGlM1X_lsLz|$P1tYAvj!jwDLBkKL#J^-jK+j)#22TMqys!!=-&oqrmk#wQxo*&MH61z*PXSIW^=--}S6#1id@*>g4UGMze4x_0YO3POz``Sj zAGS@9cIHWcpoJz-#5i3=uT3F;2sjH52{@GT?*}NYt=}!}aQHAT~NCF#lfHu(*Zj$BseHWbC+GO? z3(7osl+yR_gG5aB=pkx<^$A~2A9Ue4Mdw`O62m@0*V;hZ4z*I?Zp%G;oQ|Yol1C3x z|Ic5b5#~BJrK^rvZ;P0#18oEh8_O4^;;i@kPfYgY5jv8KI=}t^jmT&uQMTqih!_QQt%X-b@})+wk|&UuIn{iKJ=3gWm?Rx5kc3XaQqvI+ z+N&Kg-+_R^BJb#IFVfm>yO? zNt7&*WT2&HA|JfdRllZ*fbUS2sF@^?#Gs|7BRTZBx+q_Oh!;>6NyqagA!sc%67ivX zbx`gJ42T(jRTN)p04*~mO$XRBgP7fbo^tqgga+7^pnYympQ_!{K9iL1LChiM_H8Ep z1nujVX_9|CTSp`@Hv+oKn*~>YjrV}%W2)I-uM_;0*A03^tffT2x~W|X*w^hoHs!nD zAmFIHGSDGJkAPJp>nTvtX*-muDvZ~!MS#O|Wnh$9Pd*PBpwhsmRMp+gKleC@*a`v5 zxsz>B3sTI>9d^zWfm;!x-Z}r8AYd_*G7laRq=e69Gj^}_kvrthR87bZ!3Y8KiZzEq zCkIYASUXV<+iP0lWr(>!Fhsy?jGC!DsTnfhgmF%nwCs28p4rz8MueEI1@sXx-380n z&;9}P07_8Rs(&}Y5vI60Ffcjf1g$O~bP+H?z`miJ4SBW_nCL7`Bqm9fBj6Hs*`Q~V z6JdB{1Z?L`j=@s^lm%@H&v|X@M>Y^+yE-sMz-C5eK0G1^N_dhwd=8s2(wicF$VQ06 zvsLEdXZLt%Cj&|tIAbC|=-DGPuL(>%4q{e{+{4@Kq&4?j82Pv0=*yRT67jpvX&s=e_%HNN-|pa1`ceC14~(uD}PW)}hj zH8G~pm5nu%O;~|8H2m{dCVu!Jq;BmTGko$6KRgZb7ak|6?F z;bdq-t*<_Yd)Ti6%ng`R zT%j*8ibue0KIIAgD*&RiJY@0eA_P3+pH)~9SFm0_SjSeHbBXSV9QfeEYbA`Z*0$In ztH&W0b?C}r``Kq&6T2Gbfe+?mDtFiK_*~t>t{5ys)nbvG484O%1Oo1JICoeEc<8hX z*c7SC&WY#^Obysx{8oSEU>j1>HNBY(WIzYwoH1e0UduzS4D9qChXkly1As-?wLo~? z_>(ga2$1}UQ<=)oD}~sw{=pL0Re~)7##aOm(NiXSgUq1AJgdVPbDoWU^%UV=qV45@ z4Fb*w?A57}-2@r1q5YVusg~W&-7ofXz*b~0!Qo!Ptb1@kz-wmZW)R;|=s<>UlerTP zSN$~k`5pv!T9U0T_p-q*b;Q}j8{10AfDEl}N=J2|UQ<#p3+!`k*j}=&wP&zEzzG4L z8dW(#rvMl_%ltNldzB&JmwsWZ#~~q@`3k`i0gthIO=<@jaG}*rn{cpw&R^p4!3lE< zk?rM|6pa-f3aNlcxn z53*)Gvp(#ChtEx09At6b~A=)lD6Hskhul(6z0V4ca} z@L*HUZuZ$}4yQUO4?yU&9VV}?yP0$K$-!n(x5SMRFv{Ekcp%_gJiW87%@NNA9=IdU zkYd=ij${o8o|(D$&ZNqUKEN@jR|uWH@}kox=s<&RljRC`zwZSib9x6S1+GqRvMB>! zH*1hJN6qEPgBZY|)B4&}cI5k7b86P>1}cf_WS%|tUchttiK|c5vB}ec1x`Ct!l-jy zS-xlRvJ*XXNHx(%_69Bpco$+vOq;xGcM2Uy&{^hlDAMywzZ@~E^a`$#dfYRI^khhh z7y!Hy4z`X_v)Pi5k)&b$KYn5Ir;kF8(hu&Z?r%RK#P|PysPg)=kfhdEpMazG*PlX? zvddf}aUi?d_NNa8hj1 z^xwY_!W%bc`s{s5-@6ZTf24JO{Sh1u{``%JA3gxNk9*JX@jFPO9e=o9$8Yt?jM-L? zNJ`BG zz2q_Nhi|apuRkF2OIh&e@0sqyHz9FvQ@{Tjvwr<4{nMUVB zj>t`yF!EeC+WvI6;2x1es7a-vOJV<%<-l1TMf;yQ`ap-RetWt5OuE(W!xHt9X=Gmj2c{PSKLi4= z8`@Q@`m%rx&4*<*rmc@ge;}zz6T>T)nDP{+@y7DCpc_ zlf{d73vT>e{XY2UA@M{e>3R?_iO3(29n^L=XZNq%C7|=z@p=}4?2+og) za(PU0>0|O(6$sd--ecR{u48b;YQb`}Em{^YUR}Mjbqv3SOtmYl9!!(PuvxS1(fvSB z+g+VKutNIpF#~KREicMhIv4FFxYM>Lh_+{2i91NWxiR{iy4>8{y~D9p)S|H&vvD&pFfblt!8NVwCC0wzvt^~I9I1G z{d0sa5tgXOb(h&NC?ms`=m&zk^v@ArtSf8ggg-h3z0&eA(`a6y|ZYeNXmR98dH0OfUY4p?tp7eEmQUTftuNBhAur` zuW0w*y{|PZ?)^R^)tD$QW!Nm+p5G1l_6?z3LFl!dt7GBre9XnsLG3hUim|(ku5F)P z&es=&{dB%PTW=F4C5B>a1YA}*W0$TVeE78vRO%KTFThqYlvmA*Pu?XOZ0#f7E@xaN zWNQWfs?-=(MSN^>ZxGx?v&Xf@lHt;)+GuuQ(PWl zQfGwbMjBJ^;-u%(?CBiiiq3wq-b3aw)J(Isq0IeEIOnU~oMV#ad>h|IsxdM*6qghl zYJ)O+>)7NTAt*SmD~KAV*PcnMt1i;XqNU(PEWS1uDcQ4XkhS)A1u=2K|DPPYb+*!= zmLO$*U7Iy3vj742EKIzCefAiu=Z%Wh!lyrmR!b>Gr`5KZ!=_v!9Hsj9^_{Di=6om3 ziY~~EvWmGRg<9k$+C!4NhQPicJkGm^wZ=}^on5se?|cl4mO?8_o8w(-tTl6Yz19S8 zTvChV5PM=SaSw{Q^a@P8i{2ol2MCMv?(^k2>u^;iRjU;1{t>p&QDYU=s4e{i1=5vTE9`?qa*3X{dsGlu8G9))|;eTFiy|RgU!9++RoL(tBG4Cc==UQ(<2g7tOgHm zMS~%-h$zTU&gua|lFq5|dav_bJr1(>s?lrSFW&QOl`hemi$7f(VToZl9X^PDyKr$f zhQZZ3&F4@fp<0!wMEBRPKG5E4uH#%iH}q=Gne<#)$@vAMD`C=V5pN7x;x3-E2MBF1 z6fS$8Y0p1+uYF_Je0H`9RV`n$&}p(iqlfE z`|G&rXM^?Hzw2{MzN~$wdoiVb%NBRBM2In-ctx*8t|HK$o|>H1qUAD4aT@Q21MD+x zCv%TF`G_?yj6Y{?vF^|1le%h&c(K>(tcz*qBjINggcEgbKIV@)oT-k@;#;mcr6aD8 z)RpM|{65Dy`Htrv{U2}Z)x;$&)ABj9l2g;|fr?zB72Os#mp%%!$6^8k{?C=POq@pJ z^}cO?oXL;9>wQi==-z9G=yhPExv!0zdR919TCDqB$?mU{R{btl^{=9yL9!};@xjAId1jSk>|epiU-+y-L7B$KlOY1<4k#5uUDgqTPEqb zmdBJ>%paw9*+f?~T&QvO$OvP4YR)PNT|>}w^$KC_le&j+%!45?0AcO*ckF;{0mS6)go5 zW)Bq#@%DlctdzBUi}yR{&-TX=H6UkHQZEp(qDD-tc-Kjf`IJ9<&i8Avd=^#qjQq)A}e9}TixjWTDA-hZjVK9aLbnOx6!RGpOAGI z$vM&av0?W3!0ad`rV#l-AQ)VFWt1W^Gs12xhz-e4jb0)rIXQV1EU->FOGKyUhr|{b z?Gc%giYTStWgu3-AmB=&R(&o4#)|C3z|8mv1Z?egqtO@^hWYIFts~+y0~50qDY<%U zD6U4JfJ4BQa8{Sqs#oUbrbI<4a1j-ilAEj4TdgjfS6Cxb0A$4-_1J7SgTWAr3xmO6 av)Metv*NtqDhb=+Lyd(A3w%FH*5m-*`x?Li literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..78587a1 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #E6F1E4 + \ No newline at end of file -- 2.49.1 From 7881e61a2ee562822604cfbb928845365a1385f3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 17:57:20 +0100 Subject: [PATCH 299/563] Updated android icon --- .../app/src/main/ic_launcher-playstore.png | Bin 6223 -> 8160 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 700 -> 828 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 276 -> 448 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2164 -> 2346 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 588 -> 656 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 210 -> 348 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1400 -> 1546 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 904 -> 1082 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 334 -> 508 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3060 -> 3276 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 1228 -> 1456 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 430 -> 704 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 4656 -> 5036 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 1656 -> 1982 bytes .../ic_launcher_foreground.webp | Bin 520 -> 824 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 6724 -> 7024 bytes 16 files changed, 0 insertions(+), 0 deletions(-) diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index e7244c6c07cf5749b8e233a7f2291a14fdbbce1f..a18a1cd6aad41e029f362c6632ef15a7dc081199 100644 GIT binary patch literal 8160 zcmeHM`CC)h);<9$gEFXBnF$mNT0}r(lCcFvilCO(0T4tKRD^&GfiMILBwB}H)q-Ha ziWRGbP#J8At;zI1Y$q~7$9Lt$eerdinrhMe1Aai^YBY@emH0Ewcowgde?f_ zrGtUKni>Wg003zE?eqB_0F=N-B|vQr__vtaF9rZ;g`dwiKct4ujEIQXz7gJ>1i4N7 zlFzs6?D7j<+{|;d|3$}+zF!lk_2|v_aQ{EP#_64-wfBF$&4J*v)hy8`*jrV$mX=s0 zV@z2swSGMjdZ(_EU%8~s4Q~#U4B+{7c>W+gEM0;pN;x9{(Epw&RzLur!V>}jn>GNz zL9^9|e>Mo@GhMzVblk%x=8L% zym1Lk?SOQO5J3rNe!#&L#mZ$@fb59*i2!_l2~?VJG>K&w@qv^}me&Zh()U9*b{Os6 zd8M@8-q?Xdt7K*=bDU3~aMum1Hf1C+HCgk@_oxP9};F|H2^9QXsf6ew7g+eb`9c!zMBKMOldTK%0lKlhRC@tz~a znzqgmpsY=-O+#_GjgI?^Y7WFieu`HuhfUl-=v6<`F z-NcB!&eOkKiXfE3r*P2<1K68x7Q>3FVMWKEp=bzGm4MxdSt>Ozh}f9a#1uhYPNS=- zq!IGi=^B#XlFi$V=V&nEc&BFNL}yvu8FuyJ>v0e+kt2{~2HH^(cHAVe`BXxTxi}nR zo7@@S_S!wf&|oCM`82n>s~@AC-7&TnldPk&djrt>D#)sI8tcYo<_$rrkHpxQH?iH? zPC$C43f*6c(xy*mGlvxX3S>L007+Yb*BbyGG?0Bw#Vc3|7}EiIuLJx10WTxa=ncSG zb>N~m2&a;%H3Udd119$Y_rC&6wIG18dUmY}@E2&{>SC*yt-8iPD4YtOT0>*Cxbg*T zwLG_ubu|EwEJYp(r}525z8LzxJ>%248$m6@>Q0VyV+C3z;C^V>?SpL#TSm!^6C zFr|ZEG67X5cqYX(%v&n^sn?&5l znjuyiTzouhX8I|wJnwx#gsEB4q}v#B9p9`|7r!1w#FrTo+z5C z(X?OWiCb%UHo!x0cXaP-so-11o%+xB@Yc!3&ESEN)1S$Q%v;#J_tdOpf39k-c%t^! z%2Ac+BXhE9W@Sr=Ze3QnHgnr_-HH7lDK5)@@?_bbCbV6jZ!N8sdC|``4{75jTg$S4%D;%cGQ|4 z9bns?RI1eM3$H}eAmANHOGZkj%1Wl1kb@S>; zhBt82Z}o#!l)!>z)iwUNY7*>tCUJwgUEHzp=?rH&LVs*;m84l;k0jHz^K{(J;4!DVTmCiN=P={s!BH`{pEF zV!}Bg_w*4oTF!gS8$ul+Hz^qX!AOsgl{EE057NuH7q&nrkRz#{8+wTI)1wdGD4ax# zdk+k|7&WF|XgFzFRhpHYc2vA?&-`|Eo4GzEyM>78$Ap^@5j>0uH)jgoIHi&)m&Y{Ihpa8zI*ZnPZU(K)T4G@ zj>afoFkJ}Ijri}n8rf``;xQ*o z-7-^@M;rHx#@}wr$7d+UMxAWL(LTZd=S^UU3T?5zQrsBCnY*9-{3mtRB~Oph;`L|M zS5h|03OHO9zCKVPeu3-!Wwg}31BE}nbc=Qc>_kq13~L6w++_r*eji@4TqYPk*kP0* zcRe5u)w4@O+g{Kp^QlR}In4AKsfEV(Oji%WlrtWvc&$_Z* zRC%Ys&?!BsDD7FlEmy1`(T<6F-Ok`VKyT9xRExwI3x%J)uw$}LTID|9 zzgxh@!}1NePqTXIgAd6m8i{!Ko!eXBS)4T6U7bu9%l%ElyS5*CG2t9G0it!M1}1#( zb(2s1IqC1#z*;0x4N-$X;yU@m$>NLE!umnngqP})_2kGmXK7E6nS?h|EizV{Nh{to z^$;HJ4=z$`bUEWcy7HwMk-|q(1mqTdv>p2Zvi=2YNpy| zuJpEM%gP;Z0N$?$PU`^rI>1@A?49b_^Wg92Jyg8XRJ`;*+p2WeP3i8szb$71Ik*N= z0G4McVCxNFD+bv5Ia>UHo4&x!Z&t9{0=zT_UVe71)ooWn{uqp_F0~p!|Eb0Q|8!wH zak&h-3(z{R^fn4iaA3XP96wN~s-6AETJ71`d0FDnhp8il=#4hV7zjo^0unz~%DOKZ zqULI?p}|NxN+;&>J5lfPZm){p9EQcF*@xClKcbC`9ZG0p!zyhR4huhTi=cm_bb@Fz zy=nGeK-o4(LCzjsqm3Z{$}gX0j(6rir3Vmx%h0f7xHuU}pJj%`48z!dXtZZ##v_Lc zT&ZNip4E@vp$UqTy@=_<=9XL?!Lf$)qe+}f+QNy+&DJGvBrxQ=o1iKerR8tq?%}pD z)Q3A3TPL=G<%5!EN^h@8rZ6yKly6UZojyS*UF)4lqE4X5JFt3tjq8JAG6@b5JwjgF z#-;lI4Ah#i&VtEo*)h6+zx1(Y_sK}`$DMbb_b5*%vdh??Ov_( z^NNV8iq40BxaKcZxw(S!z&(e@ILC}?M~1A#l|1WvCC)u7#C#1Yc1&NAH>k+;DN;a7 zHx4@~P&xF$wkAd9=BpgsX@yvxj#a%EJD;GM@7XMv`z;^?qz}*5B~*OdG-#I}kv;Mv zY;6ZhRyb{U;=t|!_*_Gn4M-^-4TZHDzD=K9kl*jAJZ`}CQcSNkk}E2>r~j> zkTbhGY#2Cxh8{zh--LeUJIx5`qz%PIQuQOr@VM45&r65Jw90zKQW-aoiBH|R{|?fN zGbbx<(oCm7Hws+aIhBvuag^CIXx#{fXm{>qV1!`GHy^Bz;wT>px->%sXZ1e2QJ?OA zdsm0Ci^Z}C{!x*3iRJY8#u*k3z5`1g*axrzd=jpxbtuoWqO}@rKAw6WtZSTRpC2cG z!E~u`Fb5S?{i723hxoB)m&rxmzV%^S?>j8KGbaV%cW_%Gr_1{4<<;{`teaiG3TE{d z6`HTl(ug7{G9B$m2$mK8$dV8;Z9wrKi;25fl~!(_Fe+b`>CaFu)`HwzpHb_=5O;+j zDyXqr%djKbP~$53Ae{6F&OAeT$V6$=^PwXPWZQ_Iw`h$^6{5-MMpq&fyvi!j;DCO2 zP^F3JcufvQqLVsI>iv%*Ud*v(tRDy4e`yUVxWv9WzvoC9RN!%LDM$AbM(O()Qp_vS zT9QA^ow@djTqL!$!6D2trkW=5|3qtNRTr@6=eI2D{i!UO04;`is`1`A4o=lc>vM0uAt^8>$j)h-B3JXCyP`Y+z|8se-mk5kswd3N-W z$9y-QOj=+|gcg4u*EU{%%_TZc$PXgwEsht*#)>9Kcx+eL;>q-Cn2K$4F@4ai(`Z2A zjtW3R#hfdtDqed>Ws_JQugxN9?5oQNxKf6ZPIyfMG=bC~Pmd&^`OwN($|+k=-hrBg zW`#Qo7*AW2^GMcJQJE#mr`GnA8qZ2bj<;Y=ilcWHv9+i#ldVXDNA9fefYzbu$4*hY zQ?QQr;71o;mW8rt$h|6FsIX9eM#J=wKl$Cbg&;1m(MuE0{;59$jg%f5N0jYx^fZ%z z!;^Zp?GIdeVi0$hl79rNYtcPI(A^Z}*j&s%KgvGweq_ ziIIy_gzYm-4z@)4m%7>9$8GOXYY65?uBN| zw=N5F{FiCm9LhOD&gCBx*Pjox>kC-Wx_{{pwrZh8&xj`|xy3CHtOPw_>-hGH_U4xN zlz4hM0eyqme1FN@GMzGjgL~W+!IwOtYv?6|ZDI$GvuGff5V#;PqrS>bT0Wr*oI5h=rJ0y0M>aAnngSVIo+yPTcM-_`%CK`{ zdMOojh0w%C(Q<3G+_zMTr*5$rFzld2e<)~&>vgl;{3pM&uHefyv?4|d$;O!N9IT3B z3qgWD*+p~&FR|*Q6xG0m!_lyjB$3x&*il5_y4h~noz1Pu<4&X&40BUh>cU{oZQRuI zv`rh$9w*%5gsWrRmku@vuEikW`hl_!ez=Lh1lYN{AkvXmWJ1IH_7VgUHqT~zF45|d q%dH@gt)UJ84+~Zw{-Geq!mDlx8xCkxgo2ZMz;92WPt~_4e)>1NGopt8 literal 6223 zcmeI1X;hQh8OLt|BOn3_7LYA~b}}sjR@usups46%W@2@YiY#H#K{^OV1j3S#!39T_ zxEzRxBvgijQ-sK3Si>?7Ngb9TvIi0%Y_cST07*zl8dTcT)35zv51-!q;eVIsdGGx_ z_x-;=p8CdDZ@bxc0D#`f6Gu-2097}kKzp0|v5f0k1HjbyG2`nyII4hvHDN9Q|k3afk2Nw13`ieFoTEr0BT#7)o`yjnfxa)1Lgj zFR#9$;%pN;ZMtp!>T2mqA6FPHtYl9kr1R?#&^{Rxu7m)o3JpN>O-%s4ILw5+c=G?;(9*MU`hRZ_%M^WZM9bD|lZ#G;SnvGmXZ&Mr|( zMfYvypntpgHoPn#lc*jz@Uhp6I4FQxAo155aME|ZzWo9zJF_()dW5im6LhWiCQxg) zH4kY6?H7_l2~)^b^wvbSndG=Qj0e;mpH!HUr=91=h{!Y1BQg$d!(wj=MNluY{G^eh^*;dEug-eZR|3bFik+h)P>-ED3|4 zAQmS!d)b0ABk+f^-+`S+fH#2L^99~JAONE2Z3O{;(gd12L4qdubtl;A4JOn9`)rA} zQn0lPKIwztw5Tj*G4G$zVkt?I3@_M%SrQ_d?96fA0Gow-ZAvU9yc5UxhyZV_FB)bQ zjbB{Djpoalmh55A#+8{%{JUvQ2g?S2Lq=W-I(LQR7DbqSEE4w%tZ0@g2^LxMKsh`$wK83AhKN5|?Jn$69oA9Bw|zN6{7H8dYf$vjwf6VMMjWJeCh`!seJI z5+4~kvNBq;THZ!>AU@|YJ4~`LO`=MybmE7iL8{(IP@U>&^QFS_;)Z8BRTgz>Zk}^_ z06!Z#H`A@?|MDZSc2^DD6C|u7nUnra4cA3``|{Z%2Uk-X;v@9>{;ip7Qh5pzm8)2h zkBLK|;?7E@uggY>sS{wfX%ju6AXJ-}8iZ69W93=qo=Wsp(UdL=AppZ;osWJ)+Qc*&(F_vhyl<7lMSePABI|!!OXD@(w@2p{_%T| z_@1o0_kn9V#GoztR%W&~!TH=JCU0N`*8;` zteBLcN}O}Gi!XIi#Z0cj%PdF>vR61&Wf)34tuO>17)X8w+(hq0qE#&qRxigP$bB2u zfiUwF+aHt16d3UQE@*T*{%h3j#MZo71BN__CxO?DiR@Ts56HR?5xF zJL_6}2)!>fux?V9pIzvfA?e=(9IXg(!-^hPGJ?9{3UqvVb>aMl8!z4K3;~ibmb3v) zJD74-y3k2={6N0T?6i7jQ^Vr=h7V$&f8y7*)k2JUGSLdL{~Ph;@l< z3|1@`!Ll@9LVxvIh9qwUz0URO+dDbS=Sgd_a731_c6lT@eZi&1ZzSFK!&%w>O%>NI z#sd^xw@QQ;_S?$Z-vj=bJy%0I7E_&xs#b`*b&h33dnFFe#!Faenoy+rW!&y2Ss&@) zvQQHeIjoQsg_RT1Yq|9GHId?PmUDz~ezbnR=K%phmR6G2lgkQQH%6apl+6_JCDE$y zEIjbI2<1kJc-H!vK*TU0`B!7$&{TVYf|fAxpf|ny{zEPDcPVszLPd&ao!o%Wgg1ux z=+GZctIeR7Yo@YM_*_b;*cFcOK*UjSSB4w;$EW>Xv?kDTcV$$Xl7pLejJEHfh|yp- z9fy*=#*;EUsEpVEMny;6ZElMqZc(l;Ze<{y@5|;j zUaL+Vln&)MCf<$7QN~f!^{}nPCYe2_LU5I%nMDa_Zh$YMMEgcdjcB9};V_c0^owN) z(jcJWic-CT5WOfFznW!0Sr^nyXHqkf_S`ijqmissI;NA`)N#hH=)4L&Q+rAN5~VUo z;e~b>JDyc4OEEM;N^%*@Mktpu%2ya#EHtJrCUS_t5YO*VmXLU6@t#RyY3uj#H08I4 zmLl{-n%+imgcp6Svd9N6A!$(OFJ&5=Jl$KYPkEcE`YN5=Zj8nnvk+p{m{87h_Mdii z>I)TRH`_Yt<15_Q&p8+D`{oy4Yn1Fto{Ie0cgyVk#LQl`ORv^y)+cws@_aPHuOGH3_BSzjmdqJvgZP~J)_xJyY z5jotQ1g;$jKmis7+(w?{pQ9aU;PAcy00IagfB<@c1`t310VJRW)PVr1v2Q>I%6)U| zv-2Jxti}rUhf~E0hye<~!V0j3q7;^bWLeD_LC&zk*;+0x^YK>?lf(P>)6Air-lilV zB62W+rDF{L`z}h1VZWk$dalad?c07G%L)uTFbFv#$Qj?VJxt@V4uqnLf&`YQ*zxi4 z_lnx(^*a^i@%(Z|{c^4?2KEl?>%m{`ZIq?H>!J*0__QU+(leVR1%6UxJC;)q z&>6==20nn_X*yQt`BZ@BRK`eO=l^bX>EN0M+CT(y2cwpNs9cLCORe?1(`mtabMLpl z5)d&tHh~5EdAohO3fZ!4o3^vAZQHhOE4FnR+qQEN+xgIiTWiKodyN^<{|OL(-(#Xk z@-jp_qA-GUOHZG_cxJW7Zb`Uk1Gw0i2}rabuRe%qtr+KCCnS-?dtb*oQQ~bvQXQRsM7-2mUp@aXRN_+`{0%e#?rnCZK8O?zIven6nZ?ctHE-^h@nVD#b)xSaC=royprsARA01Pk#3%$Rv&zU&)X#T-{>oM<%W%VH_ G{-y$;Ig}&- literal 700 zcmV;t0z>^$Nk&Gr0ssJ4MM6+kP&iDd0ssInN5ByfKjgTPBt^>M`R(qJOXt6Io-j@x zIN>&uBt^=*duGtytoL6>oqwm@Mv|mRHM7qW+VB>=|0)@9yi;&%+g4SreR20f+*=^l zBFN~1R;&ghkDvxZ_b;ao03aYBAP@xsARr*%KnVDOfY-PO|GJYGPJYEAtp3i|%g(=?ek-uma=f@vY5P(>PRS1Y2PA^{{G1?uUr)hI- z)$zd(6uPj`;h>$1&N4p?QrB9s&syly%4m<+Ne3he%}NAN%98-fT+|Zfzz{#%X33Z@Tow6 z4vCQFKO diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index a3131a17d34303d3abda58d4d5a158bbbf437bf2..9ba77a1cdeda8bdeffbb27fc4cfcdfc27076e1c5 100644 GIT binary patch literal 448 zcmV;x0YCmyNk&Gv0RRA3MM6+kP&iDi0RR9mp+G1Qui`3_Y**<$?>%x3D$s*6RH27& z?y&(Ok|f1ec0VQ;&%d9igX#Zq5J{3EWzQZx$aw#*UIFv4jci-V`R*Uwd@QayGx~MI2vG$1VHgEBUiqQZ$$qmz*zs=Q%92@tcyw`y$;@aVJzbOlJ&YMj8!z6tka<| zx}kJ!A6l;|HZG3*2I%r~Ul?mRyI_xWOc>>TPggN7jNpK)VR~?*NG8h`64Gd|fZzEN z#t4pY*z3wxOBUm}_w)(gr18L=;(5@Wy~<;qy~UjA;I7UpI;Mmx$5sz_pZ>l{gt(nF qp8@z_XnLCnamoDsvC-)%cJ^IWb2FY*XH;J%rr3pI`ya6Ww~YnFd*2xV literal 276 zcmV+v0qg!!Nk&Et0RRA3MM6+kP&iBg0RR9mp+G1QXMw1Vq;&5E{$*eajIcHkM6!+j zC}0#&N`FC&$Eb}YN8X5INgaO(_sn(4<85GsHK=s)ZqnJ<+e_Jw0%}bC(Yp9T^lnmcqsuD?*&GV+z3=8hXRM& aA7Cq4pz}J(>m;C_j(ko7y6ighzd;&|czuQd diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 358841497c4bcbb8692e6c7a010b20ea44b26573..c3f9a6838627d00b8df6f9085ab2fe754b5d97a8 100644 GIT binary patch literal 2346 zcmV+_3Dx#eNk&E@2><|BMM6+kP&iB$2><{uN5ByfHHU(>Z5W3?>>UgdF#$3wd#Hle zAGW4hc~klSf03>Hv3e8k?(XjH?(XjH?(SaQ-QC@;yUV$22l|ygXV2~o58#(*-rRNa zsYrD1gG19~jfZfT7f-;Q^LlNArfCwRBRNb) zo8F6C`nSd1Iq{Dz0g@!iahv-88+DfV6{wy8Kt@Q6BuSB^&|kOH;F%dZG8<-R%_PXS zZQ4~O&+o;yZQHhO+qRu9Sl_nIF2uHtd)}G=IMuc-Z`6euHfBy%+L@WkotQ>uX70oc zI-Zc2%BX~DsFMh(oPUpxnyQ3ItAy-v`pa=8NwR6}e@pk-w#{SSp1J>yBiUA}{FA}m zSFm!vyWn<(1o}S#;OImkASVJrKV=z2EGGh5BA}~aqpGN~C_IV>tP?$6v3zHw484`j zYiPdu(n2?ZwMX#?O;rHE_kT#5y6(Qi8jDU^^tVOlExKpcbF*G=zP(xZ-dAGrWPaUAIZ zb8mIuIUaI~Oh-HG43R23f`_b9B3>8(7DuT?FR9LSbfC_3wCL4w02?}FLoo7J)MQ>! zG^V%ca3@mp^!1{!p#wIAar|p!(Ix7XP=JyX&AMdtuQ6_NOQ5+cxmZ4D*qY?`n+GRs9SRalI1#0COihf6T(;K^3aLVDsKr01Vn*^ zwuCy-p4$Mf_;9`t?&l6!->1X0x6I|CDZTYc5V}?sC^jVm8pQ3bX4VUo?9v-1<+bkU z7(FcwW_q?z|34}q@z}}55bJ1zYMU|Mr%tqMR~U8YVxTUK6awsTssi;p0#pEm~r8{Ng)ojC_i3;5v(sL{&~P)K2RnWh7rvd&Et;>ackIc)WxFnxQZbrfy6QCuS{kzvpCHY9lQVUzyJhghBQ|XCPxpw$Y@- zmdA=I;`85vLp9NG%5!0?4T(oicIp8o@OYrN-fG73*a0~|)=KosNgMmpwd!@KW7G@O z#ijG;=|x*JCRRNTb;k=@x29Iz_SP`O6O_aySrpOYX%qW00FMk!g1k>>TYQl!JC%bP z$TDOM!tu>4rWIFO9wuD~1ri|3G67!f_zRi17#%JCCm^yR{iHa;}=H7bs?5qH&1n9l&8Awqm_ssKvsl4Ll5}ufmi^o#5yI0Jw{{YDD3PCl2 zZlENL9m235xSxmkb-}FPvolV}Xb2CJZj}T&ZW#dnAs`wteThsMD*&r$Jk#hGJ6DO* zw(rb<85M)~{6IkL9{>P_0C(UuOE9fKET-{K!WMR~Vu$VCsvVe>yw}aX>b4yj7*;X? zMg^cBud;)Zc)UDJcdvLbyIi({9)U?fV)V)+GVuf@H0ZbbaILAqPj%aNmO+zIT`WfB z^Jobg-79L_fBb@afJHzqwE2uoEDQ(h{v;qjKKT0&0pT|H6`ddf)x{Caqb6u#U&*I+ z0Sy13dSDY!7%_S7a*D&0-cHtU&o|3V05c0lG>*lYGZTqx|_b*5|2hgZ6o7>6y>G_#}W{74RJVrn~ zD;}m}C-dk;L?1=FpWjgZ^8>1VzK6dG@iFUW4|E1J>P8{Hb*PZvxF3Fo;;fdxIncCDEdGmYU{80^ARgHG*;!%r7{7K2 z!}T>CGdXGS?A*X2K*A07b(e&a1k@|JpCKdFTu5eC;uV5-ZJz|gb{EQn@Gb9lgT39Q zF(e46h63yUYkr84fTs+0RB%duc3;&`p}^*Yo84f4tz-}-ph&=|<9&6HRXs}1QwBRC zI3-U$X7#B3b@~Vxf4zYQj?@YQZgwC~K&Ke+?W5>{`Lcp-@`;k6C_xu0JxJ8LcDQ|Ee`a;O@`cGI$1nL^aP_<*ZN)a-OY>L6Zq?E zD}Z2E2+12{cOam$e?C6{@*yjLpxrqGYpzezyg)$j@QlDbBd~s=Fm9jXuG|0DAD+kv ztX4Vv*cZ}#I@dLB5WYJvV1}%kFUVv9a(ktj|APj QSj7W-o&C4o%B_Ke0cqZTg8%>k literal 2164 zcmV-)2#fbpNk&F&2mkQ-z1gR!0YW5~?RnOM4OpZh>hRT3o0Hf<}8 z@oazFwr$(CZTnBbwry()&$j*E+56T6K(xDnVs`;GN81Jy+g(8G0=f$tUB|Ue5jX*a zcX{>G${q<9dQ6aKYx%p-W5TuHKzQH;Mspni$Ue0&scU%l<{m|FD*9W|6h(_=t&z3i zp~qz{?tfj+Y~9_b9{@P6ksKS~0xK$ebEOhrC|V#ZR8llk>M8O0S8oE?j#1nIAp6w% z%atpNmQsq6N-+^kGD1{oN$T@`-|F`QV?H8%`t1Oq3#hIk8~~{^Owo^uLh%J=VZ;`D z0g-M^A9@bz^Nk3t;I_s~hvbQ-hJfL07;O&2|MhPZBBKnKAZ`!kK9 zL1QRsedqLX;OGRV&MQ42sYA1E^m!xrX?7pS<9R;>?5-z+{)Q>u4{fSZ#8A-zNyngv;+k#!TC90F*u z86jygTIR8MJ5zE}4e=0!InXJtp#9K1Xk{eSNj8EL=6a#wGs?`w@Vt>X1XLFQY$ykz zLXwA;nJnHaDcA>Mzy@%k<;ZYZ@zG3p`+!n&ng~@v2XLTm+)bIeBo(2ATFDX50dSKY zt8Svnn5dlAbwBBXZ4=Nkhd(2bY=pKWnWqEl!e|(UHZp$+n6_=h>|lpt686*7^7B6` z-hN2ATlY~dfAc=|9=#&#bif$>c(Y2%cn|*XiD)>!W5J<|Vf zF^0ukC54+nJOlw0?Xj6yRzz^t%a53$U8VAXHMb2bve`z|TJ+83^LDA)gGRc971(Th zVX(i|HSE+LTL`fdEpu6l(l%D@u;vaB*svqSggOQ0Ygs8=9-4t|!_W|rVUkc(X|8^} zaVj+rgo16`1pGS1Uy{Pi#bo8o_@6+aZG%g|X=QGkE>R=>(Z?=;^UP^ghp5RU6&VRd zdpPrq1K<+S3$H$rqG5#XMAqqux-d>?5Eb50Idlzk{z(DY$t9s_rskq$kc_`OYTtiG z^*fJHulw)?S=$LU(MaAnl|L7>N!kEV6?6hYEmAL$8Kl+ce-yd8;cJa)zgMWSM78c8 z2n44902~5trP;S&KZE?Isq>#TYO=~S<&-t=d;Byo!n%M$z*qv>r&kKmGLL8^Z`@Tp zJ9PWP4ub+<5(qu3aub<(Bo$ePGF1=n?Fl({0qyZarjvS5-z=i~c-w?+*D&kxs-Wit zn}9_ix{>*e%q$G6cKsv}a}lVf2m~Cs=GmDE)HlcbyiLLmYhL{B31Fzf>R=OyZIC<{ z6*1+RLrQr)%5;Bo)9PS@#sZ+dyUei+YL`?}%M7BCiqgnbE_5uz-`yQ_EwDS--{kFn zEImP1VSeLDNi7T|zpZzc#J(4uzgeUIE6&VKcoLYLQQD1Yk#8GpB6>FamU za^{PVVCygc&{Fu~iu6AlBn1<)@p_c*{+U3_e?4vRJ%NysICniSNx@iH#@`){-hShp z*Pl^u^YuRoyyTqMUx=%o`}zx+|8}4-8jaumAg#K#bA)sniiT_k3erFd))( z!6gv2{P{Amc~zyNFf=sd=Ok52z*GWJ-$CRnu@58=zso+ZXq`w!VMb!SX(G4Yr?mtU zay=cq#W@BOXtw{HHn&VkE~UJLQ4%qJ>8{7T{lkSgg?ON6a%Ko2&|>?2T-8J?a8SY+ zi9z(7q;kuBTzmNp5Sj#ilXGDhf#%DcFH;&;Pt8n_Ri=!Px;K?anEpuLBzXT>f6ks!w^K&wy`}puaaa1QCcM(CO$kpFX#$Je0tt zl=pr#-p}QsgmbFO51-RH0-Xmv=s`5SslXi#VhFUE1;!Td{;E}ipm0e=O0QIci%P46 zqSar|e7=n9`A=^TqD(-a7QzTLA<%K-^LcssuJ8ZHxS~>0k$OLp_fza8p8Nl&8?Jd} z-)pCZuzw)>Uhgp=>T&{I)>!kR!c`B>PV(=(hI3hkq=NMm5}8V+QqT5d3FQ2mPD-y= zR$uete=i@{+XF)q(58dH(IDpXzT?60gVubw^z84?tjcbikf!vI6s}5pq$}+b&Z^2T zGyA)5uj_mHKY!i=hP`Db2$&e6o+Qxr)-jG{@J9Q2)v^0@vFIEhSMUD!v{d4GNp92h zk`|SU=bNUNG)i($PbCg4Uc=+#=Wy|%F^*+0fi6E@d(%u1KtKaSC&vcssxcsnK>Tk6 z+VvR{PQ>4qHHwrZunyZfKI zq(KBs$%p{Rg<#y>9qzjU08&62$N?!J4P=0h^cfI=cz=C@G-*HrJWB$&0c3%+^fM5p z6QCo228IwolU6B$7wJEPE~efv4x}Rz47Er4Nt&fvl@e`IDKTa>MOu_P|D=u&N6P3JwdjEeqGS#9*a{Xva zI`#UGSCvW|pxw4zNJ_iA)E?VLk8RtwZS%heGs*DddlCJg0P<7%ywX8T1EQb?0vdtA z;UUQ|NI>IgWTbzB*wzX?i^C^Q3+ts>l_uEuzRJ%SW!`jGz^gY@wcaPGS^jj;>MN&?OssLZ?$BEc;tW qVNNbe1uSTPw{t*qdR}htB;%{csS&<*?{Z6rdyNRkx~0e9rvw07zc#-B literal 588 zcmV-S0<-;6Nk&FQ0ssJ4MM6+kP&iCC0ssInFTe{BXX7@KWLKGe@1reELga4|36{K@ zG~7m#q)63s&k~bY@4o{tzjVCiMv`q+nS0+C$cS7P#r{)rB7HJ&Yuh%p&Cho9h04^% zms|C?qraX%vi<+>-2eayAPHoE1ds$$Kuf&_0uWxmXQWaAG4QNJU=2uvNd!s-vI(R@ zDNrC4N`=xvWgB{~YH3`%|M*kWPc0SV*T3~rDSG+)^&d4?HGX~mFSbDk*8yact?RYx zrLlE2SI_IccmJGVMsv3kycnbR{*x1d4P zP#Uua7Q2CYz6Lel&4`eKh%q6#A1tzu?%K#X%Am>c|Bu;=dQ9YOU&dmw*ja>}y$qV( z`ny@ZsDq%xbM(|f&PfK1yZg$@a@AtJ a#JrxXNk&Fg0RRA3MM6+kP&iCS0RR9mYrq;17s58Sttj_Cz@-TyTeAPe*a}$M zVA@EMqndq7E&KdKe0IM~8%c6hvu~k=pMQ#v?$=Qy+fLQwoLM0N6yRS42Cxu*SP=k% zm+*Vv^xpS{MMs+b%eHh=hU0{k^(v|JU^y-7oP7OyO{6>3Nzgb)H@A_+o7 zDij8Up;oC?O2zm__K(rb{|{p9(HE1Pu4t$G!m+F3_|7rb^Ld`ighBN4pdj?)P^uV` zaJaT@+iYsvj8nHsZT|mnov7!rxF{m}KLNhCYr_evVeFQzZV%>^A(zo_R&|P8wquF% zhgXxCVKIc=xGq?2n#fIs^VQ;@fLuhl5R1d&LzY#oVR2m!BA3vPC-@*mk<0aoRb~FL uW6|F1EviXF+I?aECo!3S;jL)i9gCML77HVPs#<|biU;zf0+Td*%M<`dsj46V literal 210 zcmV;@04@JgNk&G>00012MM6+kP&iD!0000lYrq;1_n@F{BgelNe-cl!G!(RL4X8ezHQmMgTeo MXhJ!asU7T diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 0d0468ecc8eae0b6ededfdd8ee53faf2ddad7fa4..2ee59a056d1bb6d5c4f8b7e0672432397213a300 100644 GIT binary patch literal 1546 zcmV+l2KD(;Nk&Ej1^@t8MM6+kP&iEX1pojqFTe{B6^DVeZBqZT_j`zl3Gf!GU}{df z^8cH*r1cW5XRz@Oop#voU^8OeW81bP+vYX*-mTc1Pw>lTGOs$}n2l9+!$vsRV>F}j z%Eq?h>HpaF3Qsl+S3b4JJkeBZhr@kOS2=RqMw0aZug+{Lw;;5L#ZNh$w*L&S=c z*y`R7h=6Qc+wo?_wr!7Uw=>(et(naWu-#j*k))VMtww|+hcqs!QjSOOUQ8QQ^L3kJ^^GtHi<4%6IN`tOX(Cf$Lm`I|5j}alfD|4mU{HsTHJUY< z3K{7iC&xVGM20ax4JcO%7^L-<{u4KnfAVlHr`e{X9#Ae6V9glO6h`D}6cUxs7z31} z7_f9%^9*^O;Z$l?7$Be=2*4Oeb%v9VJVOPO@vkB5B+MCgnsaivglKqOpcAm#MAVG9 zrBJzo97iLh%~X_ic+aK`Ib0^B`F;p?HQ}}bnWw$NG-`n|x-|JeaP`O3F~mT5G*d`U z=Mf0*S9w8z>Rq3E=(Iv1<+N4?NW`N_lqO}GM~>zJbSApA%zy(kt04izC;(!4w9#Dh zkLK!}#J)065d#thHb-*`0$o~~fW2)IBm_`m)+r1|NQb3c4?!p#eul=-H{!wIgA^3# z+vYbWsHw=+A{|jM$Wr*R-6oZos*oQi$xtXL8AHK-agG5(;p@V9Xm`+9SLo2Xng-B0 zIy9azKm*&o9&)idY!(qP?pOSxz%L7sva2P=Ugz|Y>gxOcFn}GpM5W2K%`5=!aVIN^ z-k~~-Ir~l*j^25**lszn%{i)aeYBZ{)lp6mA?h&Ejr` z2-b*Tvv%xSVt|oTzuuv%nxe=nd*8DBzGz*A-jyJATMG`oLy{=66fQ}?diOScJyl_` z-`A~7Ux<5U?pw;&Lr~MDB?)vJChhNI3lT|V7%0ir*Iv)h7ux|^fC2fiW`~UQ&nxq< z^%YfA@6e|kwBI)n((cVZ0~!S6Uz?s)Gb@UMU?@CD;o=0W{G6u0BoL!U);>Rd3G!V`O3S8j!wR5O8BO`H^V7(Wcs~A*YLYzvv3>N1Z+jSyE1l5&wtlb z!iA>7PNIe36#e_I$$9I$vnuZV3GT(3fMvj7U|42%_r8feMM=U9g=~3}Q0nZZclR(b zK69Lar+1BIz^?*s1Mh9#zTH2UW>aJ5E$?n z=vtl=wb--UHt$;7H8CI1Iu1{+j_kbu>Bj>b$F7~`m#+GZfxhyiyLtl>KE^W5fLi(P w1vU73G>4ED|9|M@d!M@C-OA}{8N+9h-Jdu1xb#O1NDL^io$^uoxT#qI*VCr~Bme*a literal 1400 zcmV-;1&8`lNk&F+1pok7MM6+kP&iCu1pojqFTe{B*XOFXZB<$O3QqqvBp``?y@%d= zHl9IpB*~2=c@?nyzxs=@imVo#9|4e%BimN3tiA93ifjI1kOV0(XXbv-*-&g-Teh70 zejo1c?heW6ZkYj)83cnNQOP9t=I-wBegFSI>i_@(0uBf`pg%x75D*Y>!2b+@2LuG< zkk8+OcL5}g4qJJvgk)(lKLWc2&6afdNtB40aNCE-^asn`5hCxQ+ z5P*OLj4vaJ#1k-HDIUSA7?N$L6(QY84n-%(cY+g0h$J8hj7UYM3SupsfDo(jF5(oi z3le;q{_l_2g%c3kpKqK%f)jiS0zO5YhJ?NrK1KWa!oa6Nh*fwmVl8eTuUtP~A;G8d zX%J!+2(gRpxX6A?4fWk_d3P3+%k8BuHfs}1lri@E_vg#{{=PVuz8NEns|B<5*@Vd^1=)XC*GKF#gRbUK0dXX1W3vD_m)wcbn5v~n>sRG^xdPFBF;t$n|0 zRzQR@g0IGbPp>T0%k6)j8g%GO?H9FkRFL^yLxI|VL@`jMB3YEA7Eq!Jsbmqp)QYKO z!a3;x$`=a3oYNV3&L+h%4VuM;DoA6b2}wo@@I**M0Kr4uKI$s%L<))O)t&_c(xD;d z46k^I(^lqm?frH7^8aHcow4*-wcK}a(zP{iug`Cuw0k6eXcyH>;_E#AU4K9335>ih=&NZKmyZ zQtw7=dl1{1Z_S>)4rAL+>QmchQY&@4YumPB?UmH+u45;^F0Y9GPXIzo-kHOfcjgi? z3kLU3UAAxU@)O)l#32}fZ9RMS8!&Ltpn(JW_3F6|027gQ27c<+XRxB6gZp&*0q7i! zur``#bXS0f;z0Kyq;nKuPScRh01v@|EsBgq zky?gmEWiT^ptsUCMQ&>vmjVwUficR~6r;B3%M_#^!zQIQ#j1x^e-7r;85@R>O+go* z>v{T|0A#E0|3GIC0gNI;fls^4^MZvz$k0b=2p7XfrLFj^p=*8z10e9@NXk)slvAJV zh5#6TR+^GPLsvfs1__KOlMF+zry)6hsU)2Y?M(XzyfZ#i7A1p*t$r4rIldq}DR%ku zDUQ@ve#6&$u8o0S%>aLW<9E_|p6AYtPW^g4@YuJ0XI|!c;j9=wyb<_!M3ScO0scYw^J9 zt$B(uzjfk&E%r%2h-~WzCpuNpRXG#yZcEtpb7j-u*GZ$G^9ShHFsL@i5onzrA+V$~Bbz4`w8 zZ@&EGsulY?G_6y*1~CejZP>I`$D;=i95{HiW2>eOvrHgnS@?5jwobhU4eHg&?(AHU G2n_&;$GCI= diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 8aa87cc45b48978136c981269ed293717498fb24..635adb5a294434fb7a2a313502de52f2b585a326 100644 GIT binary patch literal 1082 zcmV-A1jYMONk&F81ONb6MM6+kP&iB_1ONapU%(d-CkHo@Bu7s5%-(}DiFyG4ErA9b zAaS-k(;!a3Z6wK&l+}G){qyAgcZkVLuM=)0*{+q@bH5hmM`uC)A&>|bSWo?X2Wqx$ z%a-%`KHS}jUjhbT0V*R98HN!^GzNf3B5HEiUf*+<0OQYNWGV^!e z-?iBefuS6BqpCitQb{Vw?Dij3sibcIVcbrxAR-X~gaQF27SX`UYIGyCON=TXgsKRT z$Zkl8h;FhPC8D4df{Sx?eBax;5r~RRi3)+O8v!MhIK1tRDoR9R|GE>2`@Z^GCPuHX zwYAs#^xot2`}qGGJ;{RQuWbqIls^6Z`kJ+ezrV?qwDhqRd0V#>(zb0c#wKrV+qN;= zwrzG^G27_ecD8NXTRmI9<$1H_`84K9BKki8>=$bq+ThDUdK&AiNb($LuG1S}H#Tg+gdri|4wm^7P7<}4GWraJwn8Xl z(bdb)+XL`vbLZ}J;Im^3@#%2mVKuPo{QN&K)N0%W*f1TTwqH)223}l}fzPr7j~)Z> zUhjd=;^S4-z$aIF|9;p0=DkW_#kmFJPn1>yOVzlghs!I0_pkM;d9`00I0`+L)AQD$ zqd>YPN1Z&Pfd^mJt9i2Zv^-$$==A7*)AN8zMyHO<%>zzt7cGFuzk;;nG6j|9vw7sl zVbXD)IWPSqD}7-Fc4{<7n9I)9zb8y?ZaX z{wJ!Vb!LJ;FcVS1ittwjQoQu1qKt2Rc7W})-+{^~1%wl#5MlR{3GgEi z2web@4B)M^JDplxmH2<#`qAp^Oy8)HK4*4qVFM%6EBYOXCF~QFmphAMHrm_3@CGh(S@IQ^)BEiBDLI5AAZkJ3L@hkd zr&bn~Kqd_jBitY1l1!2*OZ-Q;B=ha&vdeb%ZO;2?3&yl>;GJP_w4mkTzl<-^W09SxF8=v z-^m-kXC=$%%d9RbMbRs2S}P%iAN1xGmLjQX)dF39NbR9^me07(G1|fF7?$=urTC zvf&RT^S6xwA2%XYf5bpfKg8%5$n5wX?+NTg1C7JRss1QzOoasp4nudA)8rus$dgpI z-GLsQrvgz0l!&O@1sWnE7g|PYsQW9(QYp7kF_&aqYsF;Qd9f7#{ZL*Z7TQ_=F8GcJ zPeiY)%dNzrmPc+zPgr7+pI5cyc2w0UPX(+ueT=#wW`B!0w_#3cCW3jRTh7LOwZ%AO zCicUP&1+UHH%e=>+PnVApANAk+F`SP;)^dozAH}r(uQ6+qSB#=l&iZfes96kc*h`<$iSsK=LE~ zqW|kx-|z2p{2GSqKR^(K0Kr1AhzM{H9{55Kz6kijz`)=S2O|Yup|UEQARDH>sc^wI zD`ke1@W`1KwWoLfrbkvL`LmLuG~*wV?6^=Ok-zGcqWs$*tFrxm8sp&KMR{+Xohnn0 z+y7t^LixVz$DX1JLRTb=hlM~2T}WX)Z+xB)^J%3DRY>7=*?C=ds?bw7AlkOADxo{< z?!pez?pAEEmH+<@Yg;s2-@Wg>9nt>@un7MP{|o^8R!K$lG`=$G7ENcEVpaq6|PhG zuY_aJ!`nyfIm1@jLFc^={g*T>vYE#ZI`q!viTr&Veh+?B%0qskDdiBqaN+Nh2SHft yj+C};FG`b!MLK3-{$XnlAh#1KY|OB{jxL!{BPw63_2m?Fe#;{KFZ?h3Us)SGNdlb! literal 334 zcmV-U0kQs4Nk&FS0RRA3MM6+kP&iCE0RR9m*T6LpkHM&oBuDN&9LN80OpZ@ZO@mPz zNshevzl-ho^N7tTk|agBaHQyzod3t!7+~8-lA>exbQb;RpUKC87BkZXNTJL1$!VV# z0002~kd_HhEMmbIPH|W7XjQz1791*CFs(?ce|k!F;7iOuPcvQ_t41llp`WUKl>PWR zM%=&3sf;Rr)&6I{dYJ8=^*zsTUO!vU`@oeX$)|MJ*h&+kn{|0jTv|H=R4 z|6k6XA3*)??i&>Wtc>?wF2M79FT-Wzzkd+;VGdnx@4meufYs*S8wI#^@AbHp{Pz`u zTet}{ZtuQIKr5?z&w~l&y%*sYZKRk#?Cl;15itSXJE0;< z+fSE$#l;cA_-$v_Hbs)1bjA`hGcz;GV~;Ia%*@irVrFJ$W@(w3nR&OTyXt~X)6-Cu zA^Q_ETWnH^nI~j2GgZV|*P1rQ6*FURh#4>6QIscE$CxEDM@%EHg(Zede_+Aem`E`* zPP8mp%xQ_C6(L)8UhqUIv@ThKvc$|%i5a$pEm}_zZHT25gM>(u97ziP0H1^w6PZ=h zy=RhS+m74j?tgC{CZm4bi|;-FB%mr;v~8P?)Uln^OWC$)2R7BN5Jk(AVWN{=(wrRcnFA1}6+h(8h{0=^5ZUcZ#oBV%F+BI95nHeP6 z;Y3ha6}64zfZD?UwO26R$rAwEI}QP~A%LD50vI`_QaWx@LjcpH5F|Ct6fSf+1RpF4 zemetyP^;tXG#=!CtOtGm+CSyGe|XX(z%Lz^>guDW4gf=Z`^+Bne{6kqqHK1{=Kdi6 zuRl`E8|1m_WYZ~|ekl%q_mkI`P3_)y|G_hz`9N0y0NteOGARHI{_~HXWTy@CT-Rlj zC7V9kKr-OmXAmgo{BW|#yy~AP0sv~Nhf;tZAd-?vve#4DGz-oN@hRntWGohs`;11@ zw0Jxgi^T~cod5smH~lC8rRty*K-!m?{9OBGQzAGg@wnzQS`xIa#p8r<{_~sv7l2ab z=_Y`*m${tll5DyJw|!4WHJgOo*79t^kNftI0+_nOOaSTcOD3DcvgsB)9f&umJMoblFr09*Yx2#$$wkNq(FGFs!ba6otVoAt%oT&(nxvwLBuOb#I_4 zg-`%FSKQcw#E7C}gx~M$08|ksfY0m^JkKTyxAQp9ihB{zz*K+<)Ta*x_ogIKVU%!> zJ}LuDFtzp$JwPOxA41cJDm9IJ%+H!2fp;yL;QRAmDm_USF`KB=j`1E(QX2hz$-xYZ z2~9yHnIsVoU?+|Ve7%F5x{``$ylX_g zWGoCD5)-;vLNc{HN%nWzf9iH3GV>lbO(cI!zEe4E1I1ySy2}`rhI9VX#G4 zgBmmDf*BNrRv?i~8FvXDh|AlWij9I2Xi%(2dr-Ip7eq2t_lP<>PqJVrwAhUO}0i>YJ_g_M<7H}6YE@_rgocZ@9hg41`>zs@i#Kw!)XhkQ3 za&;y%bI^6Gn?B}If)_ICo@(1y2pGEVF9p@D+GpyygBwS*Od3}=%LFzF41dG2cK~mQQ4^* z8tlIo@z5MM8Q}bW zi~0L)OlmZ^q<%;KTtJS1_8|buRu4p$sSHI+)PNQAS||}sV$Gt#2my^|+xWc9vuZPO zaO8;QZENPMgCA?N?_=vVUASr)yH|O(o8@QI@8jgtURoxozVoAscNMgH3myEG&j>2)J9DHG>lkKbmMCv6fj&25b`8*~}ZP>}>Ze z(?J{+oSW*eYe|V0OC**M8b_ogOKhtka70t>THM)zHz>}gClRPR#WYoHs0bzEaKG_6 z9lv$$f0O<2?f6){tW+un|Z%^0np~#KucPw2oNCxmUj9 zjX8a&a2jCVP78>OmjY5HEtv|~7QQVfSTyiYUM3zhG#e+0(K{$M(0TFXuY~|lH{v4C z_UaUyB?AX1j;Iq`=YLPoh)V)aAP8s+0U-jtax<$9GYk`iQ=NbDBqS%>uk4Pw}v1Q&=w3L1o~?_du3+>q2a-+8qqApO3&UjaDE_0 zKnn)aQwKQ$^Ro3#_lOcK7$q;<)Q(RdI7S+RMnGE-@DZqI2A&PlO2e0u{wlMS)$1jB zHjzFZfx~tijsP!RWx@9K61QG>gvWo+*OD+VprFG}`efu0O`VNdzn+S+gorV!Q9iAn zG5Ee_-XLVbfi-uKAu!5(*Zfu~$sLh7Vew2{fLs7zS3<>E;=t6diIl{11v0`X+~f1H zsoFf{24IB$qX>-)6LCcS4st19d)ZMAAAwLNX9+q8EH=FJWU06nup>9i!esdKgRxUn zYbs>;@4sP;ZK;sqC1r=En9w9zM66}!`3HYB2_a=u2j0X$jlgD0+vjnG3+Uxa&kaxC z^xoA}fzLt%*7QL<5P{>ir#}zfT)90?6IXScnfVui)4;D-5bDS*Kz5W2aOwa4=1=AH za8=*=`<)DMKg!=C5OS8D4X6;9m<2&}|9?w=?e=eJF)6x^-~VcYpr8EaGz%TD0w143x+s2n5puLj;z;UD5k&EZb4s zCW4>ONhl%VoKH4r6S19+lI88({M$>7lN({6P;0<+fS4kfBe0t+_^e~sr@Zuy-77ATh`5s0P>MwiN;%jX@vzFqgKrA=)jvJ7Mc zNl73b5Hn<%jhCMR zUdy^?-9~r5tJG1mrxzYU2ywoW0@rmN$93I;lJf~6jIXwgsN7NA>+%`koj=}ikIYRo zP@$E~{m!((aa8#lP?@wR4U)Q)W}QcdXr?%_4y!Y?;Giq9UqRn*jt zt=pcFQM~t^jf=qkQp0^GH)(-Ntt0~e%clkglLE8x5IAIja|U?M_w!C%*52#Zyz|V) zI$vxx<6qfnR<&AQvu&s!TN+H;Kf9A!ZG&E}&D!dWe;e<7zSf;*mc94di=AlPJCApY z;a*;tL_h}>vj_qX0%4!C|MbDV@BG^gaLEAo=S<_CpjkuYygu*5`G1~qhjI20 zaEjr^DSB1z@zNFpEHW#7gJ{}Sr!6DB%MyD KBN5AD3=|8Fl|lCa literal 3060 zcmVp57!{r{wYkHnFeMj%X?$S6Am*}xqD<*WsQh5+O|!-X+B%Q7w1!K+g5Gcwr$(CZU2gG+ji2v z_r5&=aF^p0pfd%~(o+Dv+;rN+O?nDo*e z`|n;Ysk#T>=T{3Z^yq2;Fl|>~whI75&u@%HGiA^^UzJq0r1~WVQGqc*K`CSGiIU2C z$&N$MZ;S!Jwe{n=0L35_qY0ApV@b7e#t0EKDa1rPo=6CaG7KY;h{xjzLI`8K&9cI8 z09;oet_vWJNhPiGUP+a5#z-Pz2ue#-dqyHb2xI9LcLi`=?U^=!IA*n!`GTanIrD^7 z{|%3X-7_jY!e;-@ZvbplV>W)`wTrU~k7(ShV5~IWjX(!%4cOoc`aWl&gdjSsB& zHw>an!(b)qdutFwEJ8L2SN9TeRSJ)LM5kVy6|YL|ay`kwj))E2K`5$ZUP*LZ$qIGK z7$5;V0xr0s`pPSbt}Dp^_5H@c1UGDH072d3aZg{KsELq5e)%DVbhun7iFb0tBRcm) zMU`mu=-LZzm|{?xIfgTXggvxO7@<;BcYgNeUkcm)l9(-gK5%G{DfDM2sU~07NZ{RKxIV)h>#Ga z3O%b{BA}V3Iva{QQ#kP=(d6-4sHu&F#|so%bJy=Ko4A1 zw79aL2Lu;HRAf<%cB37qYFx-4i%L{oETkxW_&iT{kGrk5u(L9omZ-BlAow7HA}ia+ zSE;SYhHl*zqUvBiPJEUdciv$vHqaWFN?cl~c5#E?gh-0|L(BLubrqzc-oGnBNEfmI zF9GS1D#^N@WRNb;rURm}oOpN%DJ}1>(4ah>+U1z0Q>Y)ZP(l#Akkud1*1SMKH%+-T zv^0i-YG6KYeBL{1x@LC;j0|+S7|mZakrNL$NuHS0%%NTQ$&z!Of$=5g6WkD^I7aiX z+m0)fhGPP=M5p7(C!Le*0{{PIX@E36JqU_) z4pCKfeTT^&zJ!XebbS8{x(Gw!cb>pgc+;f#V+#H!vx7A6|eYna_`mZ{Nw@ma-b2#Jc%5(Tc zWp1^t_8a1Ak<0I!hurnXVpS5i;HrD2?Lu=n%bgcYi{fe$;3(jos%lRUnrJ;>0HmEG zf~-L0;HjFliwh10hR&}6Ob3CePx%MKlhw5I7lF*l0vdps1q=j&-tiqQS)w9eKk2Ba z2!!7@0br%!ZBx6M=iw(QDeXXoQL7yvz7mu{z?q=?G)4^k7`#u6>vvxij0~(YQvhSR zE6(9wsyd0Ui-OB*4ETQm#RJYSP;zq*H9qgIyU#gC;BqUKF;fWi`^V|}m~Y~H&oKVk zBPbsK>@ixu`ijzJf*R7SDi?uCYRNKi9bhAnc1f-=U3B~M8(&d5`}0&RUKnGX%A-xU z0on2S&HnvZ-RCeOC@pS6O3U+NTx%{Xe}793IC3Kc9Rc_Btab&uj`+CodAp?X6b!eI zd|M7q32X!go#Q8@5;K;liHDC{^%jAAH7GX=SO_GyzwNUv;^8I9b7EY1F3W$iO?kf_ zeqd1N>p~KWNxW2(W=ZM(!FKq!%Y(B4Z=QPb5lc{%hm+(QeKl@f?fg7jfx`KhJWl_Pti=;|*!n#L#-E}};O?8AY2W&~%!fC4x05YiOyk>Cn*Z20#nK}6L+=0G4r zpsrtjRTM*1Bo-`Dk)Il6)Q4ZqngP}Hw7^4Pctr5*lvG(lLdcaa_h|C;Ek-@wg|g94 z_R#D5KM>OP&3BA`ya#3XT9=UA-^Uo^bRQ}jJbH!V>pRF;t6L3|n!|zbS5F24st%kJ zffRvx*4F1{Cj|3l$iT~M^!olEJ-;77arVyxl&%v9DZ0AB+;xiQUZ0TAIwR-jA?g(G zJmCBiMAqjWtBCX8jwLgn2&7R6XX+?mfIxG*(T=~H8!|fHh4+ea{n5_8{>b?&y(0I`atNQokzcFSSYiBx@7nbft~}?lbQcg|O4i(Lo=9Vh`Xx>8Rv{ zq$oo#yR0$)*xk~04_@#8R)&TTXzD0nfIx}Bb-Zpr-pzeW50=lLG;+7!2s~c&mlTCa z(&df>as(;_&ZCU>ymt(~dPqTg*6O@xf?IFx6#|#XK6NVTlTS5P9bzwjIj8&#_2=c`*j=&R8@Zn@rEEQGDuf$s-FP+hw?=iK^B?-)(1YKA>`1n_>w_hWCLCPP7oPHLLEgMp%I^AW)Lu=^Hz z$+4`z&FBv&>B?*yxA#h@J~19u93!N_xGb|uGzwzOmK{^=5DGm>b7ahx*>y38mj@6!xpZZh3lii|R(Yn&u zF>dYD>G;Fl+f*9O29@mbyZ+p%+S411cIskdS-tdJo4@w^^Ta+oJcWP;I;K$sYy^T0 z4gux|1~mc=0-M)vHv+hf0PY_<@V*5d4I)o^ct||eVIA|h&%lBAE%0MsciqO-)T*bk+||^O5B;4h5`I>Np7K(M%?jiAEiVQqU}v CIniPO diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 71193874ff1e2cd9bb09b8af3de77c7e3ae27d1c..65379c15970a6d9eda447b381f005a6e1e2351b2 100644 GIT binary patch literal 1456 zcmV;h1yA}?Nk&Gf1pok7MM6+kP&iDR1pojqkH8}k)rOjYX&B;PIMDGLpbQMK z`C6CCwYEKtoWBV(Gv^LcZI=sRDw#@VhKiZF->t&T%uFS7#&U82Zbx(cry?zb*Bf>Q zojF;a1ucW;jMj16HiE?dtGoj4Id*1tb^!>DBsr3TKgtK-zp7`}N^opjJNB&Kwr$%s zh7h|-WsSh8lDTB1{QI!9o&NXxe%~_zaJ6kah|q%*xCHL!={1bGNZ=t4jaPK;)1?=_GFUu;792W~XK!AN%za=@jl@kzxB!Hn1UTOW7S%6xA+44Ur1WA$9 zA}JxHghVjb=@e1|kCGV{pyXozg&-@#R5YcPp?KNC?ff^Hi;Bjsz#z$;oPZ}4Q9box z`#2sB(`dK=%Iju91XEF!6ZE)+v(qK+rJ+CtXtwlBIB_8Z(Fb?YUNi1*skh+^?r0Hf_#Brv87t&x`+lLcLWUH)0JMFd*aW zIcq-5nGK+(W|SgUBergR+7}P|rGp+c#%-j0je#oAX)&(UVYvjyl)70=89><@2N(mw zkqMMrTao~HI^ZFKLo~fk#e~$Z&f42Z%QQ>FHDi0vcnw3mk zPw&8~tI2^WiqJE{tVzkl^-S9gTT(K9J%c?ke;*@KiRT&(>sIFj4*%;lYMOuzDVeaI z!BUr#gE^5WBJoKj24r}a>%=x_S58G$x-QvxFxhCM9%XOpc z`5A0_IZ=RIxcDKGZgWFa3eYRw&&f^yTaG-tJiGp+vKBAN(fwH3(s|a!p>>8 zx#g+g`@Vyq++;cB- zAbbh{r7J8z(iX?vS#OC_2O(kv-?XrQukTiP07*yKoC3h;h8!>~-D~k~3x?D|1{Ow8 zoSuOhXRo?_xikPez;A;daIW9S&XpN>*#b#$s+176ghVhlHT881r)FT`-bc}`F#!A! z1!e;5$mZAp-<)T!9^C8uRtql@APEQzV{W&w^Bg~u@43ajk(S9Q0+ifxK-~m45NJ+j z$vKZB&UtfW=KsP>@7gSUbq1;Y)miw`Oz&vtKVly@3(s-VoNS68P?rM?PJ@{M3lIak zR(Jq^HNw@1c6py_&+i=>_`MVTzIQ_9D|&qIXa}m*i2Vcn{$NRx$*6LG2Cz#2Z4K-# z@Yf*-1gjB7F!bYBRC0->b2^Oc79a!^fF7{az*QGdBmAmgR0HSV9RD9n=ZrSj0Vcrx z1IPenKA`a>tGdsZ|1A;_IXyaO^f}i7MiRhr0=)QgRgaH%W+&z9oHK{y+%$CrO_Oux Kz#KvLsZs#_gT*BP literal 1228 zcmV;-1T*_mNk&G*1ONb6MM6+kP&iDu1ONapkH8}kcLzC=Bt=qWHSHd={<__p8M-Tv zWq{Z=k|Rk~J#*gVAKWEj-p*|o#72@7Nm(^`@c;hX_Es=Y7ZlspmM!Obe|PtvDH`;`n?U5Uh~k{TdtOp;mhg zs}(lI4hyl+L?+Q;Pxr(45uS~5A#B2qOA0_DiI84ol>`Y%gpDMC08mgw5}7>JvhGqn z?!pOW4rC)4BxDoS0D%Mo0U!{}fNUfo2}uNj1Sfyfh0~`QP5KU@12t~wmCr=zVcy0+q zqz;jQ^yrcO`?7Nko}1PYCq?D}438byiBPi~QUalP zJhd0IIG8(e%_WHv$%~f{?ln1}JNfL!QeG1A@|nG<9Ms(NBV<7$k9v4-GAyak9vGs_RlMyS0(aWx-ic9 z2VIsM%8n;LwEcX&C6Ukhu7k-FIe+>?n|QI=Q25dtcQxN$oX9!9f5)lSVw`inKIbV0ZT~t{w8v#_@{7Xc)95iWl=3WTbPVV0#x`e+_B!bND6F zWcxmYM~xqA@Z&}g>D#tcZ1G?(yG#NVSKqKQG5PXWtW~GB!DGz|1@f6?!hBs7iKwi) z`GyuQT_i3afC72pQ0At}WRM9r@4)cLf<=oNJ}V?l>g}e=NSX;34{!gV;1J7c=$6Hq qzYm(KERknS*j?Q{JWVe{H%+A6E*O&>k}-t~06Aw29hd=#e<}r4S5sX8 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index d62b6730ab7352635ef1ebc231be8c96592e690f..8cd64ee12d161275fbc05d638e7fb51b53b6234d 100644 GIT binary patch literal 704 zcmV;x0zdsyNk&Gv0ssJ4MM6+kP&iDh0ssInL%~oG@8cknBuUBcz2otK1N7h#D`-K% zKQx!WNq_0_`ycX2WC4VZadjqiV;HK z!y16`3{0`J5m?G?Ac*!*W6* zux5}Tq7#88)(o0Bu9NdN=?ntLZL%+eh`m_g7=Sta-I(ME@s1od2Ewo&TNxo&TNxo&N(Bj!7#jRxV15 zu^qwXV;DGA9&9(7F|^rggio6?9d0v9fW510<*PlQ#AZ|i_pj>rK&8!S03P%nfClG3 z=RfDaf7g5|NY^e5wH~XD6X;L1C)tgUZp`feYBkz0wbp7Z1E&|-=RWX<*o-OIetuOy zg1HErF#|iV`X@k`^PlryKxdz+Wfj`38PT>QWf2nxa_vS`KW0wnTa9{5?Xns@;Jdul zzFCKvRhuya8?X9jK%&hUg-t^eod2Bv0z#RDttYjY1DH(`sR!=#?tnTG8HTk-y<;H7 z11tgir`m_W(5FZauz9M!3FL@K2e5mpedL4jHxE#XsZ}Bp>Bmriq=;l=YK?fn&lN)! z-bF+*XUv$%5)lb%H>5Yz3rI-w;H=u*eABypGp}<>Z>lp-zWLeGH>)=1uZ|Lr7#tU` m9rh_dxEim%DnA&U9IqYwCeHuP|IYu;|IYu;|IYt`QUUH|2AA?#p!Q$ME@s1(f{;6{ZIeX|MWlo&rTZt*;IJuH=Z7wRtB)Od3MY) zkh5pU1Y`2-=wbBeKl+dU`-No&eA1ru^TSci$d|m&j$Jc@ShhYnEIEiw@X4{tV20>F z`j5p)&Ii&%xAye#7BlkM(bJ=zK`hvx99kA46Mc4MFkSQ?{r4}*rht*!*T?7|C}KDt z1FZ#7GxjYo7W-5iqPuzwRICF~bE*CMI4_qU>j&ZufJ#I?irZ9Jdbw6d+8kkFhfN)+ YC&JPptD^tufBK*Pr~m1H`v2c@06aL?9smFU diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index ab5d33715c7cf2e26c09cc29374e35444ad5d9cb..612a77e46296447c655a0f8b7226863f49da94f4 100644 GIT binary patch literal 5036 zcmV;d6I1L`Nk&Gb6952LMM6+kP&iDO6951&kH8}kHQ9l-f05+oZ&)!iGcz+YGcz+Y zGcz8On3?fV%nS?1d^^*9c7El7o}Qj=LDqG(>8)gDI}&wb5@efNu$f(B9%JV6$R(D} z%nVmsYi70u>uKXj(bk3&U1V4@U)37NE`~j`1*P~rYBRI=GBa$rkr^g1!=|2K&GuFs z#ENM$SBYVbVrHIb%=QL?=r0Bek|ZgTB>Vr9cK4W>nW?%58Y|o*qO$UYwj@c4B+35& zW0nq|n3)-RyLzY^tJ6IqGpo8zDA2ZT+L1=(+m&zI2+M8Twr$(CZQJgcW!s8vJuC=r z+enh+DpblzW%lguJzhXh`BpNRl?;l^$v`0`vsp#)e!j^-sr6OKd}G9Ki&;g*oYq(R zO-Kx;Q!8A;tf8dgv`|rsI4eBoKjervZ7$uu74Sj-I z)k&kQL8U#YG=NHzs3f40gi1i~6w^HNJU>uA=?P0Z%_N!fRZ++>AA(vJqtZKQXfY~v zqY`2v60!tE`VnfiqM^4@>*7Pq)G*9iQQhXKuE(I#%c!(s8i})rBnfT7T_}rQLP=tT zk}M^L@$zzaXC#hJbFVk-xj9YKS~J<}wMhAJG|HQ(v_qUjB+=cAF$pDb%EWlNd+`v$ z3tmxJahR6O<>Ghe2*kc0vDZTg5lKd5!OO(SEt8k~)I5aedcYAht@0&nlyubrsPs81 zjX}s;7>+P8?mQ2-zT3sf7>N5IiX&0!e+Z$)iz8TG?mUF2M-)fVv@fXwm6oQH-pfK% z`b!*{I0eWg4(^|H)gqeKsz`$<5>^Y}4MJ3sEu6ySJ~f1QdoE3DN#m*z>26eI7(zX# zP5r($VfxbaW9}upkD}V3(n`@GUxky5Z&fsnh8~A71;dd*j0M8e)AYR+edVQ* zY7xZI0HK8=iA3hkkDwKLRW{SKefn%v8iFZI9BE{-z1UYIl_QzFrsfl?{9^sf-x^x0p#HJ%#fpX&W0sMsDoJR`=&S4s zX|JW@R|})kW{8GnI8sU&nt_SsE#q~M(OLye`*02ob%-ZfN0Ly-#EKGHOL*2koJFNx zh=gW1j=UpbM&I~~Qi79OI$pI9YPE|enMVSFMx?-mz?1HSKjY#N6@g)7Qm#}gC2B3&m5#$+>C{Mu@ z90g=aWMeJ8X!IqhyD)qjgoIsw6GXv!otR{uz@cy5jj#(cj3+T>wetvR?n_6n7DlCh z!k+o;qY(Liq3Oqa>OJ03&v6G1z3OkzTY|7>!*VBM3nFA+sTyMee{_h{=x4Tpj9 zmMu878%vm#L}ucQRsah5YqUmt`!NEG!k$JnRsHoht$zL)?R@evICR+dn*Vb|DMfsV zu`-@FYUS>bAj^CLi@2_ypU~9x%P;iy>LvZYcm@s~_#Dk?IpS(!*hDNJYK#a@L@0|M?JWdHa0^kPQ<&Ibpq=TD3nAh;+AT( ziYm1k=W6Gi2ce0InBb%i`m1&rzQtE(A9`Q3QV~v{Kw`vAF=XX^^;`odwe<=1@cD7 zP#1E=I%oyTHFGGPXQW{SG`C;u33|I+Oji3OaNi5e;9k;WNkkR5Zl?p4<4%h|KKAxdA z3_qr$qjvD?%ouz1NY09n^CoH|kYqTKECj1|h&m*wFe-z_PoD`9Nyh&rKJhxy`}s(> zr;%<>PvB4wr;$M}N8-j6lEJ2YW6z^s5p*sLJfmCpLV+aVW}#h;oo?Z?KOR!50DJE#5NHfRRHv> zNmC9eJrdeWdHkCe9`DgK{KNPB0FQck{T0>x`4^f#&PPbbHtYyh*8ngrZj_`QC<34? z0L)3`&65G}qd0ct0*;{DNwP^$i{Q4G_VYqXqB;y5!_iSAM1oe6!q9cKs6gtQEMQ!F zppmiU4bpgq52uCFC~u-V8Q^#sW34zVu?WA-1w@*hEMP*%RI6iI=qbf7l;OuxhDg(s z1x#oQt?su2l% z9}~zD98WRY^vJnbw9!2wka{Nz8jbDfuVXBw(oChS1p-N=eUk-@meElzq?C(G1HhJ8 z+B8{kpBkZZ2LO+9DoJ$tfEIu?L1;1cVeBO$IadR4WY9@sEC;jziet*Z!u_a@TdKjT zR=M0wnub6RV79gWHsxXUpYN~gSL^-KC`GfN17H~=<9@ge)p5OSglaELiv zej94D4Iiztt*GuPr{X~+1+C5*3;Z z7%K(Mv`vns++OPE8B#Ia^+YAhmJ>Sr@I6Aj{SG)3{GE4c@W&s}H2H83&i_(i;3)P` zE&J`DY0Ckv5d-(^zzo0_OK#w|Ar(ViPgJ;Usp#JgJ-z-0Kft5rzxoPIqYwAQi2_bi z4OVqIfZ#0d7|`m{!34lPn*A)LVG&7+ZWFfJ`$v`xW;Kn0t|Bl5^w|9*H{L`Ma9;Js z$`G61<_iMK0izBJV*#L94?sAUy_VmGx-e%QqU#m_iS@p3!~}q{3@EmYl=WrGfO8*e zYadxk1#6H9%me^u7u}B&M49GI)BxXpjLexP#&XV8L>WwU05n;G?ISkwVtyN{;+krZ z%ryXFKUM0mF&Y-I3{rXDtgaM=^`5Tl%4aPlfz4o~v865?j8`cQ%3@2MJVQ+wE>cEM z@i6}?1%pYnm$fYx8~{Qi*dNU*ltfCx>g{a8R{L(}qQEJ+$3DCR0B^J1kBw_jLlYIr z07uN2sb|ioUm-uRh)>Jf`s52P0MSt^f1A~fg0T8kdh4v3aStDfy{zKG!3x0N#x|cC zR~Amwn0lDt$eCgz+kC<23IUtJhy%4p-rxZckDB>&VjDNg!RjyDbLGG93i43nfYy2D z0aoV*=XzWWX`DAv4HJx-o~UWAXE^5$HcwGCo&|uRAn?6wX#3*?Hf|Jy)mz(y==$Be z3IM;8RJH8JUv(}9=1wHHv)fP$#$IA*XIJ5HfOBqOm+maPvD2Izh3$I(D&CZW^&ZVDhd?1%L>5-*v+6PcnOm(VoyXNAA8^lD*IZAW$ev&s z>cDUjGbgK`>lrgjcEP(C7S0F408%}7f5y&6#_hc2#|Jd^{qz$(zy1bh918XJJ5=%a zzm&gh>F0ca>-IFFsqNR_2>sUEm~rSU%V@fN9^r<$ovG+BP~pEDg1`MPW;|-*+i%e{ z{%}vcC}2NtjhR6gqrSTCeHJ!|R{YDp0IXR<1VDC}it2b0ZkU8Q?a@^D--fFH`kQM1 z{0kha^6!7pRCU-9Wu4##xt^$C8CY(6Ja5wW%k4ySW3Cf5A9rY~`0F3=yh%%aTGPq% z6D;-(v*)cfIlRJUU#S)MksQFYQ6aE!)XcV2n8P*-%|AY{vIdWqtLiWi>@<>41@7F* zD?JQoaOwRWKR=<|Fa0}eUwycfrD zi6ut5U+Cv@AOjBdemOAM^@R0$g~j|?ZvR$Jl{_Pl<>QzK#2vzAePI#@ zh$IUonIpXflALn%3_9=rhQkvbeIz~ktgB=NaRB-LD!#X|aU(03-uN~lw)*??UwsuR zPflz3%)onB0EI!S)kU|_lYVa0Q`cqyrFYE~e9KFC@-8~%df7l|T>xc6b*@~+!jgJg z>}-1Pwhln$x(ltR)Jd*#*8s5Q4AEr^7|X(8E4N+9!jgH$wCT6%&iwY*tLp<%(K6(?kyIMyip#I2=EXW5i<-<031(rcPuSY@ zo4MoJ4o^(yWH8c1NoN3Hyo$;p|FIk}aMqR9HhL2&UsXR{FWuMXyWb9SP+cX^prdI5 z$MQ$!4oLtxE2!wZYVCwJQ@gUHbD5Om+JvjxXa#fpzklTN2Az>Wj5^Qd{c?b$gB6tZ zlJUdX4(uiLEZNEsiBU5M*7X-FD0jG;-}0W~N$@!G)i<(&(r&B0^oRHodXmjQN2)4z zIQr}UH+uDvl9T8Wm@X5DWCu9_C5_JY$|1Avv$63ePZi5}TiXnnwXNR&|G&=vb(ugk zU7)9qCSE#&QviTqE|3FIxLI$VHJ5#fg*2BJcC-w_cGl;!-wv58Pv*RHfRoe-!b5|H zBY;3IkOh#pg0jx5Js+uZ%+4ky!;<3F;Lw?4?RS6q`2h02OXKK)ZxVDt_DK)Gn_HlPxoJQwJW6e36ei6*K_K^09+r*8S-;k zL3z8QKQctt{pYcl(3=z(uqHhsF_F!{o$amd^W$>?DBS)dM=mV!9IC+K8R+=AEFqOS z6#VntZwCe2+7}TsMkaN@A+o@*imI@n5;iu&WsY>SwXgEnZ-@9VlO?1-=NRaTJr&ae zfNniMk!b~GmCpIp&|CdIO68Qb4Tne~Bak56U`2hPolWG*sgT|NtZ>e!iCw?-e589Q z)Ko4X@VSg3nG=*8Y6ayrvYsEd+uxC$OWqdwbhUOq@X^=XjWI|5VR<;arEl zZOP9CEU%Py6>d&@r1Q0Ng3@oyRr&0v_gL+V!7JOwb`Vzrk#Kb-%3w1vu5ia|ozz{m zFDst&8FPK(j@JedI&xZ>u3}pPFr@d}ISa_FdFWg@!2#`df3nZ4%c45QZ ztlT-hD@bzHM&vQ#rzca~-#S!yT8?Y*Op;3y2r?1Bd=K@^w#V3j6jmM zVU80?`~q%zy*GsHj@C9~WpJ@RI-5UN(^xLCg1K|~CqL7YJNvl=?kg3MEZ8?NJpd;F zKY(Z!kbPT5Q1bk_te~P0IJ)t<+}!2p@Atg5y1}aMi0T;D-p2aH6avS5W3K1V`L-oz{M=R2V^okZ@bp+XAgwci*phEc z?k`GN!Gdz;dTOJ4zp3qgey+!@eHNzsUwvgR9@M#U$co*=R&9%t(GxABf5fbVBRd91 z$moxn(KCEy+feCUgE=?$ow+z%_rEUO&OSeTf8g@?}deE;NcTXG}kv;hDt9-dMW zIRmv}UjW8w1Jb4e5Xl%)*+Twp*+L0`5^?FF-?;c8xzF{mg5lWW=XydZb3N%wU%Ssu z53z!Ik3Cde3!r4Fx1Hs>sndv@z z?l02wMe-LZ`5l*k+gaAg7!s+SuW+QJ$Pw2|M&vg<0GQTuN#_T^muBbGGj6oTb+drX z8cQy@#v0en7!n)Jn0n_l7Qb{Z>n-nr?NGr`2N{$;PJs)r=i(TU?kY$6iFD*u(girW zthaEWlaJC6q{ecnlM1{P;S}1mTV7d}T?X5sLwO>R z11p8LZD6IY0=ANaH@8!{DOeXM`Nc}1RT#FeQqO@())t(ojyS2MTWQ}|fxlaof+R_b zB+35&q+@1g=DTI;3G;}otU93}Ns=N-vj0D3;h343nU{Gt-90igtLuaUwr$7T>{w&3 zLG3nnGTYvM+qP}nwr#WKY+JP*e7Gn`k{d^oA_*gy05P|u-6yacz10vVG=vOiLr^Hm z>C{0!&)N`_2CGixPe%T8aq3{K8LY}bgYRmbUhxm;H9_m=;H29I1HzO=(`E`*-~7+ zjH^xCDcr@BB#VVunHEhlC2=w(D~aPwrr20c;p%oRJKlX0%d!S1)B6qBcmZDHOyEPM}R(tSF*?H{XwmiF^)b7UAlDgpkW5&@5A|h|uPk zSA3n!o;d^!$RST+8r*3H%NlfyfyiXjCCw4SmFyBUrr0J3H8-=Y zfibQQQ+MNzA%rY~Mr9IFyI<*;VOm-C2Q5|4(bO1RZ6+>RHBLjmRIw~xdK{s~oWKBa zE`&B`*;bl<^0L@;DVEd|uD}ox<{c-p8ht7guNX=DY+MbZ#twloa=6fcf+pv%tW%RS z8mMPUTn3p^^&Ef+8XI9?8-Dkb8dE=`@MwWyGBpu{9lyB${JVxVXlTCwZCLTrTGBQ~ zb%m7HmA#XlZKaXC{&Weh_9DEL78og6DUE{lzA9^#vjz=>r0mB_?ebvi3MsYMG$Gno zD^^nW;i?B=rL-V0cLjEOtC}#)MFU%Jx)_hz5VsebE~hoMvc4~#W#W$A@?i85 z>!`ii%MKcUP$yfBAJv#zOWa_%P7wT}4e#ZKjgTwK{4*?MZ1-z>9X=^2+5QT0}fPAQGWV7T83L$@97d;_0jmvC#^xq?I|I* zCs5G2Kc(^8&{y?mTg$~->1dM0^~gnEl>6~}3Viwj`9FCd3i|y)^etD2re>!FfI|Hq zgIy9oMrV`fzyGrO%U4;*unrf0@V>uCMZs`kfkP{ly!z%6Pdn*pd;oqC?6#;vHa024yvx{5ODezh#8 z-SgR#B24uo4lX#+Jsw!?PENa1jk;M@`VhGUxDZ?cG3CuyW~&-^{8LlnkW9(2;Xt0T zwC?mg>Q|g*S?L$#5@17cMV3$g(K{*cb;|QmxBwqQxJOJbvx@5GG)tpDkW7g%5))Zw zQmJ=R+`Up$5OTnX1JzDgeR@8%&1#mE-Y0e#5i9|U;+?N34RP8lrt*nr9-MGuwPbMq zwJPkZfP%s+!Cc>Q2eJroB30l@^oz+e^{BZls8qi(?EsY0(k3L+BBThGX64Kd09w0F zb=`9`3yBU`aUl1YH73i2*7gg;(bVCr4{Nh;6tKYC!4>fMNg7mhD) z_CM)1tahEMvqyP85(Rh>&=#wt)~GuEH?y948pXhjrLH1(*PEH-=0??5ko#sMoIWaXPI$*|usw5-tgoyb@^4lKfTof(93?Jtiv-UjjW)12q*w(f^{|Q#B5Ql)0`BlBdfd~>{4|?x#^+Nicdx_adp1 z)cBtjzV~lZ*jighzoky6$G`uv_UkWj z6(!|-_jLlFKZ0QhmQUvF`MH*a*Ml-Cp!Ay>bSwM)H(W(gKKF0JG-85d3M7>P^j(yh z3Y13%#_Ppfs8ja4uc3<1z5ieuxA4VgZUbOC)UL?uAp?MNI53Ckz3T&rrIJGffg|$( zMQ+S|3FJuBNl~UG*x^vQr2QdKn%PAO0IK&*mq}Ic`T>>W{-^IFU{l7pWZ5HABccO# z&P7!^qc2az$yoB(K};pq52&6bU$KwrMkQ|0F1e2grc&z%RF8#MoR^u}*MnM_#r6?h zU!Z!DCZUUVfn$d-HNJj8wK(zoeN5Lr6;#T6@UTy&hvB~_)e^3#Vn&{~Ycd(HUdt&} zJ?jU9Dkgo8-bYxOQ0>E^FhreLKcGyfeQI!Prvjxi0Bpf(&-%f8s@T*a0J++SfI<=T zP@n~14JfUJA5NkunA!@!ajsbr`&6I>km;*`MH#RY6PayhWmI!zo(g&Z6W!ytp@+9z zxcbbmQg5&;@dD5Ru$1fEk8XjTXrGv+reA1E1^{!8>GKfm&tV={a;=1So|o&|knkMP zB!1wWGq4&7(E&G7C8J7l@_E-M6BD`K%;r-E0ll78!u$|00Y-;n*SS6J_@m!Y6+Z@xsH_us_l-VLaC z*68q#wQi1TOM-o7&;YPJXCG0C>JvF;5%BCGYrb*~ia(_i-+Y0zoqTkTB~D32OLm+>Q(57FFN)GDN}*bOon|p(4+z=@r$>M zx4;XxMUFN10dS^XX(t7Max%ztmFW30EKof!a`Z>8iNTslKw1Eh_3pKnOKYSsg#!vG zp38NmpS_QBB8UnpW^M0a`;~`YE8YSt(UI$RZ39sL2d$YLyKQ2yy4Ib#uURyt@NqRd zyl?7R*92g*5ohw_s9=nk1S0;bL*(Fu6AE^^$Azg8BZ7%It2&Mi4gkf);*Xliln5of z#Vcm<>F@3s1G2PcRoF9c1CaNEwU*V5Q^)}$=c3}B^y9C6`WJ=)i~P2#BLLmdkPV=$ z^we+7VjzUKbXtB@<@R?aDV$X`j|x@*1+PB#sdcsRvBB`+Kqb4xryu)*3r7N*%}xRR zkwZc*fQlkrKL^LeKnHL6+%i-Do|Ac`o&v1dI2c&>e9Cn$_fcb_12!C}NPGOD*LlXC zgM!USbW_;?7)F5nb8^QY2gJlc25)hXS!m7g&KV90c2Vk9J@}G6$AZZ>K5?pW+ zMebDRs4#QSLBOs&S@qyuhJ%8%#n-&QQyf(AaTOEZyw_7{$~&cUYR^yzz*DTN;bvJP zKm?Czq0>q;HvsTPrz+Pe4H*h@wtwt(uBs7_^Bg>ILV>Qtyw`cg_KH)eZnicM2wV^uB+S_1<_9YLWMYw+VXj82K+Y+gmR~ zjSPVx+VX{UUpKgdlR38t6sYKIS_^Uo-=5&};1(_l^8W8X>OQ%Lv+u~>TMKCb`MkWkuG@s2uH5&+v^ zP_gk6JzuDmfGOF*e56VaFWcc=YrV1YNuf!Ko_bb9A5Z}xu=?bCkC+&SLhe?q=4e_65a%0uafyYI7${ z3p}I4Ky>$e+p04niY4odjMDmgt899?PGf0sRR4CcqWH$%xW_a4|85-b#O+J zG^yQlWXeh3nGojZ?;PpmVsfm0%;RIjqVEg_&XgnNP4q0=E&v=SZG3jlnfsB2u{S(p zR_i+S-^FHU>5|z7z!;(1c-Vc+T&L$|_ZSN!f6#L+H99Q2*7l*#%j}JH5$QHF z%hI?Kvjznh0KXf=R+(8-J*G)C&p4OEm0USyiJ1*4^vi79B?UmyK;Q=u{ND1bsy}xIQyw_Hi!ak*@l|z0otL>`gR=vBrtG40gZ9*R zWGH~bfxrhK;0BR*O+R07YSb$xoZ}hsqoLegqiKlT|vtRp^6<0Tv>u|Frn%8t@IipO6rB=7hbgt8ryLI_O$D~a4t7?6xwgHg+s{tVp z<_6I>?Da^Fo+m#kQCJ)rOn8R{Q`CrAXI0xP&;589fZ&0@%4(zV9jd`)Ob-Q6&^x$$ zg<$gF=VkT+M}JYEGh91Cz_h^en(A=j3Xhn4UBmA``m4dt%iQGly@Th%&~zhtXl4We zUFy|@=?0ORuYKyG$NatURMI_0z?8^I6|@^XF$a0Y6q-sdy!y}SuYH=_y*f2Xd4@(! zZDPuWJ%Vch2;Jib(T8xJzxe9E3#G;mI3t`GP4nzHiK1qQf~m2^R{t|E=Y?VSB(%LS zB~h#FYTN{1$=dT913>t)uvxEl`ux*hWS?rSm{3=NDWaq4GWc`3Xmoq!nE!up`pX%w zeMZ=22lWn~JF=RpbTvB?fT82nck~9{Zuf)@05jjP){`ss+)yMLJ0L)TqAlFHyxKKO zGzyG~ESik9e$BC;>K|r>-P5h>9aneUg=8ipOaMv%n)E0Dxxe~G!ab+CL42<3Jn!9m zy|?=G_99b>>M} zW-7Q0ORm17?SK&M6~f73ZZPQ~92dQ7t+!TKvpH|4t6YaPQ!|9(EEWqVRnjfo{|L!{ zfEbG<4k7+__m~PD_FHjP(}fS+$0cqsIV`!+$J^dnb&d4NU-#>u+#JjZMVNLq9aKM0~6zsZBkC%6F_;}ght6k$qlAWb)Ba^ z<6Jkr?%dDKfAnXIt@YQ+U2E4%?anzlns;Vv;ZAqaPG9k!10|DzVx7Jso$mZot-0Dq zvZZ#f(zSMpwf@@o_2+&zYx@Vo^n1ciOKz0VHf_&SQvg_wG%97xOaLfs832q;Q%*e= zKxvQQ?i&ILeM1O9NZxwSWuNIj=ylE?5tfa6ohv80&edPJwAFXJyTOzn-Xre?5NemO z#WxQ-x>!z}vNL+pb-|*&{g9wmCM-Nl8a4C@CpJYqVt&z2r66T8VLT=?R(TvfDM?%K`vIngcEX diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index ae5a179a7dea0e707310319706535417ab8c0c91..7f10af247a6c85eca8c571830b5f0e967c540b02 100644 GIT binary patch literal 1982 zcmV;v2SNB!Nk&Gt2LJ$9MM6+kP&iDg2LJ#szrZgL6^GikZ5a9g!_$P!5uvti8wQ%) zk}r6S7%3Dw2b@Wz!1GZ=KvkG2^F7F{9@rl^x2+v>GPZ3ywr!(MXI8fJ2h`hD(%H6E+4XqlnE<){|DS)f zn{C^+ZQHh8X#gjcZ9AFc6a2pIf02~wGe9OV%J&MHBlYi_;~rO~OMR-Yx@qp*1^}Bj z|G$(nw;PUO5Exra+8sx-tt$B^gS#(%`*C^io-a>;D+%;}0(jePt_5N(P-}r+%N54* zi@p{ZU;>N2#zcIrXkb7HEXvGWpuYI|&#$S#`Qv5zFMP_(Tr?2fS|FP;8`F5R{tuYR zu9>_TRkL|wHxrx1KW8SECZ~RW1#{D;$X;X4fBlJG^ZZ{o6Ifp(0jt;RJ^fhb*HFF2 z?5TEYtlGeM@qtPb|_Q1H`nJ2S>!7(Lh0JU0WSbzKwK|ciqu;@ftk9WJ=`(c1B1aZ z3Rq|6`WI*$}HxW)n4sXAfL=`|7;MSjwA^*H*ON%jrt0WX`J zd=B6z?}QtP{S%_h(c|c2CpEMWPXrVIVZu%%U3%F`e+fnK?c;T#=+e`!jN7~NN<^T7 z0n*shXu9-ppP9#8uQml;PnquSE$f)}e2@^x^~a1cjUfy`ZYCJM-`clbv(B7@TxCmS zi$v?h$PCSdFcTe^m8YZ#P6`~kG&n29xCjf)2pc0oZh7mp7oF}xt3KSNmyehi0t*ck zmInuy@2PUvuiEx$b5B#{@d+<3S*Wl4WzfU#xoxRh#MRrL>iB>wmlFSNRoH>qhRUi4 z@rLT586(Y$WQO2Kupn3vk={Boa${IkWugrc7Y2^d-&I?x6l%*^79Etwr(S!Z)om|I z(-UIX|3{&`7$CEJ(yxoARhu~Rz6NSiq)<)_ke%N-=jj`9+I0a`r6}>=W@GziYpPeY z`>b^S1Cb8Rh6_iA#-3qApuP0rvNh)&GbS1r)+T^n4qQo$EOcO&8X7=#cxd)FT8;iD zyS$-^v?jtpPo4~Xty_2Bh}}O2D54(_W+8V5RK`dBx=ZEtKHzJgu*83xjtXMHzoh%B zcGIP;U3X(28=y8N-Y+bN9&VrG2jarx_!2LC+w3otrf1oH_<^=&)B6ua^n=2J(Ej_% zfrr}gFsuvWACLvtRhUl|Hb8>H^m-!J?ye%f^YgUO06N|EiPoSguAoI&Q5A%BRX2tR zaxuVFXZ%1lKZXm?8x!6+9~IUn0=+!=!Wg(9@I{fV5UdFL1Y&EVZ|f?Y^BvRXlw1hU z#HQ$@eLW=1Ip^=;zBWO6rZqtPZb^C1Ody1M8?_m!gLe5Kd_U?m%1A6UIp~wws@AUe z$oM`lL=4%%1ElVs@mxLm&@?%5>YktME}`FNgMa`)rU82lz*(_+N|H4aFcQ~_35>tn z9UwX;H8_O;KxcqjapCUhC!WMbl7)I;*j)=1e{fM`g;sIIXD?i}3VMyiO^-&OOy2`t zKSy=NjLPh!808aQzADvVO=7Et+7T!xXiqHfw5+eaLqs~4uZxvRnx_`Qk61e#@O%L=x`|K4( zoKOs`04zXFiV#o&o}!291EX!oUi4^M)~>r*HI%rX8H zC;=VdJ=ncZP1^$t*T9JvU3AewKUw}k2VHd0CyQ2LqFK<_z0U(aUp4g~<0wWbb|^pt z_({acKcp@HlRcMDV1X)3)eDB%?V;V>>fhh~e1LyIs4W;+ zImZAjKpYQ!9K7C-MoR)#%RW)h|H!*P z5BTiM?fhp;G=kr+e&L(D9OEcPDK>xuC>2Y@K${$&`qA)4=2*s2W#18{+qdx&RUli*gHo z4=@033qS@a9=`(6TK*M}-}GCH^Xh(I&{e{CMcKZWo~i3U4-ka3u>6p_&-2%P<|Uu4 zgi(ufeWAyE?>zvZH)v4T!W%SbaDeywV?uqd62>l~Y}fG>EEsC>1q=Ed*H$7N07aBi QHUX7VA{=mqfV@o?0TLzB5C8xG literal 1656 zcmV-;28a1lNk&F+1^@t8MM6+kP&iCu1^@srzrZgL)rQ)(Z5aCh&r{ckP?)p(G+sUKJHZT zqfe}N2wx9u0{|@Z|4X&qId*OC3<3Zw)4$AZqriCEW`X`s0ROhluOYsM`WpIcSB&FV zM!f;!RYqA7M=Rby@G8vAGwaJ~#`z7MdH#eu{&2|5T=B?#4H+|wX^vh0XYJ(5PQHk$ z#y&CFiQeId?L_-uZlc5-$HYdNXIy_WzB>O;>;yk35;(p2e(rbXQBtGKR&~r6V<#q& zn5@(!fmWi_>rG=!syfa*N+@QYFvd>wB2ldb(r6{BNc5)panC#;I40x8x@GJ{9dAly zz3^ofugx~2V~k1K%0Vz&Sjh*PN1kzdlJi+tn4PdlK zqoP0A$v0-MeDuQCdT`TsR_mu>Xxs-4sB~q)j5j58Jb&NJvcH6cr@EQStr3YyNUz|vEa#9xwp0Dc8 zm}YlRgrjh)YYwpYcarTBgUKd}jP%X(J;oDf3To$h;PtPkCjfs>Cq*O%kw8+O?J*v^ zQxqNRNgx4)+d66Ta^z0Q2#NgSn>*?9a`>*&#rt~|38*}fZkT4s%c1vK<_2mOD3F#5$Y!&N z(caemX!zWv!eMw@<@?d#z6!bRfhp35|853#k6j^@0|5LE_N1Vg+YDYVz480*N40r~ zdjc+OF@x*ek7{Ed^F$o$U+2SrXL5~Yk`NXk#0m&Hx){Chp48das3$OV*k%O;_at2; z44=!aJxOPg2f4&}_-~e}J`HhC#D#5caGgXnysu*6cwnh~#t^9o`OMlCLAh|l7J~}e zY^4py?@L6J@8cm1I~*(>s?BwoY3*y=mtYZ_^LGVxj!mVJj};(#J!y9IJeJG_1bwWG zP-7ma-#0_`sh1G;u_A>3(evz#9sN*&NR4v01493*X}U7vWY4mR-%TF^x%N!=xA(=6$L;f zYaszU;PuMM=QR8{z=mPk2`S>pfVMO%%xp zM53fq+%*e(Jx6DCeH=%8646RnuZlbHziRfym#Rot)qWroL9zE>4^EkVtK@Yf3ao0t zVs%`A4+y(**4{Hc_@ashy&#Z-Am}YfRWWw@?)xBY;cB>+Yl;M5+;A;?Kp==X``*_d z>cJZ|SX3+)twiZN{(=R?qDb&o6}QjA@%O$1A}3;C=K@ACLG1$`SOTZj2>>C}h}>hh zt}A=JAFF?qB2`7xPT=?=K{~}J30|$@{$99nw+}n*)}y%qoaRS9ASkfvy1*8=tw7Ln zgij-K8u7cnXaAYI?O(au=bLBy|6q^)kzS^UdyEeMa4*w?vkbTQ{J&!N&!;{6!H)kq zIE@6o9N~Wew>fTCF-brG3UI)@QC*6FHHid~(}-FZB`)*WAlxFH$D?b5DEtiWNK9{R?6JKe~15j(FlrMowDXn?nY6;}oT?7E^ Czc)?* diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 85dcbb73f50e37f8c5dc54eaba500b6fe6acbe45..7c56c5023541dc95b2235664e808e2d6444a68f9 100644 GIT binary patch delta 817 zcmV-11J3-21h@u&Qb|TeFarPpS4BclR!}%hBLe^cFR#IC5SQdAl5JP%^X?wnXA|B3JOPxhbG z8Ml!~gI<{15-b|M35R|2BDsfo6t9Z)i|S`p@E`mK z|G|IoAByK6etq|*T=#rd{gz5dcy;Q8?(H}vzwk=)7Hi4Ik3NcbKvq`I=-p)vYkV&F zaRU*5QL^4G5E0!T7?J#pz1F*Ec5g&;;6L~e{`*g~Fm`uH)JvAmDp;?7s6JM~PFdrys3+`|0?ax@L@&Apx8Gsg z?1Z&+b<6E)VJ9q@HMDjMZo9=26~O0^$#){y9etN|#|vyI2~+_ykybCRIK+I}C&4Suo8a>$GnpxGf66E% vp--fnT_iaF%~^o?#@WrR<084oS(~%w8~ws}_lpGQrN)jKlkWi+1E045ljoUM literal 520 zcmWIYbaP{1Vqge&bqWXzu<-f9$iSe#{$RF1{-G7QDxUB1Bo6;K)tGiZ=*C=Omc<^E zM4leHJA2FAdRdFND<-PAcbP@ScqjebKdoh__VtN5v#)K<{eRa)PPlsKt-9;g?^%o! zewP-OwQ+7*c5aGMkk^)Jg;%&r3_fjl|7xOj=|yGetCfeOw=Ll2U}-dH`f$91kylND zjY09_5-F3#4W!;5>HUAa-|g4FdAvs!x-1E@kGZse zk;w{|uHQFO@=xsiV8?&$|KsA{pWp78eY}&6W0K13ZRa-U-rkn`w%+yEXVdwA{apU5 zI~@4`!Tt~b|HEk1i+2|mEX%4IDPUuZ?W<_@KHJpP9~^?dL5z+t1!HFP(HgX2yC4{RL_JZ1z6q{MY>d zf&CBuf4|SEho7`~crEQn-SH`tE6yLR^;Z8W^gZ?E(H-kO^_if?t>40Bd;Z<6N1`XB zFMauyW3YI-MZ9sv3D3j4s#TU|5~VA5E_<|Vlg~Px{0*%ue}2vRa7E?rl$eP-AGIxC z^-THcYR|&e>)tMV@=A|IInF2Skl5B8m)C8*G_|bod1Zp?8cU8J^8ecZKdeWi{>z^L E08)Y(Pyhe` diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 7da3e2afb5a3f852ab740e6d50cda6d6efc9ba9d..37629ff0cbbe1521dcd1984dc018363d0119c072 100644 GIT binary patch literal 7024 zcmV-$8;|5tNk&F!8vp=TMM6+kP&iCm8vp<=zrZgLRfpQPZ5aFivz=b{h)~=ERkGxqWEhv3nVFfHnVFe+cbJ)Z zmM#y^WoBmP;X8e)s?I=swWm&>>aIHH9~q8xZ?G6mcGa@l7;%qU8i>r)Guer+!=cEb zRnfeHJ6XQ~vpaKURb*^4bA`8Fg0I@l%=W+=SZG=0Y?gPkp=j6aHhZx~PTj_Qq>45! zW7KcJTyn%T4!2Bm$9YM{mK?EN>U1VD+gIw>uD(3vnREql`1wvNwZ9m@v4K`*vuVnY;}5Kv)#!;Wb8HbP4AWZMU>2rwj`(B`8un~ zZjB*w^!HF~q~u1DBuSG0zp}e$ZhKX_s+#)Jpx8!|BuO&;bz2>F@Bas#?j8}Dm35E+ zNs?(>VcX`l@oatDwr$(CZQFddZRh&-{vP1u770i*Vl#Xp!H1 zE75>N+~~8Xy=|OrLJB%=9>Ndr2qe=w#T-aaU%+ez&peMU<^t9H*m1!CKAni1<}{Xs&(2G{a&Ow))yP~@}K?l6)v1aiIkCV_pyu1-|OjL^+{fVXdIZV z&O{xJ3lA_6_2Vs4VrSD@gxUv1(F0Wafm-RPR4%exQK=u5WayF-l{BbCpb}tb(~TbL zQLp!PYhC#p*1Y>a|JN;OT<8*=U{Q35G!|`sd^eo^Bf2o2imr7SwO)(tVpQrhK~Eu) zKok%krtetkZQggb^_eo;GVSweEE082V4Qb~&)p5xnvX8sSPPZXRrw-wcdr`zU11**+@{mAaugSdpw^8KFe* zLJ3|Do@Z9eWbF`(XSlaDAN|X5pPSmhQs@*k{R*>vuOL)9hDuqeB!^g_k~&k(b5aSO z5!xzT>t`SKUH|D7a37w!voYU)q0(lNlO$G5EKyHdEmkcS(%<&K#C9iDXDdYGXmsh( zci`u8G6^P=g%Ezn2Q06k;%KLiroa3%W?L&BLfF}}A}hfcLikRf@#QqFb5d zpcMjTE-K{`?8vg|4B-{fU&8AgE zq>^2{aq}n`bCJ&`uvVhdFl^tMqeL*7P$UgEw^otOrK6pwbb&~vglKOmU(5+Qw2o9Y z)AzC#TAv~8Vdtn5t3kVm{iX6pwzQ5!)uM}Z1=z#HQ7V`o_Lg#9{fbJKROslIE+MK_ zFP=fM;M>C%RzridKRF(uibT5ABnFkb;En7Y^+L3yge~l@M$ITP+*jeGg&$2sB{8lj znJsN`laHp-PKC_(2xtkHr#zGwZYfcVEo50EWu{HmTb)f1O~4mr)5Dh4i>KLiGOm_Q z)WhM5!r4p`S8t87>10~X6agxA;EKx0%`A78PY2p`vV7j4Qmuq()Ep*|Fr!^xwxZFp z%0zmGuaA{T%E1-ob2=+O@3NhYULu{eO|WGT~v2t0ren&X~|S zZ+y#DS0Z3F)7gWi)1gw&M7lhfi?s4&WxDA)oO3LbC!;%njxSSw=eB4U)Lu&)_3fgG zz}l85U6aBlSh^*vx*wxz2$p2b$nbDx+)$e?W@sj%y7I`TkgDeuY%SuMGBfSf?hJrA z{oDNi+W!60u}XhHM(2JD%9+kanVH45SCFP=oB5xAT}4teY|qJtSvmiQ&&O-aVXJMs zPNr?MUUqh9nf~BRaLHAWtCfs;c3bYRkGHKD=t}Wk2xtqW=dyd^^yFkt22s!Vb7r#z zlL?*P)r$51FRTC;O+?*&x>rr!DQSiDCbpQ&g3y;EC4I|ff#SKQHcHJbvFW6yHB}r$ zQWd6=8d(jj?bK#VyGcvBqp`Jg5&JU^@W8P%$*5%CHr`Q6nnzZ^Z$lFYAefHz9ZCKs z%2pZlq;bY(6j)y{4McF6-76)35YV+Lq|i4uU!De`FPPxDh7JnPEHZlk>F>1V{PPzh zft8M}V1mo+P&vsx?ce{H*__^GRbvZlDYk+N-rmSYF?*9SC+K^7E%w*nrb4ZJ*njef;^xe28v$bt^`!d0BMvAQV6@j=haD4Q*> zgpdavyuE>gJn0=uubQ1k1oT=`}d~l}5HuAMkxx8i`4781h0V9D{0u)MW5W>zXp>&eDJ1{q1(ree% zTz=R!2w?;gs@ua8&P03da#h#N%2tR<1P?-ZR3he{dgiEWRWDuZTiRbC%YhKCN0;KM zIJ5DRUb!yy_3;);&7g$!tK^7=+lTFy%T-;CtmzOkV1%*Pv5|FZxl8i9mtru`)z7+g z8k#sT!kL=d$m6=D{{e;MS4IR3TT1`ol>mu`?OrTNN5EA10}?x~{NIa&bwK$A+p$6- z8cyN5BsRXwQd?`33G$$XhqWBdq?6f}!9ZUEw(rtUuWW}V4wPt2sgt`-=)cP*;ih**;XuaY0XlQ(s~x_-`QNeP))HQqucmp+vD{lEjYB&-uEGJYqx= zs@UR@y2t2Wkk~mXKMWBtsJlE1O5x-^*NQ&a0>Hu=kk`4ZuF{Byz0cLXDrgjbHbOv) z7)PUIi!}bcY+bF^WWeP8lrD=js6?b@+jG<*fs=rNc}D4i6n@V!Sv`0l;O(XK{HXlw zkj#hhmq#E&?7bM$$kf!v>^2n&UknQs}O$ zH0m+4ggi6f<*N{K7|^k#HJATM!FfA$97e}iZZdq9McG~(2~3bxY_Ft^+b;Vk_& z!J?&=4F!@0s5G}xBq`Y(*y|shkf76ob1S(VtT2A1?ApTsDToN$3kHqeO>QkU0TxZ! zRND#9!wb5QjR?o2`>}>BHD}MzdUO}EU z9VC>*ptV#qOu+Of)rFD>e?8;`$Xtr__9zE zau_g0z^cH^JZR^^2@k6$#E9MNWA0AOe!&C*%W-BR1kxa6zzJh7n+%;|pGhfu2U9|f zdjKN@ERM{kwy^7APk`cAWU6&PJiLHifw9p>6O7t67$9JdfYVHJ0u-VPt z3o=_;<3{S)n5cc%uxt=Ei}>K4!D{`S$y|}FvNIIngmzCL{bw;*sM)3hwUMp$KmB$;@3Kii>DU$6BH&zTYKKUIod+R2 ztnOgsJ*R_r0d@|hB#iob#h zXG7&L{3`&WVIMQcbUFe)Q7tWJBenq!o~{Xn2L;Ri!5INkCFPT!&{1hXh9CQ%g*6ns?m(7b{=Iw$D0^Z|wukHdFaG}m&A z1S~XcV;fmJ1qd)A1_V1#Zenu4w!kfQ%=4_B!6xiHsNe+>N+rbU@7htJ{_EtM! zQ{a+1C~(V7@_T)f1~h0gm&npC8w7-@n*`SYT;F?(MecKrfS$yp*GZN2Y_Yz_xgN$N5|#+6NqHG?y7FPMhHEs z=@X-G-XCw4=?~MJThsPka-{AD=Y2LqirM9RvNsc$cmQ`B zD|II{t#e>9I=^I-QVU8M1rIYoM(18@`fA#kB%O@_cb-APlkkl9kA$ti86ZXP@4c~Q zlIG|8D~Zw;?MY%?7;OOzoPg0nYiczqrA`=^bj}0thtW3&KoGDm=kHI>;3xU7X@%|A^3f>}65nv2)p4C+n=^shl++c7kgdgV>YPI2$H^M(Xt z8t#nLBm`VH1vCU$6SU7f|Hm<${AkpE#Znk%&()7vqZ@%HE$KW1m+c#92(YuKUevMC zS|)hv2s7Ym@0|5)`z(88^Yp0%yyF_bSb3@AiUsKhU6HM4i zMSv%MVmj~>r6_4*f;SD+^KSdzz5^^U8+zW2gJZN_)4clv?9*+F zKtq7JcZe-8Ro&PI#gnaTyHfQ;1#TV~A9L9EPA)uYxwgPj zQF%hYubFPx*@ak%%+>$GJa zc6vyd!)2tUkI`igK)@m40XrSzJaY<6iyGOWY?ECu$8_fr&$Hk>m0%4pyD&`_X)&i* z!#4-4PDg!bR{nI$gZQh)sqWxhow@_>GriB2Ih=5Vr|IH`;DuhP&z|p%oqRg)ky_dWL%F9tzw*SLqTRfA zbe}Epa2E=W(c-4dEcNox;Dt5^>^@uK3CiAzuaw}Au0*Y3Kdy9wan4EEA~&SZW`VO% zaHt$tvP-2nu>*a9V8T5w%ck&rluEXlgu+u8<(~KMPWK%yE+tm7PDA)6fYqrl;EmLM zwwNPrY~!zZs;E4>MD9d3SofvKPWK;f@KiF-7(>EuVC;O^nQqq4M9Fc(h+wO5H0#&4(Z>E5xo z+zjx3BHfyzwTO!p8L&PA?#o@LPhY?v`Rtno+mcJPwlxgtRnvmgwj@KHbJyMHI?DyF zAAr9rIno>u6%n@yVE0iS=j^4qTBYSv=#t5kTFvy3u*NxB<;OgGg{sew#zjO0m~lmt zbUEUaG8P&TnEsLdf&Z9$o)k)?b#3^mNxS3JA_5kj?V$~$P3A@1Ub9Fj~IQvFl;2-J#|8WuPr^xk6FO=|h zE#|PK3yclP8_fEvB0$W`4>>DNu_Ai>W;N-~?qi?5Y|?f@k%_YR0<4-uJGSE`?mFI?q@$g^(j6JwHZIT_ zlSVv)WG-z0CeE`7;0yq~NcTy8-_7To$}BRaTK$i(jfNaMzYc|`R1@vaI?+8_-H|u> zta&gsKC7WPR z%G0c^x~2c;rbZy(fTaaQGWI3o(hWXgo>cOt&FBh}r$l8WEZ0`N+5bCJM%d?0 z)=p*dMDzSz>?|f|WTCLIL+|3#>MS!=!=8O_pYNT5?S8H8atRqxDe@p}CcelliVuZk zgfG|De^B77+?#-qlkc;0yw0yFcZ)7r*G4b9NiA+kb*>5i(S_G|x3SOub&`8`$=#~1 z{%w~F&j=Bp@}!b|2XWZxHp58mrt%_XCf63(%x`o8VoAoZ-jsodv6Oxf0 zmplM)VsD~yyZ8FuiKoBk@Ypjya&&46w|42G{G=&_R_1 zMW-Y=+cjTt+Ix=f<-L>EcB^g@`%=kq=^37P;-b_h$`H8FJTEx_aJ!0yt=}8@@W9^m@m)y49VzH4b_u5;&H}dfNEWYBXGcPCJ zla#$(l`rWjnn>qqwd#jYGc85S1#d5^Cvwy+f^TSu`3>K zpC$kC?rv3N>|*U+GC3}xo7E>Ejc7-;jm4o2!|V~5fB^laVi${4sod^&-vT%6sgHK= zx-;B!Y?$twqwVv4!qopPsjTen(p>GDsi&IqPPGU(f4`xhNr*h_|bm(YJT-2f>u%VM$U_4*)O==FMw#o`j5mE;9iOFRxA ON-T_6;OCLDM*{#6#_Ie4 literal 6724 zcmV-K8oT9ENk&FI8UO%SMM6+kP&iC48UO$6uwEGcz;e zGBY#tFf;QkHFpm-<1jZ2b2AilW#$>MJ9P5o$vm0o{4GI7bQ@yyIw};)marx&Ld>k( z@s8axhb)c3jEoh_uC!5wlE|u%e7yTg+t&yp3{?E!LE?V9BZ#bGh9SbrBh2 zS(_1ZYsg!9IbP=oELndI*+L4EY@4>N>VK1-^=;esKXOYmspmjC#Z@eBGt+E9nBI!<&5AvdVXO{1<(c7a(c}CaXIwMve;~FckF7 zL!h)6s8nr+A|a3<3WyK$cdzkA z@4V2s^j=Z5 z6-a7`i?`h1o)~!M_Z5-}L^UB1R>bzraFwBOaC3qEaa;Ng)=fq})Wyu~G?+;oAya>7w`h z*8lVZxSOYMZ_M{!s5DF9N^56KsgmyPwd#eQH40Pz^cjNoAG6`lA z3xx2y-)&VD9b3C&P_cTRU+%%GyjMO1PXPZ@$r z6$C4)EW#l?89F>zC0C`~ABTHJB^iXR1QIOu zViO2Yf6u=lm&Z_DqDhB5WYeVrNuP4oS$|@hb}{yp=Bu>j0M79`FFWI zjk-48@E~+ZAnp`35B<@nQq-kNlixoo9Tazpx`!4T0F6kxC2E=`Zgn0h;m{N@c^lVZpa< zL73rW=@aWfr;mMmmP=Oh7z(9d53iHK3^PlsU^1Nk^7DCGzEl~i7`eYI64m%2>_M~O zT0uYS=e+3{YCJ+!Y4T>3wy0DC&+cUD7lI)<^s{T`?{uWXbyaRT|56WB65^7U!`~Jz zc&U%vt&;f~fj0c%-uETJC24d;KUp%rGQc70WsHLtb;l=dhmS3pJMQa&GduO*pu98IuzBL*`GYf=ZoQ))Ebxm=^$RL}#qEV>{X0U(=i>|KSJWV=D>LX1) z1DFUjo{Kfn`+EL1^VqM)0}Dr*bWJJ;Z|I=xl6T!$;Flkm?dwme{q-kE9&>#AIgS7R z1O6DuYBZ6kt(XbuoWvK{BHkJ^^NE0NGeL*yN_AF`3#v!&1B}b|ChPlJRH?Q zON!S*K!YcplcCAyzrU&U+B1-&V~sC9M0QV~4mFdjYPm{!9sMc;Av_{WQEGhg0kV7g zuyt&vt~uHfBQ>p#Vk?k7V;W_VWy3j~Z$;D2(vmJ_3XpbSAL9TItfisM%=RJE#iXS9 zWW=J95Sm#4!30RmcvfhF}Z1bQm zsL0e8C?%dAYQYAhn>~>i->V~;CKprt+52Dy3pUt{dN$RnPpdCRqCQjZocYpkXaOCT zW7cr~kJiJ-G&X2UjJnD21PD3M!8zx3D4U+ImelMvA)p-~J;dBJK=$ToQrQUTo7|Gl zbNd!4keZsBO27TQftl?b@D3${ZNZfe;9NM%}0$CqS3_3h@tm;QhfOVG@YV5qr+CE2d+ncwOMAp=Gj=bSc0 zs>XT5zxGlM1X_lsLz|$P1tYAvj!jwDLBkKL#J^-jK+j)#22TMqys!!=-&oqrmk#wQxo*&MH61z*PXSIW^=--}S6#1id@*>g4UGMze4x_0YO3POz``Sj zAGS@9cIHWcpoJz-#5i3=uT3F;2sjH52{@GT?*}NYt=}!}aQHAT~NCF#lfHu(*Zj$BseHWbC+GO? z3(7osl+yR_gG5aB=pkx<^$A~2A9Ue4Mdw`O62m@0*V;hZ4z*I?Zp%G;oQ|Yol1C3x z|Ic5b5#~BJrK^rvZ;P0#18oEh8_O4^;;i@kPfYgY5jv8KI=}t^jmT&uQMTqih!_QQt%X-b@})+wk|&UuIn{iKJ=3gWm?Rx5kc3XaQqvI+ z+N&Kg-+_R^BJb#IFVfm>yO? zNt7&*WT2&HA|JfdRllZ*fbUS2sF@^?#Gs|7BRTZBx+q_Oh!;>6NyqagA!sc%67ivX zbx`gJ42T(jRTN)p04*~mO$XRBgP7fbo^tqgga+7^pnYympQ_!{K9iL1LChiM_H8Ep z1nujVX_9|CTSp`@Hv+oKn*~>YjrV}%W2)I-uM_;0*A03^tffT2x~W|X*w^hoHs!nD zAmFIHGSDGJkAPJp>nTvtX*-muDvZ~!MS#O|Wnh$9Pd*PBpwhsmRMp+gKleC@*a`v5 zxsz>B3sTI>9d^zWfm;!x-Z}r8AYd_*G7laRq=e69Gj^}_kvrthR87bZ!3Y8KiZzEq zCkIYASUXV<+iP0lWr(>!Fhsy?jGC!DsTnfhgmF%nwCs28p4rz8MueEI1@sXx-380n z&;9}P07_8Rs(&}Y5vI60Ffcjf1g$O~bP+H?z`miJ4SBW_nCL7`Bqm9fBj6Hs*`Q~V z6JdB{1Z?L`j=@s^lm%@H&v|X@M>Y^+yE-sMz-C5eK0G1^N_dhwd=8s2(wicF$VQ06 zvsLEdXZLt%Cj&|tIAbC|=-DGPuL(>%4q{e{+{4@Kq&4?j82Pv0=*yRT67jpvX&s=e_%HNN-|pa1`ceC14~(uD}PW)}hj zH8G~pm5nu%O;~|8H2m{dCVu!Jq;BmTGko$6KRgZb7ak|6?F z;bdq-t*<_Yd)Ti6%ng`R zT%j*8ibue0KIIAgD*&RiJY@0eA_P3+pH)~9SFm0_SjSeHbBXSV9QfeEYbA`Z*0$In ztH&W0b?C}r``Kq&6T2Gbfe+?mDtFiK_*~t>t{5ys)nbvG484O%1Oo1JICoeEc<8hX z*c7SC&WY#^Obysx{8oSEU>j1>HNBY(WIzYwoH1e0UduzS4D9qChXkly1As-?wLo~? z_>(ga2$1}UQ<=)oD}~sw{=pL0Re~)7##aOm(NiXSgUq1AJgdVPbDoWU^%UV=qV45@ z4Fb*w?A57}-2@r1q5YVusg~W&-7ofXz*b~0!Qo!Ptb1@kz-wmZW)R;|=s<>UlerTP zSN$~k`5pv!T9U0T_p-q*b;Q}j8{10AfDEl}N=J2|UQ<#p3+!`k*j}=&wP&zEzzG4L z8dW(#rvMl_%ltNldzB&JmwsWZ#~~q@`3k`i0gthIO=<@jaG}*rn{cpw&R^p4!3lE< zk?rM|6pa-f3aNlcxn z53*)Gvp(#ChtEx09At6b~A=)lD6Hskhul(6z0V4ca} z@L*HUZuZ$}4yQUO4?yU&9VV}?yP0$K$-!n(x5SMRFv{Ekcp%_gJiW87%@NNA9=IdU zkYd=ij${o8o|(D$&ZNqUKEN@jR|uWH@}kox=s<&RljRC`zwZSib9x6S1+GqRvMB>! zH*1hJN6qEPgBZY|)B4&}cI5k7b86P>1}cf_WS%|tUchttiK|c5vB}ec1x`Ct!l-jy zS-xlRvJ*XXNHx(%_69Bpco$+vOq;xGcM2Uy&{^hlDAMywzZ@~E^a`$#dfYRI^khhh z7y!Hy4z`X_v)Pi5k)&b$KYn5Ir;kF8(hu&Z?r%RK#P|PysPg)=kfhdEpMazG*PlX? zvddf}aUi?d_NNa8hj1 z^xwY_!W%bc`s{s5-@6ZTf24JO{Sh1u{``%JA3gxNk9*JX@jFPO9e=o9$8Yt?jM-L? zNJ`BG zz2q_Nhi|apuRkF2OIh&e@0sqyHz9FvQ@{Tjvwr<4{nMUVB zj>t`yF!EeC+WvI6;2x1es7a-vOJV<%<-l1TMf;yQ`ap-RetWt5OuE(W!xHt9X=Gmj2c{PSKLi4= z8`@Q@`m%rx&4*<*rmc@ge;}zz6T>T)nDP{+@y7DCpc_ zlf{d73vT>e{XY2UA@M{e>3R?_iO3(29n^L=XZNq%C7|=z@p=}4?2+og) za(PU0>0|O(6$sd--ecR{u48b;YQb`}Em{^YUR}Mjbqv3SOtmYl9!!(PuvxS1(fvSB z+g+VKutNIpF#~KREicMhIv4FFxYM>Lh_+{2i91NWxiR{iy4>8{y~D9p)S|H&vvD&pFfblt!8NVwCC0wzvt^~I9I1G z{d0sa5tgXOb(h&NC?ms`=m&zk^v@ArtSf8ggg-h3z0&eA(`a6y|ZYeNXmR98dH0OfUY4p?tp7eEmQUTftuNBhAur` zuW0w*y{|PZ?)^R^)tD$QW!Nm+p5G1l_6?z3LFl!dt7GBre9XnsLG3hUim|(ku5F)P z&es=&{dB%PTW=F4C5B>a1YA}*W0$TVeE78vRO%KTFThqYlvmA*Pu?XOZ0#f7E@xaN zWNQWfs?-=(MSN^>ZxGx?v&Xf@lHt;)+GuuQ(PWl zQfGwbMjBJ^;-u%(?CBiiiq3wq-b3aw)J(Isq0IeEIOnU~oMV#ad>h|IsxdM*6qghl zYJ)O+>)7NTAt*SmD~KAV*PcnMt1i;XqNU(PEWS1uDcQ4XkhS)A1u=2K|DPPYb+*!= zmLO$*U7Iy3vj742EKIzCefAiu=Z%Wh!lyrmR!b>Gr`5KZ!=_v!9Hsj9^_{Di=6om3 ziY~~EvWmGRg<9k$+C!4NhQPicJkGm^wZ=}^on5se?|cl4mO?8_o8w(-tTl6Yz19S8 zTvChV5PM=SaSw{Q^a@P8i{2ol2MCMv?(^k2>u^;iRjU;1{t>p&QDYU=s4e{i1=5vTE9`?qa*3X{dsGlu8G9))|;eTFiy|RgU!9++RoL(tBG4Cc==UQ(<2g7tOgHm zMS~%-h$zTU&gua|lFq5|dav_bJr1(>s?lrSFW&QOl`hemi$7f(VToZl9X^PDyKr$f zhQZZ3&F4@fp<0!wMEBRPKG5E4uH#%iH}q=Gne<#)$@vAMD`C=V5pN7x;x3-E2MBF1 z6fS$8Y0p1+uYF_Je0H`9RV`n$&}p(iqlfE z`|G&rXM^?Hzw2{MzN~$wdoiVb%NBRBM2In-ctx*8t|HK$o|>H1qUAD4aT@Q21MD+x zCv%TF`G_?yj6Y{?vF^|1le%h&c(K>(tcz*qBjINggcEgbKIV@)oT-k@;#;mcr6aD8 z)RpM|{65Dy`Htrv{U2}Z)x;$&)ABj9l2g;|fr?zB72Os#mp%%!$6^8k{?C=POq@pJ z^}cO?oXL;9>wQi==-z9G=yhPExv!0zdR919TCDqB$?mU{R{btl^{=9yL9!};@xjAId1jSk>|epiU-+y-L7B$KlOY1<4k#5uUDgqTPEqb zmdBJ>%paw9*+f?~T&QvO$OvP4YR)PNT|>}w^$KC_le&j+%!45?0AcO*ckF;{0mS6)go5 zW)Bq#@%DlctdzBUi}yR{&-TX=H6UkHQZEp(qDD-tc-Kjf`IJ9<&i8Avd=^#qjQq)A}e9}TixjWTDA-hZjVK9aLbnOx6!RGpOAGI z$vM&av0?W3!0ad`rV#l-AQ)VFWt1W^Gs12xhz-e4jb0)rIXQV1EU->FOGKyUhr|{b z?Gc%giYTStWgu3-AmB=&R(&o4#)|C3z|8mv1Z?egqtO@^hWYIFts~+y0~50qDY<%U zD6U4JfJ4BQa8{Sqs#oUbrbI<4a1j-ilAEj4TdgjfS6Cxb0A$4-_1J7SgTWAr3xmO6 av)Metv*NtqDhb=+Lyd(A3w%FH*5m-*`x?Li -- 2.49.1 From 2616f7c113abdad9a1d732c1034b912ed5b46f92 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 18:08:03 +0100 Subject: [PATCH 300/563] Refactor FutureBuilder logic in HomeView to handle empty game lists and improve code formatting --- .../views/main_menu/home_view.dart | 138 ++++++++++-------- 1 file changed, 79 insertions(+), 59 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index aa21f76..a4184bf 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -138,66 +138,86 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 40.0), child: FutureBuilder( future: _recentGamesFuture, - builder: (context, snapshot) { - if (snapshot.hasError) { - return const Center( - heightFactor: 4, - child: Text('Error while loading recent games.'), - ); - } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - heightFactor: 4, - child: Text('No recent games available.'), - ); - } - final List games = - (isLoading ? skeletonData : (snapshot.data ?? []) - ..sort( - (a, b) => - b.createdAt.compareTo(a.createdAt), - )) - .take(2) - .toList(); - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GameTile( - gameTitle: games[0].name, - gameType: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(games[0]), - winner: games[0].winner == null - ? 'Game in progress...' - : games[0].winner!.name, - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - if (games.length >= 2) ...[ - GameTile( - gameTitle: games[1].name, - gameType: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(games[1]), - winner: games[1].winner == null - ? 'Game in progress...' - : games[1].winner!.name, - ), - const SizedBox(height: 8), - ] else ...[ - const Center( + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( heightFactor: 4, - child: Text('No second game available.'), - ), - ], - ], - ); - }, + child: Text( + 'Error while loading recent games.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty)) { + return const Center( + heightFactor: 4, + child: Text('No recent games available.'), + ); + } + final List games = + (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort( + (a, b) => b.createdAt.compareTo( + a.createdAt, + ), + )) + .take(2) + .toList(); + if (games.length > 0) + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GameTile( + gameTitle: games[0].name, + gameType: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(games[0]), + winner: games[0].winner == null + ? 'Game in progress...' + : games[0].winner!.name, + ), + const Padding( + padding: EdgeInsets.symmetric( + vertical: 8.0, + ), + child: Divider(), + ), + if (games.length > 1) ...[ + GameTile( + gameTitle: games[1].name, + gameType: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(games[1]), + winner: games[1].winner == null + ? 'Game in progress...' + : games[1].winner!.name, + ), + const SizedBox(height: 8), + ] else ...[ + const Center( + heightFactor: 4, + child: Text( + 'No second game available.', + ), + ), + ], + ], + ); + else + return const Center( + heightFactor: 4, + child: Text('No recent games available.'), + ); + }, ), ), ), -- 2.49.1 From e4abf53f66c3fca02c0f31f9a6305258b4bc74ce Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 18:09:45 +0100 Subject: [PATCH 301/563] fix linter errors --- lib/presentation/views/main_menu/home_view.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index a4184bf..a8ff0d3 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -171,7 +171,7 @@ class _HomeViewState extends State { )) .take(2) .toList(); - if (games.length > 0) + if (games.isNotEmpty) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -212,11 +212,12 @@ class _HomeViewState extends State { ], ], ); - else + } else { return const Center( heightFactor: 4, child: Text('No recent games available.'), ); + } }, ), ), -- 2.49.1 From 651f210ea8e4ffd0ac29cc00aca81b1cb0d1fa3b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 18:28:10 +0100 Subject: [PATCH 302/563] Added ios icon --- .../main/res/drawable/launch_background.xml | 2 +- android/app/src/main/res/values/colors.xml | 4 + .../AppIcon.appiconset/Contents.json | 120 +----------------- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../AppIcon.appiconset/icon_x1024.png | Bin 0 -> 9002 bytes ios/Runner/Assets.xcassets/Contents.json | 6 + .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Contents.json | 20 +++ .../Contents.json | 8 +- .../LauncherIcon.imageset/icon.png | Bin 0 -> 9798 bytes ios/Runner/Base.lproj/LaunchScreen.storyboard | 28 ++-- ios/Runner/Base.lproj/Main.storyboard | 13 +- 29 files changed, 65 insertions(+), 141 deletions(-) create mode 100644 android/app/src/main/res/values/colors.xml delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png create mode 100644 ios/Runner/Assets.xcassets/Contents.json delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json rename ios/Runner/Assets.xcassets/{LaunchImage.imageset => LauncherIcon.imageset}/Contents.json (58%) create mode 100644 ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..d3f4435 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,7 +1,7 @@ - + @@ -14,24 +17,27 @@ + - + + + - - - - - + - + + - + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..3ff2769 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + -- 2.49.1 From ca55b99d036476d6f25f6201ca1b5bbca3eb45d6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 18:42:59 +0100 Subject: [PATCH 303/563] Changed launcher background color --- android/app/src/main/res/values/ic_launcher_background.xml | 3 ++- android/app/src/main/res/values/styles.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index 78587a1..6d0d417 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,5 @@ - #E6F1E4 + + @color/launch_background \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef88..da964ae 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -4,7 +4,7 @@ - @color/launch_background + @color/app_icon_background \ No newline at end of file -- 2.49.1 From 88fa886ea2efc50fc9e0db9ebce5aa139dbe51e6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 19:25:08 +0100 Subject: [PATCH 305/563] Changed launching background on ios --- .../LauncherBackgroundColor.colorset/Contents.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json b/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json index 14fbbde..41fe6c8 100644 --- a/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json +++ b/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.894", - "green" : "0.945", - "red" : "0.902" + "blue" : "0.043", + "green" : "0.043", + "red" : "0.043" } }, "idiom" : "universal" -- 2.49.1 From 46d1c25bb56d0ebcfdd72e6d0fe8c6bbe7529aef Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 19:41:57 +0100 Subject: [PATCH 306/563] create GameResultView with basic structure and styling --- .../views/main_menu/game_result_view.dart | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/presentation/views/main_menu/game_result_view.dart diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart new file mode 100644 index 0000000..d953b0f --- /dev/null +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; + +class GameResultView extends StatefulWidget { + const GameResultView({super.key}); + + //TODO: Handle given game + + @override + State createState() => _GameResultViewState(); +} + +class _GameResultViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Game Result', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: SafeArea( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Select Winner", + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + //TODO: Add FutureBuilder + //TODO: Implement ListView.builder with RadioListTiles and Players from Game + //TODO Implement Save button with snackbar to confirm save/show error + CustomWidthButton(text: "Save", sizeRelativeToWidth: 0.95), + ], + ), + ), + ), + ); + } +} -- 2.49.1 From 937f1e3ac876469080e30286f7f361bb2391f060 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 19:42:31 +0100 Subject: [PATCH 307/563] made settingsbutton redirect to game result view --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 71a072e..18561c4 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -56,7 +56,9 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute(builder: (_) => const SettingsView()), + MaterialPageRoute( + builder: (_) => const GameResultView(), + ), //TODO Replace with Settingsview ); setState(() { tabKeyCount++; -- 2.49.1 From f21d0ba4e8210bf4501707fe2db00a074a63bfb8 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:04:56 +0100 Subject: [PATCH 308/563] move game_history_tile.dart --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/widgets/{ => tiles}/game_history_tile.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/presentation/widgets/{ => tiles}/game_history_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index a0ea4a9..8cd7882 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { diff --git a/lib/presentation/widgets/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart similarity index 100% rename from lib/presentation/widgets/game_history_tile.dart rename to lib/presentation/widgets/tiles/game_history_tile.dart -- 2.49.1 From 6dc74ca82ec58043d7881131bfdfa82cd3ed48fb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:18:26 +0100 Subject: [PATCH 309/563] Implement basic logic and UI for selecting game winners in GameResultView --- .../views/main_menu/game_result_view.dart | 107 ++++++++++++++---- 1 file changed, 85 insertions(+), 22 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index d953b0f..e39a4f3 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; class GameResultView extends StatefulWidget { @@ -12,6 +15,30 @@ class GameResultView extends StatefulWidget { } class _GameResultViewState extends State { + late final List allPlayers; + final exampleGame = Game( + name: "Test Game", + players: [ + Player(name: "Petrus"), + Player(name: "Peter"), + Player(name: "Petra"), + ], + group: Group( + name: "Die Petris", + members: [ + Player(name: "Petralia"), + Player(name: "Petrenlia"), + Player(name: "Petrumlia"), + ], + ), + ); + + @override + void initState() { + allPlayers = getAllPlayers(exampleGame); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -26,32 +53,68 @@ class _GameResultViewState extends State { centerTitle: true, ), body: SafeArea( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: const Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Select Winner", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: Column( + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Select Winner:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Expanded( + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + //TODO: Implement Custom RadioListTile, see text_icon_list_tile + return RadioListTile( + title: Text(allPlayers[index].name), + value: allPlayers[index], + ); + }, + ), + ), + ], ), ), - //TODO: Add FutureBuilder - //TODO: Implement ListView.builder with RadioListTiles and Players from Game - //TODO Implement Save button with snackbar to confirm save/show error - CustomWidthButton(text: "Save", sizeRelativeToWidth: 0.95), - ], - ), + ), + CustomWidthButton( + text: "Save", + sizeRelativeToWidth: 0.95, + onPressed: null, + ), + SizedBox(height: 10), + ], ), ), ); } + + List getAllPlayers(Game game) { + if (game.group == null && game.players != null) { + return [...game.players!]; + } else if (game.group != null && game.players != null) { + return [...game.players!, ...game.group!.members]; + } + return [...game.group!.members]; + } } -- 2.49.1 From 424a258df17dc87e9a115a3fa23a0b84e1210815 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:22:03 +0100 Subject: [PATCH 310/563] Update GameResultView with dummy Game data in CustomNavigationBar --- .../main_menu/custom_navigation_bar.dart | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 18561c4..b20f80e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.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/game_history_view.dart'; import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; @@ -57,7 +60,24 @@ class _CustomNavigationBarState extends State await Navigator.push( context, MaterialPageRoute( - builder: (_) => const GameResultView(), + builder: (_) => GameResultView( + game: Game( + name: "Test Game", + players: [ + Player(name: "Petrus"), + Player(name: "Peter"), + Player(name: "Petra"), + ], + group: Group( + name: "Die Petris", + members: [ + Player(name: "Petralia"), + Player(name: "Petrenlia"), + Player(name: "Petrumlia"), + ], + ), + ), + ), ), //TODO Replace with Settingsview ); setState(() { -- 2.49.1 From bd616c510a59d1cf278a8030f3f14239496a1a02 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 20:22:26 +0100 Subject: [PATCH 311/563] update GameResultView to accept and use a given Game instance --- .../views/main_menu/game_result_view.dart | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index e39a4f3..9278641 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; class GameResultView extends StatefulWidget { - const GameResultView({super.key}); + final Game game; - //TODO: Handle given game + const GameResultView({super.key, required this.game}); @override State createState() => _GameResultViewState(); @@ -16,26 +15,10 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; - final exampleGame = Game( - name: "Test Game", - players: [ - Player(name: "Petrus"), - Player(name: "Peter"), - Player(name: "Petra"), - ], - group: Group( - name: "Die Petris", - members: [ - Player(name: "Petralia"), - Player(name: "Petrenlia"), - Player(name: "Petrumlia"), - ], - ), - ); @override void initState() { - allPlayers = getAllPlayers(exampleGame); + allPlayers = getAllPlayers(widget.game); super.initState(); } -- 2.49.1 From 694cac7f265063128e56d5c94b0299cfab646cd8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 21:16:13 +0100 Subject: [PATCH 312/563] =?UTF-8?q?Fix=20bug=20where=20the=20skeleton=20wa?= =?UTF-8?q?s=20edited=20while=20it=20was=20visible=20and=20match=20the=20?= =?UTF-8?q?=E2=80=9Cno=20games=20available=E2=80=9D=20container=20size=20t?= =?UTF-8?q?o=20the=20size=20used=20when=20games=20are=20available.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/views/main_menu/home_view.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index d9cd1ab..f75eb78 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -151,15 +151,6 @@ class _HomeViewState extends State { ), ); } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty)) { - return const Center( - heightFactor: 4, - child: Text('No recent games available.'), - ); - } final List games = (isLoading ? skeletonData @@ -214,7 +205,7 @@ class _HomeViewState extends State { ); } else { return const Center( - heightFactor: 4, + heightFactor: 12, child: Text('No recent games available.'), ); } -- 2.49.1 From bbd200e24529923ecddb92a9fd721bb3f0ed9f36 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:34:16 +0100 Subject: [PATCH 313/563] use Skeletonizer for Layout --- .../views/main_menu/game_history_view.dart | 264 ++++++------------ 1 file changed, 83 insertions(+), 181 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 8cd7882..e3b2aeb 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -11,194 +15,92 @@ class GameHistoryView extends StatefulWidget { } class _GameHistoryViewState extends State { - final allGameData = [ - { - 'game': 'Schach', - 'title': 'Abendpartie', - 'players': 2, - 'group': 'Familie', - 'date': '01.06.2024', - }, - { - 'game': 'Monopoly', - 'title': 'Wochenendspaß mit Gras du Saas', - 'players': 4, - 'group': 'Freunde', - 'date': '28.05.2024', - }, - { - 'game': 'Catan', - 'title': 'Strategieabend', - 'players': 3, - 'group': 'Brettspieler', - 'date': '25.05.2024', - }, - { - 'game': 'Uno', - 'title': 'Schnelle Runde', - 'players': 5, - 'group': 'Kollegen', - 'date': '22.05.2024', - }, - { - 'game': 'Poker', - 'title': 'Freitagspoker', - 'players': 6, - 'group': 'Pokerclub', - 'date': '20.05.2024', - }, - { - 'game': 'Scrabble', - 'title': 'Wortschlacht', - 'players': 4, - 'group': 'Familie', - 'date': '18.05.2024', - }, - { - 'game': 'Risiko', - 'title': 'Weltherrschaft', - 'players': 5, - 'group': 'Strategiegruppe', - 'date': '15.05.2024', - }, - { - 'game': 'Zug um Zug', - 'title': 'Zug-Abenteuer', - 'players': 4, - 'group': 'Reisende', - 'date': '12.05.2024', - }, - { - 'game': 'Carcassonne', - 'title': 'Plättchenlegen', - 'players': 3, - 'group': 'Brettspieler', - 'date': '10.05.2024', - }, - { - 'game': 'Pandemie', - 'title': 'Welt retten', - 'players': 4, - 'group': 'Koop-Team', - 'date': '08.05.2024', - }, - { - 'game': 'Cluedo', - 'title': 'Krimiabend', - 'players': 6, - 'group': 'Detektive', - 'date': '05.05.2024', - }, - { - 'game': 'Dixit', - 'title': 'Fantasiespiel', - 'players': 5, - 'group': 'Künstler', - 'date': '02.05.2024', - }, - { - 'game': 'Azul', - 'title': 'Plättchenmeister', - 'players': 4, - 'group': 'Familie', - 'date': '30.04.2024', - }, - { - 'game': 'Splendor', - 'title': 'Edelsteinhändler', - 'players': 3, - 'group': 'Freunde', - 'date': '28.04.2024', - }, - { - 'game': '7 Wonders', - 'title': 'Antike Reiche', - 'players': 7, - 'group': 'Geschichtsfreunde', - 'date': '25.04.2024', - }, - ]; - late List> suggestedGameData; + late Future> _gameListFuture; + late final AppDatabase db; + + late final List skeletonData = List.filled( + 2, + Game( + name: 'Skeleton Game', + group: Group( + name: 'Skeleton Group', + members: [ + Player(name: 'Skeleton Player 1'), + Player(name: 'Skeleton Player 2'), + ], + ), + winner: Player(name: 'Skeleton Player 1'), + ), + ); @override void initState() { super.initState(); - suggestedGameData = List.from(allGameData); + db = Provider.of(context, listen: false); + _gameListFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.gameDao.getAllGames(), + ); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - body: Stack( - children: [ - Column( - children: [ - Container(margin: const EdgeInsets.only(bottom: 75)), - Expanded( - child: gameHistoryListView(allGameData, suggestedGameData), - ), - ], - ), - Container( - margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), - child: SearchBar( - leading: const Icon(Icons.search), - onChanged: (value) { - if (value.isEmpty) { - setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(allGameData); - }); - return; - } - final suggestions = allGameData.where((currentGame) { - return currentGame['game'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['title'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['group'].toString().toLowerCase().contains( - value.toLowerCase(), - ); - }); - setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(suggestions); - }); - }, + return FutureBuilder>( + future: _gameListFuture, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + heightFactor: 4, + child: Text( + 'Error while loading recent games.', ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + heightFactor: 4, + child: Text('No recent games available.'), + ); + } + + final bool isLoading = snapshot.connectionState == ConnectionState.waiting; + final List games = (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); + + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), ), - ], - ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, + ); + } + return GameHistoryTile(game: games[index]); + }, + ), + ); + }, ); } -} - -Widget gameHistoryListView(allGameData, suggestedGameData) { - if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele erstellt', message: '',); - } else if (suggestedGameData.isEmpty) { - return const TopCenteredMessage(icon: Icons.error, title: 'Keine Spiele mit den Suchparametern gefunden', message: '',); - } - - return ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - itemCount: suggestedGameData.length, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - itemBuilder: (context, index) { - final currentGame = suggestedGameData[index]; - return GameHistoryTile( - gameTitle: currentGame['title'], - gameType: currentGame['game'], - date: currentGame['date'], - groupName: currentGame['group'], - winner: 'ich', - ); - }, - ); -} +} \ No newline at end of file -- 2.49.1 From 290948e50d001427af6f6528edfacd39ad33c995 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:05:25 +0100 Subject: [PATCH 314/563] add intl for date formatting --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index fa4c213..07e4df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 + intl: ^0.18.0 dev_dependencies: flutter_test: -- 2.49.1 From 974d6e9b56ce94764004d941954e14f973a84dfe Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 22:14:13 +0100 Subject: [PATCH 315/563] refactor empty state logic in CreateGroupView The diff introduces boolean variables `doneLoading` and `snapshotDataEmpty` to simplify the conditional check for displaying the empty state message. It specifically fixes the logic to correctly show the "No players found" message when both the snapshot and the local `allPlayers` list are empty, removing the dependency on `selectedPlayers.isEmpty`. --- .../views/main_menu/create_group_view.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 59f72ed..c01250b 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -209,12 +209,13 @@ class _CreateGroupViewState extends State { ), ); } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (selectedPlayers.isEmpty && - allPlayers.isEmpty))) { + bool doneLoading = + snapshot.connectionState == + ConnectionState.done; + bool snapshotDataEmpty = + !snapshot.hasData || snapshot.data!.isEmpty; + if (doneLoading && + (snapshotDataEmpty && allPlayers.isEmpty)) { return const Center( child: TopCenteredMessage( icon: Icons.info, -- 2.49.1 From b102ec4c1c12df9431e4b6d680e9f52f694cdab8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 22:36:35 +0100 Subject: [PATCH 316/563] Implemented first structure of CreateGameView --- .../create_game/create_game_view.dart | 187 ++++++++++++++++++ .../views/main_menu/game_history_view.dart | 26 ++- 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 lib/presentation/views/main_menu/create_game/create_game_view.dart diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart new file mode 100644 index 0000000..94a08d5 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:provider/provider.dart'; + +class CreateGameView extends StatefulWidget { + const CreateGameView({super.key}); + + @override + State createState() => _CreateGameViewState(); +} + +class _CreateGameViewState extends State { + final TextEditingController _gameNameController = TextEditingController(); + late final AppDatabase db; + late Future> _allGroupsFuture; + + late final List groupsList; + + Group? selectedGroup; + Ruleset? selectedRuleset; + + bool isLoading = true; + + late final List skeletonData = List.filled( + 7, + Player(name: 'Player 0'), + ); + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + + _gameNameController.addListener(() { + setState(() {}); + }); + + _allGroupsFuture = db.groupDao.getAllGroups(); + + Future.wait([_allGroupsFuture]).then((result) async { + await Future.delayed(const Duration(milliseconds: 1000)); + groupsList = result[0]; + + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Create new game', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextInputField( + controller: _gameNameController, + hintText: 'Game name', + onChanged: (value) { + setState(() {}); + }, + ), + ), + GestureDetector( + onTap: () async { + selectedRuleset = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ChooseRulesetView(), + ), + ); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Text( + 'Ruleset', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text(selectedRuleset == null ? 'None' : 'Single Winner'), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + GestureDetector( + onTap: () async { + selectedGroup = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGroupView(groups: groupsList), + ), + ); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Text( + 'Group', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text(selectedGroup == null ? 'None' : selectedGroup!.name), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + const Spacer(), + CustomWidthButton( + text: 'Create game', + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: + (_gameNameController.text.isEmpty || + selectedGroup == null || + selectedRuleset == null) + ? null + : () async { + print('Create game pressed'); + }, + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index a962c05..d28943f 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/double_row_info_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -141,7 +143,12 @@ class _GameHistoryViewState extends State { ], ), Container( - margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), + margin: const EdgeInsets.only( + top: 10, + bottom: 10, + left: 10, + right: 10, + ), child: SearchBar( leading: const Icon(Icons.search), onChanged: (value) { @@ -170,6 +177,23 @@ class _GameHistoryViewState extends State { }, ), ), + Positioned( + bottom: 110, + width: MediaQuery.of(context).size.width, + child: Center( + child: CustomWidthButton( + text: 'Create Game', + sizeRelativeToWidth: 0.95, + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const CreateGameView(), + ), + ); + }, + ), + ), + ), ], ), ); -- 2.49.1 From e71cb11295645eaaa9914438fdf370073611bb99 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 23 Nov 2025 22:36:45 +0100 Subject: [PATCH 317/563] Implemented View for choosing group and ruleset --- lib/core/enums.dart | 7 ++ .../create_game/choose_group_view.dart | 41 ++++++++ .../create_game/choose_ruleset_view.dart | 95 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 lib/presentation/views/main_menu/create_game/choose_group_view.dart create mode 100644 lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 8c809b0..af1f4a6 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -22,3 +22,10 @@ enum ImportResult { /// - [ExportResult.canceled]: The export operation was canceled by the user. /// - [ExportResult.unknownException]: An exception occurred during export. enum ExportResult { success, canceled, unknownException } + +/// Different rulesets available for games +/// - [Ruleset.singleWinner]: The game is won by a single player +/// - [Ruleset.singleLoser]: The game is lost by a single player +/// - [Ruleset.mostPoints]: The player with the most points wins. +/// - [Ruleset.lastPoints]: The player with the fewest points wins. +enum Ruleset { singleWinner, singleLoser, mostPoints, lastPoints } diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart new file mode 100644 index 0000000..168fb99 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; + +class ChooseGroupView extends StatefulWidget { + final List groups; + + const ChooseGroupView({super.key, required this.groups}); + + @override + State createState() => _ChooseGroupViewState(); +} + +class _ChooseGroupViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.groups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(widget.groups[index]), + child: GroupTile(group: widget.groups[index]), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart new file mode 100644 index 0000000..5939405 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; + +class ChooseRulesetView extends StatefulWidget { + const ChooseRulesetView({super.key}); + + @override + State createState() => _ChooseRulesetViewState(); +} + +class _ChooseRulesetViewState extends State { + List<(Ruleset, String, String)> rulesets = [ + ( + Ruleset.singleWinner, + 'Single Winner', + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', + ), + ( + Ruleset.singleLoser, + 'Single Loser', + 'Exactly one loser is determined; last place receives the penalty or consequence.', + ), + ( + Ruleset.mostPoints, + 'Most Points', + 'Traditional ruleset: the player with the most points wins.', + ), + ( + Ruleset.lastPoints, + 'Least Points', + 'Inverse scoring: the player with the fewest points wins.', + ), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: rulesets.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(rulesets[index].$1), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + rulesets[index].$2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ], + ), + const SizedBox(height: 5), + Text( + rulesets[index].$3, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 2.5), + ], + ), + ), + ); + }, + ), + ); + } +} -- 2.49.1 From 4341c2509ea5cd41ca22a527c8ce84ac9f987aae Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:07:40 +0100 Subject: [PATCH 318/563] fix bug where only last 2 games were shown --- .../views/main_menu/game_history_view.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index e3b2aeb..45a17b5 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -19,7 +19,7 @@ class _GameHistoryViewState extends State { late final AppDatabase db; late final List skeletonData = List.filled( - 2, + 10, Game( name: 'Skeleton Game', group: Group( @@ -30,6 +30,9 @@ class _GameHistoryViewState extends State { ], ), winner: Player(name: 'Skeleton Player 1'), + players: [ + Player(name: 'Skeleton Player 3') + ], ), ); @@ -37,10 +40,11 @@ class _GameHistoryViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.gameDao.getAllGames(), - ); + _gameListFuture = db.gameDao.getAllGames(); + + Future.wait([_gameListFuture]).then((result) async { + await Future.delayed(const Duration(milliseconds: 250)); + }); } @override @@ -69,7 +73,6 @@ class _GameHistoryViewState extends State { ? skeletonData : (snapshot.data ?? []) ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) .toList(); return Skeletonizer( -- 2.49.1 From 32c7d458090b13f29651bb3bec6336bc94756a34 Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:34:22 +0100 Subject: [PATCH 319/563] made game_history_tile prettier :) --- .../widgets/tiles/game_history_tile.dart | 208 +++++++++++++----- 1 file changed, 159 insertions(+), 49 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index c21b2c2..fdc9584 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,21 +1,15 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { - final String gameTitle; - final String gameType; - final String date; - final String groupName; - final String winner; + final Game game; const GameHistoryTile({ super.key, - required this.gameTitle, - required this.gameType, - required this.date, - required this.groupName, - required this.winner, + required this.game, }); @override @@ -23,55 +17,171 @@ class GameHistoryTile extends StatefulWidget { } class _GameHistoryTileState extends State { + String _formatDate(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays == 0) { + return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays == 1) { + return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM d, yyyy').format(dateTime); + } + } + + List _getAllPlayers() { + final allPlayers = []; + final playerIds = {}; + + // Add players from game.players + if (widget.game.players != null) { + for (var player in widget.game.players!) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + // Add players from game.group.players + if (widget.game.group?.members != null) { + for (var player in widget.game.group!.members) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + return allPlayers; + } @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( + final group = widget.game.group; + final winner = widget.game.winner; + final allPlayers = _getAllPlayers(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Text( - widget.gameTitle, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, + Expanded( + child: Text( + widget.game.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + + const SizedBox(height: 8), + + if (group != null) + Row( + children: [ + const Icon( + Icons.group, + size: 16, + color: Colors.grey, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + group.name, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, ), - ], + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + + if (group != null) const SizedBox(height: 12), + + if (winner != null) + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), ), - Row( + child: Row( children: [ - Text( - widget.date, - style: const TextStyle(fontSize: 14, color: Colors.grey), - textAlign: TextAlign.left, + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, ), - const SizedBox(width: 5), - const Text('·'), - const SizedBox(width: 5), + const SizedBox(width: 8), Text( - widget.gameType, - style: const TextStyle(fontSize: 14, color: Colors.grey), - textAlign: TextAlign.left, + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), ), ], ), - const SizedBox(height: 15), - - ] - ) - ), - ], + ), + + if (winner != null) const SizedBox(height: 12), + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players:', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + final isWinner = winner != null && player.id == winner.id; + return TextIconTile( + text: player.name, + iconEnabled: false, + ); + }).toList(), + ), + ], + ], + ), ); } - -} +} \ No newline at end of file -- 2.49.1 From 4591a6857d212abaf9f00361c47e2b79f6b2c9fb Mon Sep 17 00:00:00 2001 From: Yannick <69087944+GelbEinhalb@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:39:56 +0100 Subject: [PATCH 320/563] change skeleton names --- lib/presentation/views/main_menu/game_history_view.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 45a17b5..60dcd90 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -25,13 +25,16 @@ class _GameHistoryViewState extends State { group: Group( name: 'Skeleton Group', members: [ - Player(name: 'Skeleton Player 1'), - Player(name: 'Skeleton Player 2'), + Player(name: 'Player 1'), + Player(name: 'Player 2'), + Player(name: 'Player 3'), + Player(name: 'Long Name Player 4'), + Player(name: 'Player 5'), ], ), winner: Player(name: 'Skeleton Player 1'), players: [ - Player(name: 'Skeleton Player 3') + Player(name: 'Skeleton Player 6') ], ), ); -- 2.49.1 From 72e48ada94118e6133d450732ded256f26fb5f7a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 13:31:42 +0100 Subject: [PATCH 321/563] Added seletion highlighting for selected group --- .../create_game/choose_group_view.dart | 30 +++++++++++++++++-- .../create_game/create_game_view.dart | 17 +++++++++-- .../widgets/tiles/group_tile.dart | 25 +++++++++++----- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index 168fb99..4eb6eab 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -5,14 +5,27 @@ import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { final List groups; + final int? selectedGroupIndex; - const ChooseGroupView({super.key, required this.groups}); + const ChooseGroupView({ + super.key, + required this.groups, + this.selectedGroupIndex, + }); @override State createState() => _ChooseGroupViewState(); } class _ChooseGroupViewState extends State { + late int selectedGroup; + + @override + void initState() { + selectedGroup = widget.selectedGroupIndex ?? -1; + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -31,8 +44,19 @@ class _ChooseGroupViewState extends State { itemCount: widget.groups.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( - onTap: () => Navigator.of(context).pop(widget.groups[index]), - child: GroupTile(group: widget.groups[index]), + onTap: () { + setState(() { + selectedGroup = index; + }); + + Future.delayed(const Duration(milliseconds: 500), () { + Navigator.of(context).pop(widget.groups[index]); + }); + }, + child: GroupTile( + group: widget.groups[index], + isHighlighted: selectedGroup == index, + ), ); }, ), diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 94a08d5..4c776f9 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; @@ -25,6 +26,7 @@ class _CreateGameViewState extends State { late final List groupsList; Group? selectedGroup; + int selectedGroupIndex = -1; Ruleset? selectedRuleset; bool isLoading = true; @@ -128,9 +130,15 @@ class _CreateGameViewState extends State { onTap: () async { selectedGroup = await Navigator.of(context).push( MaterialPageRoute( - builder: (context) => ChooseGroupView(groups: groupsList), + builder: (context) => ChooseGroupView( + groups: groupsList, + selectedGroupIndex: selectedGroupIndex, + ), ), ); + selectedGroupIndex = groupsList.indexWhere( + (g) => g.id == selectedGroup?.id, + ); setState(() {}); }, child: Container( @@ -175,7 +183,12 @@ class _CreateGameViewState extends State { selectedRuleset == null) ? null : () async { - print('Create game pressed'); + Game game = Game( + name: _gameNameController.text.trim(), + createdAt: DateTime.now(), + group: selectedGroup!, + ); + print('Creating game: ${game.name}'); }, ), const SizedBox(height: 20), diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index fa91477..8627b0e 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -4,20 +4,31 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatelessWidget { - const GroupTile({super.key, required this.group}); + const GroupTile({super.key, required this.group, this.isHighlighted = false}); final Group group; + final bool isHighlighted; @override Widget build(BuildContext context) { - return Container( + return AnimatedContainer( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: isHighlighted + ? BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow(color: Colors.blue.withAlpha(120), blurRadius: 12), + ], + ) + : BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + duration: const Duration(milliseconds: 150), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ -- 2.49.1 From c284d10943c44a7185d1fc970c4711494cf25254 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 13:49:25 +0100 Subject: [PATCH 322/563] Refactoring --- .../main_menu/create_game/choose_group_view.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index 4eb6eab..c98ce6d 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -5,12 +5,12 @@ import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { final List groups; - final int? selectedGroupIndex; + final int initialGroupIndex; const ChooseGroupView({ super.key, required this.groups, - this.selectedGroupIndex, + required this.initialGroupIndex, }); @override @@ -18,11 +18,11 @@ class ChooseGroupView extends StatefulWidget { } class _ChooseGroupViewState extends State { - late int selectedGroup; + late int selectedGroupIndex; @override void initState() { - selectedGroup = widget.selectedGroupIndex ?? -1; + selectedGroupIndex = widget.initialGroupIndex; super.initState(); } @@ -46,16 +46,17 @@ class _ChooseGroupViewState extends State { return GestureDetector( onTap: () { setState(() { - selectedGroup = index; + selectedGroupIndex = index; }); Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; Navigator.of(context).pop(widget.groups[index]); }); }, child: GroupTile( group: widget.groups[index], - isHighlighted: selectedGroup == index, + isHighlighted: selectedGroupIndex == index, ), ); }, -- 2.49.1 From e182c815a16c398be37fbd832fb9a1c3ab7de11c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 13:49:53 +0100 Subject: [PATCH 323/563] Implemented ruleset list tile with highlighting --- .../create_game/choose_ruleset_view.dart | 91 ++++++------------- .../create_game/create_game_view.dart | 34 ++++++- .../widgets/tiles/ruleset_list_tile.dart | 56 ++++++++++++ 3 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 lib/presentation/widgets/tiles/ruleset_list_tile.dart diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 5939405..0055761 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -1,37 +1,29 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/tiles/ruleset_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { - const ChooseRulesetView({super.key}); + final List<(Ruleset, String, String)> rulesets; + final int initialRulesetIndex; + const ChooseRulesetView({ + super.key, + required this.rulesets, + required this.initialRulesetIndex, + }); @override State createState() => _ChooseRulesetViewState(); } class _ChooseRulesetViewState extends State { - List<(Ruleset, String, String)> rulesets = [ - ( - Ruleset.singleWinner, - 'Single Winner', - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', - ), - ( - Ruleset.singleLoser, - 'Single Loser', - 'Exactly one loser is determined; last place receives the penalty or consequence.', - ), - ( - Ruleset.mostPoints, - 'Most Points', - 'Traditional ruleset: the player with the most points wins.', - ), - ( - Ruleset.lastPoints, - 'Least Points', - 'Inverse scoring: the player with the fewest points wins.', - ), - ]; + late int selectedRulesetIndex; + + @override + void initState() { + selectedRulesetIndex = widget.initialRulesetIndex; + super.initState(); + } @override Widget build(BuildContext context) { @@ -48,45 +40,22 @@ class _ChooseRulesetViewState extends State { ), body: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: rulesets.length, + itemCount: widget.rulesets.length, itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () => Navigator.of(context).pop(rulesets[index].$1), - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - rulesets[index].$2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - ), - ], - ), - const SizedBox(height: 5), - Text( - rulesets[index].$3, - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 2.5), - ], - ), - ), + return RulesetListTile( + onPressed: () async { + setState(() { + selectedRulesetIndex = index; + }); + + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(widget.rulesets[index].$1); + }); + }, + title: widget.rulesets[index].$2, + description: widget.rulesets[index].$3, + isHighlighted: selectedRulesetIndex == index, ); }, ), diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4c776f9..2626961 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -28,9 +28,33 @@ class _CreateGameViewState extends State { Group? selectedGroup; int selectedGroupIndex = -1; Ruleset? selectedRuleset; + int selectedRulesetIndex = -1; bool isLoading = true; + List<(Ruleset, String, String)> rulesets = [ + ( + Ruleset.singleWinner, + 'Single Winner', + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', + ), + ( + Ruleset.singleLoser, + 'Single Loser', + 'Exactly one loser is determined; last place receives the penalty or consequence.', + ), + ( + Ruleset.mostPoints, + 'Most Points', + 'Traditional ruleset: the player with the most points wins.', + ), + ( + Ruleset.lastPoints, + 'Least Points', + 'Inverse scoring: the player with the fewest points wins.', + ), + ]; + late final List skeletonData = List.filled( 7, Player(name: 'Player 0'), @@ -90,9 +114,15 @@ class _CreateGameViewState extends State { onTap: () async { selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const ChooseRulesetView(), + builder: (context) => ChooseRulesetView( + rulesets: rulesets, + initialRulesetIndex: selectedRulesetIndex, + ), ), ); + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); setState(() {}); }, child: Container( @@ -132,7 +162,7 @@ class _CreateGameViewState extends State { MaterialPageRoute( builder: (context) => ChooseGroupView( groups: groupsList, - selectedGroupIndex: selectedGroupIndex, + initialGroupIndex: selectedGroupIndex, ), ), ); diff --git a/lib/presentation/widgets/tiles/ruleset_list_tile.dart b/lib/presentation/widgets/tiles/ruleset_list_tile.dart new file mode 100644 index 0000000..0cc6071 --- /dev/null +++ b/lib/presentation/widgets/tiles/ruleset_list_tile.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class RulesetListTile extends StatelessWidget { + final String title; + final String description; + final VoidCallback? onPressed; + final bool isHighlighted; + + const RulesetListTile({ + super.key, + required this.title, + required this.description, + this.onPressed, + this.isHighlighted = false, + }); + + @override + Widget build(BuildContext context) { + // Use the callback directly so a null onPressed disables taps + return GestureDetector( + onTap: onPressed, + child: AnimatedContainer( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + decoration: isHighlighted + ? CustomTheme.highlightedBoxDecoration + : CustomTheme.standardBoxDecoration, + duration: const Duration(milliseconds: 200), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + title, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ], + ), + const SizedBox(height: 5), + Text(description, style: const TextStyle(fontSize: 14)), + const SizedBox(height: 2.5), + ], + ), + ), + ); + } +} -- 2.49.1 From 9054b163cec5f22b7d988757fa44a6bfa7ac6301 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 13:50:23 +0100 Subject: [PATCH 324/563] Added BoxDecorations to Custom Theme --- lib/core/custom_theme.dart | 13 +++++++++++++ lib/presentation/widgets/tiles/group_tile.dart | 15 ++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 16e9585..def7379 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -8,6 +8,19 @@ class CustomTheme { static Color onBoxColor = const Color(0xFF181818); static Color boxBorder = const Color(0xFF272727); + static BoxDecoration standardBoxDecoration = BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ); + + static BoxDecoration highlightedBoxDecoration = BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(12), + boxShadow: [BoxShadow(color: Colors.blue.withAlpha(120), blurRadius: 12)], + ); + static AppBarTheme appBarTheme = AppBarTheme( backgroundColor: backgroundColor, foregroundColor: Colors.white, diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 8627b0e..248c1c6 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -15,19 +15,8 @@ class GroupTile extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: isHighlighted - ? BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: Colors.blue), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow(color: Colors.blue.withAlpha(120), blurRadius: 12), - ], - ) - : BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + ? CustomTheme.highlightedBoxDecoration + : CustomTheme.standardBoxDecoration, duration: const Duration(milliseconds: 150), child: Column( crossAxisAlignment: CrossAxisAlignment.start, -- 2.49.1 From 2ba710ca2d781ee0e07d7b708528f14279e7d9af Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 13:53:28 +0100 Subject: [PATCH 325/563] Replaced unique box decorations with standardBoxDecoration --- lib/core/custom_theme.dart | 6 +++--- .../main_menu/create_game/create_game_view.dart | 12 ++---------- .../views/main_menu/create_group_view.dart | 6 +----- lib/presentation/widgets/tiles/info_tile.dart | 6 +----- lib/presentation/widgets/tiles/quick_info_tile.dart | 6 +----- .../widgets/tiles/settings_list_tile.dart | 6 +----- .../widgets/tiles/text_icon_list_tile.dart | 6 +----- 7 files changed, 10 insertions(+), 38 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index def7379..b80b4f6 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -9,13 +9,13 @@ class CustomTheme { static Color boxBorder = const Color(0xFF272727); static BoxDecoration standardBoxDecoration = BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), + color: boxColor, + border: Border.all(color: boxBorder), borderRadius: BorderRadius.circular(12), ); static BoxDecoration highlightedBoxDecoration = BoxDecoration( - color: CustomTheme.boxColor, + color: boxColor, border: Border.all(color: Colors.blue), borderRadius: BorderRadius.circular(12), boxShadow: [BoxShadow(color: Colors.blue.withAlpha(120), blurRadius: 12)], diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 2626961..736c86d 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -134,11 +134,7 @@ class _CreateGameViewState extends State { vertical: 10, horizontal: 15, ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Row( children: [ const Text( @@ -180,11 +176,7 @@ class _CreateGameViewState extends State { vertical: 10, horizontal: 15, ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Row( children: [ const Text( diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 59f72ed..72724e9 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -105,11 +105,7 @@ class _CreateGroupViewState extends State { vertical: 10, horizontal: 10, ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index 168262e..ff73e59 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -29,11 +29,7 @@ class _InfoTileState extends State { padding: widget.padding ?? const EdgeInsets.all(12), height: widget.height, width: widget.width ?? 380, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - ), + decoration: CustomTheme.standardBoxDecoration, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/widgets/tiles/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart index 423b8d3..d360aba 100644 --- a/lib/presentation/widgets/tiles/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -29,11 +29,7 @@ class _QuickInfoTileState extends State { padding: widget.padding ?? const EdgeInsets.all(12), height: widget.height ?? 110, width: widget.width ?? 180, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - ), + decoration: CustomTheme.standardBoxDecoration, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index d5c421f..6b43557 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -26,11 +26,7 @@ class SettingsListTile extends StatelessWidget { child: Container( margin: EdgeInsets.zero, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index 5e272c9..b23ef75 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -18,11 +18,7 @@ class TextIconListTile extends StatelessWidget { return Container( margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 15), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, -- 2.49.1 From 093c527591cde86b5996f6fd7a95f19853337deb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 15:17:29 +0100 Subject: [PATCH 326/563] Implemented TabView --- .../create_game/choose_ruleset_view.dart | 114 +++++++++++++----- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 0055761..772cef5 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -27,37 +27,93 @@ class _ChooseRulesetViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( + return DefaultTabController( + length: 2, + initialIndex: 0, + child: Scaffold( backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: const Text( - 'Choose Group', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Gametype', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: Column( + children: [ + Container( + color: CustomTheme.backgroundColor, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: TabBar( + padding: const EdgeInsets.symmetric(horizontal: 5), + // Label Settings + labelStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + labelColor: Colors.white, + unselectedLabelStyle: const TextStyle(fontSize: 14), + unselectedLabelColor: Colors.white70, + // Indicator Settings + indicator: CustomTheme.standardBoxDecoration, + indicatorSize: TabBarIndicatorSize.tab, + indicatorWeight: 1, + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 0, + ), + // Divider Settings + dividerHeight: 0, + tabs: const [ + Tab(text: 'Rulesets'), + Tab(text: 'Gametypes'), + ], + ), + ), + const Divider( + indent: 30, + endIndent: 30, + thickness: 3, + radius: BorderRadius.all(Radius.circular(12)), + ), + Expanded( + child: TabBarView( + children: [ + ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return RulesetListTile( + onPressed: () async { + setState(() { + selectedRulesetIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of( + context, + ).pop(widget.rulesets[index].$1); + }); + }, + title: widget.rulesets[index].$2, + description: widget.rulesets[index].$3, + isHighlighted: selectedRulesetIndex == index, + ); + }, + ), + const Center( + child: Text( + 'No gametypes available', + style: TextStyle(color: Colors.white70), + ), + ), + ], + ), + ), + ], ), - centerTitle: true, - ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return RulesetListTile( - onPressed: () async { - setState(() { - selectedRulesetIndex = index; - }); - - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(widget.rulesets[index].$1); - }); - }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, - isHighlighted: selectedRulesetIndex == index, - ); - }, ), ); } -- 2.49.1 From 3afae892349f19a3672054ce3da821d74538bc18 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 15:17:46 +0100 Subject: [PATCH 327/563] Added Skeleton Loading --- .../create_game/create_game_view.dart | 278 ++++++++++-------- 1 file changed, 159 insertions(+), 119 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 736c86d..2df52ec 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -4,12 +4,12 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class CreateGameView extends StatefulWidget { const CreateGameView({super.key}); @@ -55,24 +55,15 @@ class _CreateGameViewState extends State { ), ]; - late final List skeletonData = List.filled( - 7, - Player(name: 'Player 0'), - ); - @override void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameNameController.addListener(() { - setState(() {}); - }); - _allGroupsFuture = db.groupDao.getAllGroups(); Future.wait([_allGroupsFuture]).then((result) async { - await Future.delayed(const Duration(milliseconds: 1000)); + await Future.delayed(const Duration(milliseconds: 250)); groupsList = result[0]; if (mounted) { @@ -97,126 +88,175 @@ class _CreateGameViewState extends State { centerTitle: true, ), body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: TextInputField( - controller: _gameNameController, - hintText: 'Game name', - onChanged: (value) { + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + child: TextInputField( + controller: _gameNameController, + hintText: 'Game name', + onChanged: (value) { + setState(() {}); + }, + ), + ), + GestureDetector( + onTap: () async { + selectedRuleset = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseRulesetView( + rulesets: rulesets, + initialRulesetIndex: selectedRulesetIndex, + ), + ), + ); + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); setState(() {}); }, - ), - ), - GestureDetector( - onTap: () async { - selectedRuleset = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseRulesetView( - rulesets: rulesets, - initialRulesetIndex: selectedRulesetIndex, - ), + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + decoration: CustomTheme.standardBoxDecoration, + child: Row( + children: [ + const Text( + 'Ruleset', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + selectedRuleset == null + ? 'None' + : translateRulesetToString(selectedRuleset!), + ), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], ), - ); - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - setState(() {}); - }, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, ), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Ruleset', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + ), + GestureDetector( + onTap: () async { + selectedGroup = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGroupView( + groups: groupsList, + initialGroupIndex: selectedGroupIndex, ), ), - const Spacer(), - Text(selectedRuleset == null ? 'None' : 'Single Winner'), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], - ), - ), - ), - GestureDetector( - onTap: () async { - selectedGroup = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseGroupView( - groups: groupsList, - initialGroupIndex: selectedGroupIndex, - ), + ); + selectedGroupIndex = groupsList.indexWhere( + (g) => g.id == selectedGroup?.id, + ); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, ), - ); - selectedGroupIndex = groupsList.indexWhere( - (g) => g.id == selectedGroup?.id, - ); - setState(() {}); - }, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Group', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + decoration: CustomTheme.standardBoxDecoration, + child: Row( + children: [ + const Text( + 'Group', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), - ), - const Spacer(), - Text(selectedGroup == null ? 'None' : selectedGroup!.name), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], + const Spacer(), + Text( + selectedGroup == null ? 'None' : selectedGroup!.name, + ), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), ), ), - ), - const Spacer(), - CustomWidthButton( - text: 'Create game', - sizeRelativeToWidth: 0.95, - buttonType: ButtonType.primary, - onPressed: - (_gameNameController.text.isEmpty || - selectedGroup == null || - selectedRuleset == null) - ? null - : () async { - Game game = Game( - name: _gameNameController.text.trim(), - createdAt: DateTime.now(), - group: selectedGroup!, - ); - print('Creating game: ${game.name}'); - }, - ), - const SizedBox(height: 20), - ], + Container( + decoration: CustomTheme.standardBoxDecoration, + width: MediaQuery.of(context).size.width * 0.95, + height: 400, + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.symmetric(vertical: 10), + child: const Center(child: Text('PlayerComponent')), + ), + const Spacer(), + CustomWidthButton( + text: 'Create game', + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: + (_gameNameController.text.isEmpty || + selectedGroup == null || + selectedRuleset == null) + ? null + : () async { + Game game = Game( + name: _gameNameController.text.trim(), + createdAt: DateTime.now(), + group: selectedGroup!, + ); + // TODO: Replace with navigation to GameResultView() + print('Created game: $game'); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20), + ], + ), ), ), ); } + + /// Translates a [Ruleset] enum value to its corresponding string representation. + String translateRulesetToString(Ruleset ruleset) { + switch (ruleset) { + case Ruleset.singleWinner: + return 'Single Winner'; + case Ruleset.singleLoser: + return 'Single Loser'; + case Ruleset.mostPoints: + return 'Most Points'; + case Ruleset.lastPoints: + return 'Least Points'; + } + } } -- 2.49.1 From 1faa74f02623f71830b2568df92ce029a00a7b46 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 15:17:55 +0100 Subject: [PATCH 328/563] Removed comment --- lib/presentation/widgets/tiles/ruleset_list_tile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/ruleset_list_tile.dart b/lib/presentation/widgets/tiles/ruleset_list_tile.dart index 0cc6071..13eaf82 100644 --- a/lib/presentation/widgets/tiles/ruleset_list_tile.dart +++ b/lib/presentation/widgets/tiles/ruleset_list_tile.dart @@ -17,7 +17,6 @@ class RulesetListTile extends StatelessWidget { @override Widget build(BuildContext context) { - // Use the callback directly so a null onPressed disables taps return GestureDetector( onTap: onPressed, child: AnimatedContainer( -- 2.49.1 From 7c7676abee82968435f9b0e4978e317d32cb0a51 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 16:17:15 +0100 Subject: [PATCH 329/563] Implemented CustomTextInputField --- .../create_game/create_game_view.dart | 34 +++++++++++-------- .../text_input/custom_text_input_field.dart | 32 +++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 lib/presentation/widgets/text_input/custom_text_input_field.dart diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 2df52ec..ef7e267 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -7,7 +7,7 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_text_input_field.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -19,19 +19,31 @@ class CreateGameView extends StatefulWidget { } class _CreateGameViewState extends State { - final TextEditingController _gameNameController = TextEditingController(); late final AppDatabase db; late Future> _allGroupsFuture; + final TextEditingController _gameNameController = TextEditingController(); + /// List of all groups from the database late final List groupsList; + /// The currently selected group Group? selectedGroup; + + /// The index of the currently selected group in [groupsList] to mark it in + /// the [ChooseGroupView] int selectedGroupIndex = -1; + + /// The currently selected ruleset Ruleset? selectedRuleset; + + /// The index of the currently selected ruleset in [rulesets] to mark it in + /// the [ChooseRulesetView] int selectedRulesetIndex = -1; bool isLoading = true; + /// List of available rulesets with their display names and descriptions + /// as tuples of (Ruleset, String, String) List<(Ruleset, String, String)> rulesets = [ ( Ruleset.singleWinner, @@ -106,18 +118,12 @@ class _CreateGameViewState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - child: TextInputField( - controller: _gameNameController, - hintText: 'Game name', - onChanged: (value) { - setState(() {}); - }, - ), + CustomTextInputField( + controller: _gameNameController, + hintText: 'Game name', + onChanged: (value) { + setState(() {}); + }, ), GestureDetector( onTap: () async { diff --git a/lib/presentation/widgets/text_input/custom_text_input_field.dart b/lib/presentation/widgets/text_input/custom_text_input_field.dart new file mode 100644 index 0000000..2b6efd5 --- /dev/null +++ b/lib/presentation/widgets/text_input/custom_text_input_field.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/presentation/widgets/text_input_field.dart'; + +class CustomTextInputField extends StatefulWidget { + final TextEditingController controller; + final String hintText; + final void Function(String)? onChanged; + + const CustomTextInputField({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + }); + + @override + State createState() => _CustomTextInputFieldState(); +} + +class _CustomTextInputFieldState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextInputField( + controller: widget.controller, + hintText: widget.hintText, + onChanged: widget.onChanged, + ), + ); + } +} -- 2.49.1 From 9efbc129092aed6d559c5da9ee66b314bbe3a7d1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 16:18:11 +0100 Subject: [PATCH 330/563] moved input widgets to new folder --- .../create_game/create_game_view.dart | 21 ++++++++---- .../views/main_menu/create_group_view.dart | 4 +-- .../{ => text_input}/custom_search_bar.dart | 0 .../text_input/custom_text_input_field.dart | 32 ------------------- .../{ => text_input}/text_input_field.dart | 0 5 files changed, 16 insertions(+), 41 deletions(-) rename lib/presentation/widgets/{ => text_input}/custom_search_bar.dart (100%) delete mode 100644 lib/presentation/widgets/text_input/custom_text_input_field.dart rename lib/presentation/widgets/{ => text_input}/text_input_field.dart (100%) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index ef7e267..82f44a9 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -7,7 +7,7 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/text_input/custom_text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -118,13 +118,20 @@ class _CreateGameViewState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - CustomTextInputField( - controller: _gameNameController, - hintText: 'Game name', - onChanged: (value) { - setState(() {}); - }, + Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + child: TextInputField( + controller: _gameNameController, + hintText: 'Game name', + onChanged: (value) { + setState(() {}); + }, + ), ), + GestureDetector( onTap: () async { selectedRuleset = await Navigator.of(context).push( diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 72724e9..fcb914e 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -5,8 +5,8 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; +import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart similarity index 100% rename from lib/presentation/widgets/custom_search_bar.dart rename to lib/presentation/widgets/text_input/custom_search_bar.dart diff --git a/lib/presentation/widgets/text_input/custom_text_input_field.dart b/lib/presentation/widgets/text_input/custom_text_input_field.dart deleted file mode 100644 index 2b6efd5..0000000 --- a/lib/presentation/widgets/text_input/custom_text_input_field.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/presentation/widgets/text_input_field.dart'; - -class CustomTextInputField extends StatefulWidget { - final TextEditingController controller; - final String hintText; - final void Function(String)? onChanged; - - const CustomTextInputField({ - super.key, - required this.controller, - required this.hintText, - this.onChanged, - }); - - @override - State createState() => _CustomTextInputFieldState(); -} - -class _CustomTextInputFieldState extends State { - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: TextInputField( - controller: widget.controller, - hintText: widget.hintText, - onChanged: widget.onChanged, - ), - ); - } -} diff --git a/lib/presentation/widgets/text_input_field.dart b/lib/presentation/widgets/text_input/text_input_field.dart similarity index 100% rename from lib/presentation/widgets/text_input_field.dart rename to lib/presentation/widgets/text_input/text_input_field.dart -- 2.49.1 From 18f635e6ef0346ab689bd10f6953ece0f8ebab2c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:07 +0100 Subject: [PATCH 331/563] Implemented custom skeleton widget --- lib/presentation/widgets/app_skeleton.dart | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lib/presentation/widgets/app_skeleton.dart diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart new file mode 100644 index 0000000..209f1d8 --- /dev/null +++ b/lib/presentation/widgets/app_skeleton.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class AppSkeleton extends StatefulWidget { + final Widget child; + final bool enabled; + final bool fixLayoutBuilder; + + const AppSkeleton({ + super.key, + required this.child, + this.enabled = true, + this.fixLayoutBuilder = false, + }); + + @override + State createState() => _AppSkeletonState(); +} + +class _AppSkeletonState extends State { + @override + Widget build(BuildContext context) { + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: widget.enabled, + enableSwitchAnimation: true, + switchAnimationConfig: SwitchAnimationConfig( + duration: const Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: !widget.fixLayoutBuilder + ? AnimatedSwitcher.defaultLayoutBuilder + : (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + ), + child: widget.child, + ); + } +} -- 2.49.1 From 2d9148788e55e99e9a6711fb7ff36837d257d2e4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:18 +0100 Subject: [PATCH 332/563] Refactored skeleton widgets to own --- .../views/main_menu/create_group_view.dart | 20 ++------------ .../views/main_menu/groups_view.dart | 18 ++----------- .../views/main_menu/home_view.dart | 26 ++---------------- .../views/main_menu/statistics_view.dart | 27 +++---------------- 4 files changed, 9 insertions(+), 82 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c01250b..ef78169 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; @@ -11,7 +12,6 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -228,24 +228,8 @@ class _CreateGroupViewState extends State { snapshot.connectionState == ConnectionState.waiting; return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), child: Visibility( visible: (suggestedPlayers.isEmpty && diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index aaef1a5..29fbac8 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,11 +4,11 @@ import 'package:game_tracker/data/db/database.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/create_group_view.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class GroupsView extends StatefulWidget { const GroupsView({super.key}); @@ -75,22 +75,8 @@ class _GroupsViewState extends State { final List groups = isLoading ? skeletonData : (snapshot.data ?? []) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), itemCount: groups.length + 1, diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f75eb78..1667f2b 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -3,12 +3,12 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @@ -62,30 +62,8 @@ class _HomeViewState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6107586..564d0d5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -2,9 +2,9 @@ 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/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class StatisticsView extends StatefulWidget { const StatisticsView({super.key}); @@ -48,30 +48,9 @@ class _StatisticsViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), + fixLayoutBuilder: true, child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Column( -- 2.49.1 From 744a402602f92c4912547ab2005f8db571fff796 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 20:58:56 +0100 Subject: [PATCH 333/563] put player selection from creategroupview into own widget --- .../widgets/select_player_widget.dart | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 lib/presentation/widgets/select_player_widget.dart diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/select_player_widget.dart new file mode 100644 index 0000000..14cea0b --- /dev/null +++ b/lib/presentation/widgets/select_player_widget.dart @@ -0,0 +1,287 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class SelectPlayerWidget extends StatefulWidget { + final TextEditingController groupNameController; + final TextEditingController searchBarController; + final List selectedPlayers; + final Function(List value) onChanged; + + const SelectPlayerWidget({ + super.key, + required this.groupNameController, + required this.searchBarController, + required this.selectedPlayers, + required this.onChanged, + }); + + @override + State createState() => _SelectPlayerWidgetState(); +} + +class _SelectPlayerWidgetState extends State { + List suggestedPlayers = []; + List allPlayers = []; + late final TextEditingController _searchBarController; + late final AppDatabase db; + late Future> _allPlayersFuture; + late final List skeletonData = List.filled( + 7, + Player(name: 'Player 0'), + ); + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + _searchBarController = widget.searchBarController; + loadPlayerList(); + } + + void loadPlayerList() { + _allPlayersFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.playerDao.getAllPlayers(), + ); + suggestedPlayers = skeletonData; + _allPlayersFuture.then((loadedPlayers) { + setState(() { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomSearchBar( + controller: _searchBarController, + constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: 'Search for players', + trailingButtonShown: true, + trailingButtonicon: Icons.add_circle, + trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, + onTrailingButtonPressed: () async { + addNewPlayerFromSearch(context: context); + }, + onChanged: (value) { + setState(() { + if (value.isEmpty) { + suggestedPlayers = allPlayers.where((player) { + return !widget.selectedPlayers.contains(player); + }).toList(); + } else { + suggestedPlayers = allPlayers.where((player) { + final bool nameMatches = player.name.toLowerCase().contains( + value.toLowerCase(), + ); + final bool isNotSelected = !widget.selectedPlayers.contains( + player, + ); + return nameMatches && isNotSelected; + }).toList(); + } + }); + }, + ), + const SizedBox(height: 10), + Text( + 'Ausgewählte Spieler: (${widget.selectedPlayers.length})', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var player in widget.selectedPlayers) + TextIconTile( + text: player.name, + onIconTap: () { + setState(() { + final currentSearch = _searchBarController.text + .toLowerCase(); + //widget.selectedPlayers.remove(player); + widget.onChanged( + widget.selectedPlayers + .where((p) => p != player) + .toList(), + ); + if (currentSearch.isEmpty || + player.name.toLowerCase().contains(currentSearch)) { + suggestedPlayers.add(player); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } + }); + }, + ), + ], + ), + const SizedBox(height: 10), + const Text( + 'Alle Spieler:', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + FutureBuilder( + future: _allPlayersFuture, + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + bool doneLoading = + snapshot.connectionState == ConnectionState.done; + bool snapshotDataEmpty = + !snapshot.hasData || snapshot.data!.isEmpty; + if (doneLoading && + (snapshotDataEmpty && allPlayers.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: + AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, + ), + child: Visibility( + visible: + (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), + replacement: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!widget.selectedPlayers.contains( + suggestedPlayers[index], + )) { + /*widget.selectedPlayers.add( + suggestedPlayers[index], + );*/ + widget.onChanged([ + ...widget.selectedPlayers, + suggestedPlayers[index], + ]); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, + ); + }, + ), + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (widget.selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + ), + ), + ); + }, + ), + ], + ), + ); + } + + /// Adds a new player to the database from the search bar input. + /// Shows a snackbar indicating success or failure. + /// [context] - BuildContext to show the snackbar. + void addNewPlayerFromSearch({required BuildContext context}) async { + String playerName = _searchBarController.text.trim(); + Player createdPlayer = Player(name: playerName); + bool success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { + //widget.selectedPlayers.add(createdPlayer); + widget.onChanged([...widget.selectedPlayers, createdPlayer]); + allPlayers.add(createdPlayer); + setState(() { + _searchBarController.clear(); + suggestedPlayers = allPlayers.where((player) { + return !widget.selectedPlayers.contains(player); + }).toList(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Successfully added player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Could not add player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } + } +} -- 2.49.1 From 6c9b742bdf47c54c5eb70b3835441ecb8395e83c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 20:59:23 +0100 Subject: [PATCH 334/563] Refactor CreateGroupView to use SelectPlayerWidget --- .../views/main_menu/create_group_view.dart | 290 +----------------- 1 file changed, 16 insertions(+), 274 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c01250b..1153fc0 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -5,13 +5,9 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; +import 'package:game_tracker/presentation/widgets/select_player_widget.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -21,29 +17,21 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - List selectedPlayers = []; - List suggestedPlayers = []; - List allPlayers = []; - late final AppDatabase db; - late Future> _allPlayersFuture; - late final List skeletonData = List.filled( - 7, - Player(name: 'Player 0'), - ); final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); + late final AppDatabase db; + List selectedPlayers = []; @override void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController.addListener(() { - setState(() {}); - }); _groupNameController.addListener(() { setState(() {}); }); - loadPlayerList(); + _searchBarController.addListener(() { + setState(() {}); + }); } @override @@ -53,21 +41,6 @@ class _CreateGroupViewState extends State { super.dispose(); } - void loadPlayerList() { - _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.playerDao.getAllPlayers(), - ); - suggestedPlayers = skeletonData; - _allPlayersFuture.then((loadedPlayers) { - setState(() { - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - }); - }); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -96,204 +69,16 @@ class _CreateGroupViewState extends State { ), ), Expanded( - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomSearchBar( - controller: _searchBarController, - constraints: const BoxConstraints( - maxHeight: 45, - minHeight: 45, - ), - hintText: 'Search for players', - trailingButtonShown: true, - trailingButtonicon: Icons.add_circle, - trailingButtonEnabled: _searchBarController.text - .trim() - .isNotEmpty, - onTrailingButtonPressed: () async { - addNewPlayerFromSearch(context: context); - }, - onChanged: (value) { - setState(() { - if (value.isEmpty) { - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - } else { - suggestedPlayers = allPlayers.where((player) { - final bool nameMatches = player.name - .toLowerCase() - .contains(value.toLowerCase()); - final bool isNotSelected = !selectedPlayers - .contains(player); - return nameMatches && isNotSelected; - }).toList(); - } - }); - }, - ), - const SizedBox(height: 10), - Text( - 'Ausgewählte Spieler: (${selectedPlayers.length})', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 8.0, - runSpacing: 8.0, - children: [ - for (var player in selectedPlayers) - TextIconTile( - text: player.name, - onIconTap: () { - setState(() { - final currentSearch = _searchBarController.text - .toLowerCase(); - selectedPlayers.remove(player); - if (currentSearch.isEmpty || - player.name.toLowerCase().contains( - currentSearch, - )) { - suggestedPlayers.add(player); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); - } - }); - }, - ), - ], - ), - const SizedBox(height: 10), - const Text( - 'Alle Spieler:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - FutureBuilder( - future: _allPlayersFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), - ); - } - bool doneLoading = - snapshot.connectionState == - ConnectionState.done; - bool snapshotDataEmpty = - !snapshot.hasData || snapshot.data!.isEmpty; - if (doneLoading && - (snapshotDataEmpty && allPlayers.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == - ConnectionState.waiting; - return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), - child: Visibility( - visible: - (suggestedPlayers.isEmpty && - allPlayers.isNotEmpty), - replacement: ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: - (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index].name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => a.name.compareTo( - b.name, - ), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); - }, - ), - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - (selectedPlayers.length == - allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ), - ), - ), - ); - }, - ), - ], - ), + child: SelectPlayerWidget( + groupNameController: _groupNameController, + searchBarController: _searchBarController, + selectedPlayers: selectedPlayers, + onChanged: (value) { + setState(() { + selectedPlayers = [...value]; + }); + print(selectedPlayers); + }, ), ), CustomWidthButton( @@ -338,47 +123,4 @@ class _CreateGroupViewState extends State { ), ); } - - /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success or failure. - /// [context] - BuildContext to show the snackbar. - void addNewPlayerFromSearch({required BuildContext context}) async { - String playerName = _searchBarController.text.trim(); - Player createdPlayer = Player(name: playerName); - bool success = await db.playerDao.addPlayer(player: createdPlayer); - if (!context.mounted) return; - if (success) { - selectedPlayers.add(createdPlayer); - allPlayers.add(createdPlayer); - setState(() { - _searchBarController.clear(); - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Successfully added player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Could not add player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); - } - } } -- 2.49.1 From f1bd9c18e0f047547706579ad787a12aa5ac0970 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:15:18 +0100 Subject: [PATCH 335/563] Made selectedPlayers local to SelectPlayerWidget because its not needed in CreateGroupView --- .../views/main_menu/create_group_view.dart | 6 +-- .../widgets/select_player_widget.dart | 40 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 1153fc0..5636c18 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -72,11 +72,8 @@ class _CreateGroupViewState extends State { child: SelectPlayerWidget( groupNameController: _groupNameController, searchBarController: _searchBarController, - selectedPlayers: selectedPlayers, onChanged: (value) { - setState(() { - selectedPlayers = [...value]; - }); + selectedPlayers = [...value]; print(selectedPlayers); }, ), @@ -99,7 +96,6 @@ class _CreateGroupViewState extends State { if (success) { _groupNameController.clear(); _searchBarController.clear(); - selectedPlayers.clear(); Navigator.pop(context); } else { ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/select_player_widget.dart index 14cea0b..1c4f87d 100644 --- a/lib/presentation/widgets/select_player_widget.dart +++ b/lib/presentation/widgets/select_player_widget.dart @@ -12,14 +12,12 @@ import 'package:skeletonizer/skeletonizer.dart'; class SelectPlayerWidget extends StatefulWidget { final TextEditingController groupNameController; final TextEditingController searchBarController; - final List selectedPlayers; final Function(List value) onChanged; const SelectPlayerWidget({ super.key, required this.groupNameController, required this.searchBarController, - required this.selectedPlayers, required this.onChanged, }); @@ -28,6 +26,7 @@ class SelectPlayerWidget extends StatefulWidget { } class _SelectPlayerWidgetState extends State { + List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; late final TextEditingController _searchBarController; @@ -88,14 +87,14 @@ class _SelectPlayerWidgetState extends State { setState(() { if (value.isEmpty) { suggestedPlayers = allPlayers.where((player) { - return !widget.selectedPlayers.contains(player); + return !selectedPlayers.contains(player); }).toList(); } else { suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name.toLowerCase().contains( value.toLowerCase(), ); - final bool isNotSelected = !widget.selectedPlayers.contains( + final bool isNotSelected = !selectedPlayers.contains( player, ); return nameMatches && isNotSelected; @@ -106,7 +105,7 @@ class _SelectPlayerWidgetState extends State { ), const SizedBox(height: 10), Text( - 'Ausgewählte Spieler: (${widget.selectedPlayers.length})', + 'Ausgewählte Spieler: (${selectedPlayers.length})', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -116,19 +115,15 @@ class _SelectPlayerWidgetState extends State { spacing: 8.0, runSpacing: 8.0, children: [ - for (var player in widget.selectedPlayers) + for (var player in selectedPlayers) TextIconTile( text: player.name, onIconTap: () { setState(() { final currentSearch = _searchBarController.text .toLowerCase(); - //widget.selectedPlayers.remove(player); - widget.onChanged( - widget.selectedPlayers - .where((p) => p != player) - .toList(), - ); + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); if (currentSearch.isEmpty || player.name.toLowerCase().contains(currentSearch)) { suggestedPlayers.add(player); @@ -203,16 +198,13 @@ class _SelectPlayerWidgetState extends State { text: suggestedPlayers[index].name, onPressed: () { setState(() { - if (!widget.selectedPlayers.contains( + if (!selectedPlayers.contains( suggestedPlayers[index], )) { - /*widget.selectedPlayers.add( + selectedPlayers.add( suggestedPlayers[index], - );*/ - widget.onChanged([ - ...widget.selectedPlayers, - suggestedPlayers[index], - ]); + ); + widget.onChanged([...selectedPlayers]); suggestedPlayers.remove( suggestedPlayers[index], ); @@ -225,9 +217,7 @@ class _SelectPlayerWidgetState extends State { child: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: - (widget.selectedPlayers.length == - allPlayers.length) + message: (selectedPlayers.length == allPlayers.length) ? 'No more players to add.' : 'No players found with that name.', ), @@ -250,13 +240,13 @@ class _SelectPlayerWidgetState extends State { bool success = await db.playerDao.addPlayer(player: createdPlayer); if (!context.mounted) return; if (success) { - //widget.selectedPlayers.add(createdPlayer); - widget.onChanged([...widget.selectedPlayers, createdPlayer]); + selectedPlayers.add(createdPlayer); + widget.onChanged([...selectedPlayers]); allPlayers.add(createdPlayer); setState(() { _searchBarController.clear(); suggestedPlayers = allPlayers.where((player) { - return !widget.selectedPlayers.contains(player); + return !selectedPlayers.contains(player); }).toList(); }); ScaffoldMessenger.of(context).showSnackBar( -- 2.49.1 From 6a77028171eab4b20f394e7db2ca49caa96690ea Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:17:01 +0100 Subject: [PATCH 336/563] rename to PlayerSelection --- .../{select_player_widget.dart => player_selection.dart} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename lib/presentation/widgets/{select_player_widget.dart => player_selection.dart} (97%) diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/player_selection.dart similarity index 97% rename from lib/presentation/widgets/select_player_widget.dart rename to lib/presentation/widgets/player_selection.dart index 1c4f87d..3032c06 100644 --- a/lib/presentation/widgets/select_player_widget.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -9,12 +9,12 @@ import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class SelectPlayerWidget extends StatefulWidget { +class PlayerSelection extends StatefulWidget { final TextEditingController groupNameController; final TextEditingController searchBarController; final Function(List value) onChanged; - const SelectPlayerWidget({ + const PlayerSelection({ super.key, required this.groupNameController, required this.searchBarController, @@ -22,10 +22,10 @@ class SelectPlayerWidget extends StatefulWidget { }); @override - State createState() => _SelectPlayerWidgetState(); + State createState() => _PlayerSelectionState(); } -class _SelectPlayerWidgetState extends State { +class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; -- 2.49.1 From 686463720ac89d28c2f31421678c7ff77678df8b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:17:27 +0100 Subject: [PATCH 337/563] refactor for new name and remove hide in material import --- lib/presentation/views/main_menu/create_group_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 5636c18..f8bbec0 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,11 +1,11 @@ -import 'package:flutter/material.dart' hide ButtonStyle; +import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/select_player_widget.dart'; +import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:provider/provider.dart'; @@ -69,7 +69,7 @@ class _CreateGroupViewState extends State { ), ), Expanded( - child: SelectPlayerWidget( + child: PlayerSelection( groupNameController: _groupNameController, searchBarController: _searchBarController, onChanged: (value) { -- 2.49.1 From 54b54796e858a3cec3a330e312f840efb1735584 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:19:05 +0100 Subject: [PATCH 338/563] remove print --- lib/presentation/views/main_menu/create_group_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f8bbec0..c32986a 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -74,7 +74,6 @@ class _CreateGroupViewState extends State { searchBarController: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; - print(selectedPlayers); }, ), ), -- 2.49.1 From 442e1d64a36d24809938143401b3321f3412ed47 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:24:51 +0100 Subject: [PATCH 339/563] remove uneccessary groupNameController --- lib/presentation/views/main_menu/create_group_view.dart | 1 - lib/presentation/widgets/player_selection.dart | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c32986a..28566f6 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -70,7 +70,6 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - groupNameController: _groupNameController, searchBarController: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 3032c06..c6b2f1a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,13 +10,11 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController groupNameController; final TextEditingController searchBarController; final Function(List value) onChanged; const PlayerSelection({ super.key, - required this.groupNameController, required this.searchBarController, required this.onChanged, }); -- 2.49.1 From a2522cef133f3df86283e2f8dab6e6083adfcb0d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:26:55 +0100 Subject: [PATCH 340/563] rename searchBarController to controller in PlayerSelection widget --- lib/presentation/views/main_menu/create_group_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 28566f6..f593efe 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -70,7 +70,7 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - searchBarController: _searchBarController, + controller: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; }, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index c6b2f1a..cf70072 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,12 +10,12 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController searchBarController; + final TextEditingController controller; final Function(List value) onChanged; const PlayerSelection({ super.key, - required this.searchBarController, + required this.controller, required this.onChanged, }); @@ -39,7 +39,7 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController = widget.searchBarController; + _searchBarController = widget.controller; loadPlayerList(); } -- 2.49.1 From fc9779153d87b5e7850dbf9547c1e7e0d0147ae7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:19 +0100 Subject: [PATCH 341/563] Remove unused `_searchBarController` from CreateGroupView --- lib/presentation/views/main_menu/create_group_view.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f593efe..2fe2fc5 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -18,7 +18,6 @@ class CreateGroupView extends StatefulWidget { class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); - final _searchBarController = TextEditingController(); late final AppDatabase db; List selectedPlayers = []; @@ -29,15 +28,11 @@ class _CreateGroupViewState extends State { _groupNameController.addListener(() { setState(() {}); }); - _searchBarController.addListener(() { - setState(() {}); - }); } @override void dispose() { _groupNameController.dispose(); - _searchBarController.dispose(); super.dispose(); } @@ -70,7 +65,6 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - controller: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; }, @@ -92,8 +86,6 @@ class _CreateGroupViewState extends State { ); if (!context.mounted) return; if (success) { - _groupNameController.clear(); - _searchBarController.clear(); Navigator.pop(context); } else { ScaffoldMessenger.of(context).showSnackBar( -- 2.49.1 From ebb531d825cc3bcc4dde2201cd0829022f8b26d0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:28 +0100 Subject: [PATCH 342/563] initialize `_searchBarController` internally instead of using widget controller --- lib/presentation/widgets/player_selection.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index cf70072..2e42d80 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -27,7 +27,8 @@ class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; - late final TextEditingController _searchBarController; + late final TextEditingController _searchBarController = + TextEditingController(); late final AppDatabase db; late Future> _allPlayersFuture; late final List skeletonData = List.filled( @@ -39,7 +40,6 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController = widget.controller; loadPlayerList(); } -- 2.49.1 From f8c0dbba5a8718e0cee4006893938fdc00743cf8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:53 +0100 Subject: [PATCH 343/563] Remove unused TextEditingController from PlayerSelection widget --- lib/presentation/widgets/player_selection.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 2e42d80..1c5120f 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,14 +10,9 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController controller; final Function(List value) onChanged; - const PlayerSelection({ - super.key, - required this.controller, - required this.onChanged, - }); + const PlayerSelection({super.key, required this.onChanged}); @override State createState() => _PlayerSelectionState(); -- 2.49.1 From 7be80e6f917a2a2e45e55115fb603d273c872df7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:44:41 +0100 Subject: [PATCH 344/563] Translate player selection UI text to English --- lib/presentation/widgets/player_selection.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 1c5120f..d1ea90e 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -98,7 +98,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - 'Ausgewählte Spieler: (${selectedPlayers.length})', + 'Selected players: (${selectedPlayers.length})', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -131,7 +131,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), const Text( - 'Alle Spieler:', + 'All players:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), -- 2.49.1 From 0653700f9ce8f2547a971f44e1add59a1301c880 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:56:57 +0100 Subject: [PATCH 345/563] Added comments to explain player filtering and selection logic in PlayerSelection widget --- lib/presentation/widgets/player_selection.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d1ea90e..561f27c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -78,11 +78,15 @@ class _PlayerSelectionState extends State { }, onChanged: (value) { setState(() { + // Filters the list of suggested players based on the search input. if (value.isEmpty) { + // If the search is empty, it shows all unselected players. suggestedPlayers = allPlayers.where((player) { return !selectedPlayers.contains(player); }).toList(); } else { + // If there is input, it filters by name match (case-insensitive) and ensures + // that already selected players are excluded from the results. suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name.toLowerCase().contains( value.toLowerCase(), @@ -108,21 +112,22 @@ class _PlayerSelectionState extends State { spacing: 8.0, runSpacing: 8.0, children: [ + // Generates a TextIconTile for each selected player. for (var player in selectedPlayers) TextIconTile( text: player.name, onIconTap: () { setState(() { + // Removes the player from the selection and notifies the parent. final currentSearch = _searchBarController.text .toLowerCase(); selectedPlayers.remove(player); widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), + // they are added back to the suggestions and the list is re-sorted. if (currentSearch.isEmpty || player.name.toLowerCase().contains(currentSearch)) { suggestedPlayers.add(player); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); } }); }, -- 2.49.1 From 51e3c04e723e25f61a33b0bf025ccb2809f7e773 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 22:08:42 +0100 Subject: [PATCH 346/563] Refactor PlayerSelection to use AppSkeleton widget --- .../views/main_menu/create_group_view.dart | 1 - lib/presentation/widgets/player_selection.dart | 18 ++---------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 3055804..2fe2fc5 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -4,7 +4,6 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 561f27c..ad15363 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; @@ -170,22 +170,8 @@ class _PlayerSelectionState extends State { final bool isLoading = snapshot.connectionState == ConnectionState.waiting; return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: Visibility( visible: (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), -- 2.49.1 From 7cfffadb863a0032c9c5b95c2fb4882b335c598c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 22:20:44 +0100 Subject: [PATCH 347/563] Corrected import --- lib/presentation/widgets/player_selection.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index ad15363..592226c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -58,11 +58,7 @@ class _PlayerSelectionState extends State { return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ -- 2.49.1 From e489d16c5109c192600051b9fb643ed8c735bd21 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 22:20:53 +0100 Subject: [PATCH 348/563] Removed imports --- lib/presentation/views/main_menu/create_group_view.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 6f70d7c..cbaee6d 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -6,12 +6,7 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; -import 'package:game_tracker/presentation/widgets/text_input_field.dart'; -import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class CreateGroupView extends StatefulWidget { -- 2.49.1 From 5fbf2ccb45da517435b402027e6b4376cb974e10 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 22:21:27 +0100 Subject: [PATCH 349/563] Implemented app skeleton --- .../main_menu/create_game/create_game_view.dart | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 82f44a9..4c892b9 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -6,10 +6,10 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class CreateGameView extends StatefulWidget { const CreateGameView({super.key}); @@ -100,21 +100,8 @@ class _CreateGameViewState extends State { centerTitle: true, ), body: SafeArea( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ -- 2.49.1 From 07d81d687bffd43034696fdca0f39f142785cbce Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:23:03 +0100 Subject: [PATCH 350/563] Implement CustomRadioListTile and update GameResultView to select a winner currently without saving to the db --- .../views/main_menu/game_result_view.dart | 50 +++++++++++++++---- .../widgets/tiles/custom_radio_list_tile.dart | 49 ++++++++++++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 lib/presentation/widgets/tiles/custom_radio_list_tile.dart diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 9278641..aa15c2a 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -3,6 +3,8 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameResultView extends StatefulWidget { final Game game; @@ -15,6 +17,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; + Player? _player; @override void initState() { @@ -64,16 +67,36 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - Expanded( - child: ListView.builder( - itemCount: allPlayers.length, - itemBuilder: (context, index) { - //TODO: Implement Custom RadioListTile, see text_icon_list_tile - return RadioListTile( - title: Text(allPlayers[index].name), - value: allPlayers[index], - ); - }, + Visibility( + visible: allPlayers.isNotEmpty, + replacement: TopCenteredMessage( + icon: Icons.info, + title: "Info", + message: "No players in this game.", + ), + child: Expanded( + child: RadioGroup( + groupValue: _player, + onChanged: (Player? value) { + setState(() { + _player = value; + }); + }, + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) { + setState(() { + _player = value; + }); + }, + ); + }, + ), + ), ), ), ], @@ -83,7 +106,12 @@ class _GameResultViewState extends State { CustomWidthButton( text: "Save", sizeRelativeToWidth: 0.95, - onPressed: null, + onPressed: _player != null + ? () { + print("Selected Winner: ${_player!.name}"); + Navigator.pop(context); + } + : null, ), SizedBox(height: 10), ], diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart new file mode 100644 index 0000000..1350239 --- /dev/null +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomRadioListTile extends StatelessWidget { + final String text; + final T value; + final ValueChanged onContainerTap; + + const CustomRadioListTile({ + super.key, + required this.text, + required this.value, + required this.onContainerTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onContainerTap(value), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + children: [ + Radio(value: value, activeColor: CustomTheme.primaryColor), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ); + } +} -- 2.49.1 From 00fd6880e98cf53ba1ff868e2bf561db7d97072b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:24:35 +0100 Subject: [PATCH 351/563] add todo comment --- lib/presentation/views/main_menu/game_result_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index aa15c2a..94d7182 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -108,7 +108,9 @@ class _GameResultViewState extends State { sizeRelativeToWidth: 0.95, onPressed: _player != null ? () { - print("Selected Winner: ${_player!.name}"); + print( + "Selected Winner: ${_player!.name}", + ); //TODO: Add winner to db Navigator.pop(context); } : null, -- 2.49.1 From d97871d15b134ef6bb2c9034ffd2a0853f233bb3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 17:29:21 +0100 Subject: [PATCH 352/563] fix lint --- .../views/main_menu/custom_navigation_bar.dart | 16 ++++++++-------- .../views/main_menu/game_result_view.dart | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index b20f80e..4003a5e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -62,18 +62,18 @@ class _CustomNavigationBarState extends State MaterialPageRoute( builder: (_) => GameResultView( game: Game( - name: "Test Game", + name: 'Test Game', players: [ - Player(name: "Petrus"), - Player(name: "Peter"), - Player(name: "Petra"), + Player(name: 'Petrus'), + Player(name: 'Peter'), + Player(name: 'Petra'), ], group: Group( - name: "Die Petris", + name: 'Die Petris', members: [ - Player(name: "Petralia"), - Player(name: "Petrenlia"), - Player(name: "Petrumlia"), + Player(name: 'Petralia'), + Player(name: 'Petrenlia'), + Player(name: 'Petrumlia'), ], ), ), diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 94d7182..2dcabc4 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -61,7 +61,7 @@ class _GameResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Select Winner:", + 'Select Winner:', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -69,10 +69,10 @@ class _GameResultViewState extends State { ), Visibility( visible: allPlayers.isNotEmpty, - replacement: TopCenteredMessage( + replacement: const TopCenteredMessage( icon: Icons.info, - title: "Info", - message: "No players in this game.", + title: 'Info', + message: 'No players in this game.', ), child: Expanded( child: RadioGroup( @@ -104,18 +104,18 @@ class _GameResultViewState extends State { ), ), CustomWidthButton( - text: "Save", + text: 'Save', sizeRelativeToWidth: 0.95, onPressed: _player != null ? () { print( - "Selected Winner: ${_player!.name}", + 'Selected Winner: ${_player!.name}', ); //TODO: Add winner to db Navigator.pop(context); } : null, ), - SizedBox(height: 10), + const SizedBox(height: 10), ], ), ), -- 2.49.1 From 479e9a2575195b2bb580464ba1f98ccacf2ffc83 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 22:01:49 +0100 Subject: [PATCH 353/563] add spacing between title and list and rename appbar title to game name --- .../views/main_menu/game_result_view.dart | 7 ++--- .../widgets/tiles/custom_radio_list_tile.dart | 27 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 2dcabc4..07f200c 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -17,7 +17,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; - Player? _player; + Player? _player; //TODO: Set last winner as selected @override void initState() { @@ -32,8 +32,8 @@ class _GameResultViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Game Result', + title: Text( + widget.game.name, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -67,6 +67,7 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), + SizedBox(height: 2), Visibility( visible: allPlayers.isNotEmpty, replacement: const TopCenteredMessage( diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 1350239..5081bad 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -25,23 +25,20 @@ class CustomRadioListTile extends StatelessWidget { border: Border.all(color: CustomTheme.boxBorder), borderRadius: BorderRadius.circular(12), ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: Row( - children: [ - Radio(value: value, activeColor: CustomTheme.primaryColor), - Expanded( - child: Text( - text, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + child: Row( + children: [ + Radio(value: value, activeColor: CustomTheme.primaryColor), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), ), - ], - ), + ), + ], ), ), ); -- 2.49.1 From 86ec4de5c063732a2ddbeca7bd7f8747b9c32242 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 22:03:38 +0100 Subject: [PATCH 354/563] add textoverflow behaviour --- lib/presentation/views/main_menu/game_result_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 07f200c..4103379 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -34,7 +34,11 @@ class _GameResultViewState extends State { scrolledUnderElevation: 0, title: Text( widget.game.name, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, + ), ), centerTitle: true, ), -- 2.49.1 From 2838376434886f574c5e8c3ca874c4ccd84f64c7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 25 Nov 2025 22:38:54 +0100 Subject: [PATCH 355/563] Implemented player selection --- .../create_game/create_game_view.dart | 305 +++++++++--------- .../widgets/player_selection.dart | 18 +- 2 files changed, 170 insertions(+), 153 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4c892b9..5f3b97b 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -4,10 +4,11 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:provider/provider.dart'; @@ -19,12 +20,23 @@ class CreateGameView extends StatefulWidget { } class _CreateGameViewState extends State { + /// Reference to the app database late final AppDatabase db; + + /// Futures to load all groups and players from the database late Future> _allGroupsFuture; + + /// Future to load all players from the database + late Future> _allPlayersFuture; + + /// Controller for the game name input field final TextEditingController _gameNameController = TextEditingController(); /// List of all groups from the database - late final List groupsList; + List groupsList = []; + + /// List of all players from the database + List playerList = []; /// The currently selected group Group? selectedGroup; @@ -40,8 +52,6 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; - bool isLoading = true; - /// List of available rulesets with their display names and descriptions /// as tuples of (Ruleset, String, String) List<(Ruleset, String, String)> rulesets = [ @@ -73,16 +83,11 @@ class _CreateGameViewState extends State { db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); + _allPlayersFuture = db.playerDao.getAllPlayers(); - Future.wait([_allGroupsFuture]).then((result) async { - await Future.delayed(const Duration(milliseconds: 250)); - groupsList = result[0]; - - if (mounted) { - setState(() { - isLoading = false; - }); - } + Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { + groupsList = result[0] as List; + playerList = result[1] as List; }); } @@ -100,147 +105,149 @@ class _CreateGameViewState extends State { centerTitle: true, ), body: SafeArea( - child: AppSkeleton( - enabled: isLoading, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextInputField( + controller: _gameNameController, + hintText: 'Game name', + onChanged: (value) { + setState(() {}); + }, + ), + ), + GestureDetector( + onTap: () async { + selectedRuleset = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseRulesetView( + rulesets: rulesets, + initialRulesetIndex: selectedRulesetIndex, + ), + ), + ); + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + padding: const EdgeInsets.symmetric( vertical: 10, + horizontal: 15, ), - child: TextInputField( - controller: _gameNameController, - hintText: 'Game name', - onChanged: (value) { - setState(() {}); - }, - ), - ), - - GestureDetector( - onTap: () async { - selectedRuleset = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseRulesetView( - rulesets: rulesets, - initialRulesetIndex: selectedRulesetIndex, - ), - ), - ); - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - setState(() {}); - }, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Ruleset', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Text( - selectedRuleset == null - ? 'None' - : translateRulesetToString(selectedRuleset!), - ), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], - ), - ), - ), - GestureDetector( - onTap: () async { - selectedGroup = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseGroupView( - groups: groupsList, - initialGroupIndex: selectedGroupIndex, - ), - ), - ); - selectedGroupIndex = groupsList.indexWhere( - (g) => g.id == selectedGroup?.id, - ); - setState(() {}); - }, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Group', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Text( - selectedGroup == null ? 'None' : selectedGroup!.name, - ), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], - ), - ), - ), - Container( decoration: CustomTheme.standardBoxDecoration, - width: MediaQuery.of(context).size.width * 0.95, - height: 400, - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.symmetric(vertical: 10), - child: const Center(child: Text('PlayerComponent')), + child: Row( + children: [ + const Text( + 'Ruleset', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + selectedRuleset == null + ? 'None' + : translateRulesetToString(selectedRuleset!), + ), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), ), - const Spacer(), - CustomWidthButton( - text: 'Create game', - sizeRelativeToWidth: 0.95, - buttonType: ButtonType.primary, - onPressed: - (_gameNameController.text.isEmpty || - selectedGroup == null || - selectedRuleset == null) - ? null - : () async { - Game game = Game( - name: _gameNameController.text.trim(), - createdAt: DateTime.now(), - group: selectedGroup!, - ); - // TODO: Replace with navigation to GameResultView() - print('Created game: $game'); - Navigator.pop(context); - }, + ), + GestureDetector( + onTap: () async { + selectedGroup = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGroupView( + groups: groupsList, + initialGroupIndex: selectedGroupIndex, + ), + ), + ); + selectedGroupIndex = groupsList.indexWhere( + (g) => g.id == selectedGroup?.id, + ); + print('selectedGroup: $selectedGroup'); + print( + playerList + .where( + (p) => !selectedGroup!.members.any((m) => m.id == p.id), + ) + .toList(), + ); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 15, + ), + decoration: CustomTheme.standardBoxDecoration, + child: Row( + children: [ + const Text( + 'Group', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text(selectedGroup == null ? 'None' : selectedGroup!.name), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), ), - const SizedBox(height: 20), - ], - ), + ), + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialPlayers: selectedGroup == null + ? playerList + : playerList + .where( + (p) => !selectedGroup!.members.any( + (m) => m.id == p.id, + ), + ) + .toList(), + onChanged: (value) { + print(value); + }, + ), + ), + + CustomWidthButton( + text: 'Create game', + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: + (_gameNameController.text.isEmpty || + selectedGroup == null || + selectedRuleset == null) + ? null + : () async { + Game game = Game( + name: _gameNameController.text.trim(), + createdAt: DateTime.now(), + group: selectedGroup!, + ); + // TODO: Replace with navigation to GameResultView() + print('Created game: $game'); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20), + ], ), ), ); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 592226c..092a613 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,8 +11,13 @@ import 'package:provider/provider.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; + final List initialPlayers; - const PlayerSelection({super.key, required this.onChanged}); + const PlayerSelection({ + super.key, + required this.onChanged, + this.initialPlayers = const [], + }); @override State createState() => _PlayerSelectionState(); @@ -46,9 +51,14 @@ class _PlayerSelectionState extends State { suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; + if (widget.initialPlayers.isNotEmpty) { + allPlayers = [...widget.initialPlayers]; + suggestedPlayers = [...widget.initialPlayers]; + } else { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } }); }); } -- 2.49.1 From 9ba3dd7909028ef6365607f395e72ada5f4a3794 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 25 Nov 2025 23:22:02 +0100 Subject: [PATCH 356/563] added missing consts --- lib/presentation/views/main_menu/game_result_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 4103379..f86ded1 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -34,7 +34,7 @@ class _GameResultViewState extends State { scrolledUnderElevation: 0, title: Text( widget.game.name, - style: TextStyle( + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, @@ -71,7 +71,7 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - SizedBox(height: 2), + const SizedBox(height: 2), Visibility( visible: allPlayers.isNotEmpty, replacement: const TopCenteredMessage( -- 2.49.1 From 733df2dcb595d386f2088d120f7f343fe6a98386 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 12:27:07 +0100 Subject: [PATCH 357/563] Changed highlight color --- lib/core/custom_theme.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index b80b4f6..5930901 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -16,9 +16,9 @@ class CustomTheme { static BoxDecoration highlightedBoxDecoration = BoxDecoration( color: boxColor, - border: Border.all(color: Colors.blue), + border: Border.all(color: primaryColor), borderRadius: BorderRadius.circular(12), - boxShadow: [BoxShadow(color: Colors.blue.withAlpha(120), blurRadius: 12)], + boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)], ); static AppBarTheme appBarTheme = AppBarTheme( -- 2.49.1 From 84338f8f663d3a5ceacbe5c5c2f1986865de5485 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 12:28:11 +0100 Subject: [PATCH 358/563] Changed title --- .../views/main_menu/create_game/choose_ruleset_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 772cef5..b54f56e 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -36,7 +36,7 @@ class _ChooseRulesetViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: const Text( - 'Choose Gametype', + 'Choose Ruleset', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, -- 2.49.1 From 27424694ce8f646acc8f050f795bdbd0ed4fbc11 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 12:31:33 +0100 Subject: [PATCH 359/563] Removed unnecessary prints --- .../views/main_menu/create_game/create_game_view.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 5f3b97b..e17e9b8 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -174,14 +174,6 @@ class _CreateGameViewState extends State { selectedGroupIndex = groupsList.indexWhere( (g) => g.id == selectedGroup?.id, ); - print('selectedGroup: $selectedGroup'); - print( - playerList - .where( - (p) => !selectedGroup!.members.any((m) => m.id == p.id), - ) - .toList(), - ); setState(() {}); }, child: Container( -- 2.49.1 From 919c9f57acfa976e07553d93b10cda8a2f68a12f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 12:35:34 +0100 Subject: [PATCH 360/563] Fixed button state --- .../create_game/create_game_view.dart | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index e17e9b8..9b9b4d7 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -52,6 +52,9 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + /// The currently selected players + List? selectedPlayers; + /// List of available rulesets with their display names and descriptions /// as tuples of (Ruleset, String, String) List<(Ruleset, String, String)> rulesets = [ @@ -213,7 +216,9 @@ class _CreateGameViewState extends State { ) .toList(), onChanged: (value) { - print(value); + setState(() { + selectedPlayers = value; + }); }, ), ), @@ -222,21 +227,19 @@ class _CreateGameViewState extends State { text: 'Create game', sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, - onPressed: - (_gameNameController.text.isEmpty || - selectedGroup == null || - selectedRuleset == null) - ? null - : () async { + onPressed: _enableCreateGameButton() + ? () async { Game game = Game( name: _gameNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup!, + players: selectedPlayers, ); // TODO: Replace with navigation to GameResultView() print('Created game: $game'); Navigator.pop(context); - }, + } + : null, ), const SizedBox(height: 20), ], @@ -258,4 +261,13 @@ class _CreateGameViewState extends State { return 'Least Points'; } } + + /// Determines whether the "Create Game" button should be enabled based on + /// the current state of the input fields. + bool _enableCreateGameButton() { + return _gameNameController.text.isNotEmpty && + (selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.isNotEmpty)) && + selectedRuleset != null; + } } -- 2.49.1 From b5234c765ce20841d0a6445d1cb443f7ae2bc730 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 12:40:06 +0100 Subject: [PATCH 361/563] Changed create game button size --- lib/presentation/views/main_menu/game_history_view.dart | 4 ++-- lib/presentation/views/main_menu/groups_view.dart | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index d28943f..7fbe025 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -178,12 +178,12 @@ class _GameHistoryViewState extends State { ), ), Positioned( - bottom: 110, + bottom: MediaQuery.paddingOf(context).bottom, width: MediaQuery.of(context).size.width, child: Center( child: CustomWidthButton( text: 'Create Game', - sizeRelativeToWidth: 0.95, + sizeRelativeToWidth: 0.90, onPressed: () { Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 29fbac8..4601ef9 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -92,7 +92,6 @@ class _GroupsViewState extends State { ); }, ), - Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( -- 2.49.1 From 745aaef978dcfc99055dc8130290a2effba9cad8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 13:12:15 +0100 Subject: [PATCH 362/563] Implemented ChooseTile --- .../create_game/create_game_view.dart | 67 ++++--------------- .../widgets/tiles/choose_tile.dart | 43 ++++++++++++ 2 files changed, 56 insertions(+), 54 deletions(-) create mode 100644 lib/presentation/widgets/tiles/choose_tile.dart diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 9b9b4d7..485118b 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -10,6 +10,7 @@ import 'package:game_tracker/presentation/views/main_menu/create_game/choose_rul import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; class CreateGameView extends StatefulWidget { @@ -121,8 +122,12 @@ class _CreateGameViewState extends State { }, ), ), - GestureDetector( - onTap: () async { + ChooseTile( + title: 'Ruleset', + trailingText: selectedRuleset == null + ? 'None' + : translateRulesetToString(selectedRuleset!), + onPressed: () async { selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( @@ -136,36 +141,13 @@ class _CreateGameViewState extends State { ); setState(() {}); }, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Ruleset', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Text( - selectedRuleset == null - ? 'None' - : translateRulesetToString(selectedRuleset!), - ), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], - ), - ), ), - GestureDetector( - onTap: () async { + ChooseTile( + title: 'Group', + trailingText: selectedGroup == null + ? 'None' + : selectedGroup!.name, + onPressed: () async { selectedGroup = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseGroupView( @@ -179,29 +161,6 @@ class _CreateGameViewState extends State { ); setState(() {}); }, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 15, - ), - decoration: CustomTheme.standardBoxDecoration, - child: Row( - children: [ - const Text( - 'Group', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Text(selectedGroup == null ? 'None' : selectedGroup!.name), - const SizedBox(width: 10), - const Icon(Icons.arrow_forward_ios, size: 16), - ], - ), - ), ), Expanded( child: PlayerSelection( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart new file mode 100644 index 0000000..10a695d --- /dev/null +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class ChooseTile extends StatefulWidget { + final String title; + final VoidCallback? onPressed; + final String? trailingText; + const ChooseTile({ + super.key, + required this.title, + this.trailingText, + this.onPressed, + }); + + @override + State createState() => _ChooseTileState(); +} + +class _ChooseTileState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onPressed, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + decoration: CustomTheme.standardBoxDecoration, + child: Row( + children: [ + Text( + widget.title, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const Spacer(), + if (widget.trailingText != null) Text(widget.trailingText!), + const SizedBox(width: 10), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ); + } +} -- 2.49.1 From 738f242eee50b4a3a35ee25b336f4880fd722b6b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 13:48:53 +0100 Subject: [PATCH 363/563] Implemented methods and test for winner --- lib/data/dao/game_dao.dart | 45 ++++++++++++++++++++++ test/db_tests/game_test.dart | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 18792b5..ad6b090 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -253,4 +253,49 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final rowsAffected = await query.go(); return rowsAffected > 0; } + + /// Sets the winner of the game with the given [gameId] to the player with + /// the given [winnerId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future setWinner({ + required String gameId, + required String winnerId, + }) async { + final query = update(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.write( + GameTableCompanion(winnerId: Value(winnerId)), + ); + return rowsAffected > 0; + } + + /// Retrieves the winner of the game with the given [gameId]. + /// Returns the [Player] who won the game, or `null` if no winner is set. + Future getWinner({required String gameId}) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); + final result = await query.getSingleOrNull(); + if (result == null || result.winnerId == null) { + return null; + } + final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + return winner; + } + + /// Removes the winner of the game with the given [gameId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeWinner({required String gameId}) async { + final query = update(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.write( + const GameTableCompanion(winnerId: Value(null)), + ); + return rowsAffected > 0; + } + + /// Checks if the game with the given [gameId] has a winner set. + /// Returns `true` if a winner is set, otherwise `false`. + Future hasWinner({required String gameId}) async { + final query = select(gameTable) + ..where((g) => g.id.equals(gameId) & g.winnerId.isNotNull()); + final result = await query.getSingleOrNull(); + return result != null; + } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 4cf6982..ff13892 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -234,5 +234,79 @@ void main() { gameCount = await database.gameDao.getGameCount(); expect(gameCount, 0); }); + + test('Checking if game has winner works correclty', () async { + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGameOnlyGroup); + + var hasWinner = await database.gameDao.hasWinner(gameId: testGame1.id); + expect(hasWinner, true); + + hasWinner = await database.gameDao.hasWinner( + gameId: testGameOnlyGroup.id, + ); + expect(hasWinner, false); + }); + + test('Fetching the winner of a game works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + final winner = await database.gameDao.getWinner(gameId: testGame1.id); + if (winner == null) { + fail('Winner is null'); + } else { + expect(winner.id, testGame1.winner!.id); + expect(winner.name, testGame1.winner!.name); + expect(winner.createdAt, testGame1.winner!.createdAt); + } + }); + + test('Updating the winner of a game works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + final winner = await database.gameDao.getWinner(gameId: testGame1.id); + if (winner == null) { + fail('Winner is null'); + } else { + expect(winner.id, testGame1.winner!.id); + expect(winner.name, testGame1.winner!.name); + expect(winner.createdAt, testGame1.winner!.createdAt); + expect(winner.id, testPlayer4.id); + expect(winner.id != testPlayer5.id, true); + } + + await database.gameDao.setWinner( + gameId: testGame1.id, + winnerId: testPlayer5.id, + ); + + final newWinner = await database.gameDao.getWinner(gameId: testGame1.id); + + if (newWinner == null) { + fail('New winner is null'); + } else { + expect(newWinner.id, testPlayer5.id); + expect(newWinner.name, testPlayer5.name); + expect(newWinner.createdAt, testPlayer5.createdAt); + } + }); + + test('Removing a winner works correctly', () async { + await database.gameDao.addGame(game: testGame2); + + var hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + expect(hasWinner, true); + + await database.gameDao.removeWinner(gameId: testGame2.id); + + hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + expect(hasWinner, false); + + final removedWinner = await database.gameDao.getWinner( + gameId: testGame2.id, + ); + + expect(removedWinner, null); + }); }); } -- 2.49.1 From 397c5c1550f328924c373b74165c01807ae574bc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 14:17:11 +0100 Subject: [PATCH 364/563] Added updateGroupOfGame(), added docc & tests --- lib/data/dao/game_dao.dart | 10 +++- lib/data/dao/group_game_dao.dart | 21 +++++++- test/db_tests/group_game_test.dart | 80 +++++++++++++++++++++++------- 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index ad6b090..6d9f316 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -65,6 +65,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Adds a new [Game] to the database. /// Also adds associated players and group if they exist. + /// If a game, player, or group already exists, it will be replaced. Future addGame({required Game game}) async { await db.transaction(() async { await into(gameTable).insert( @@ -89,11 +90,18 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { if (game.group != null) { await db.groupDao.addGroup(group: game.group!); - await db.groupGameDao.addGroupToGame(game.id, game.group!.id); + await db.groupGameDao.addGroupToGame( + gameId: game.id, + groupId: game.group!.id, + ); } }); } + /// Adds multiple [Game]s to the database in a batch operation. + /// Also adds associated players and groups if they exist. + /// If the [games] list is empty, the method returns immediately. + /// If a game, player, or group already exists, it will be replaced. Future addGames({required List games}) async { if (games.isEmpty) return; await db.transaction(() async { diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index f3ddcc7..3f7a146 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -11,8 +11,12 @@ class GroupGameDao extends DatabaseAccessor GroupGameDao(super.db); /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. - Future addGroupToGame(String gameId, String groupId) async { + /// [GroupGameTable]. If there is already group associated to the game, + /// it will be replaced. + Future addGroupToGame({ + required String gameId, + required String groupId, + }) async { await into(groupGameTable).insert( GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace, @@ -76,4 +80,17 @@ class GroupGameDao extends DatabaseAccessor final rowsAffected = await query.go(); return rowsAffected > 0; } + + /// Updates the group associated with a game to [newGroupId] based on + /// [gameId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateGroupOfGame({ + required String gameId, + required String newGroupId, + }) async { + final updatedRows = + await (update(groupGameTable)..where((g) => g.gameId.equals(gameId))) + .write(GroupGameTableCompanion(groupId: Value(newGroupId))); + return updatedRows > 0; + } } diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 1733243..1e9b8fc 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -14,7 +14,8 @@ void main() { late Player testPlayer3; late Player testPlayer4; late Player testPlayer5; - late Group testgroup; + late Group testGroup1; + late Group testGroup2; late Game testgameWithGroup; late Game testgameWithPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); @@ -35,15 +36,19 @@ void main() { testPlayer3 = Player(name: 'Charlie'); testPlayer4 = Player(name: 'Diana'); testPlayer5 = Player(name: 'Eve'); - testgroup = Group( + testGroup1 = Group( name: 'Test Group', members: [testPlayer1, testPlayer2, testPlayer3], ); + testGroup2 = Group( + name: 'Test Group', + members: [testPlayer3, testPlayer2], + ); testgameWithPlayers = Game( name: 'Test Game with Players', players: [testPlayer4, testPlayer5], ); - testgameWithGroup = Game(name: 'Test Game with Group', group: testgroup); + testgameWithGroup = Game(name: 'Test Game with Group', group: testGroup1); }); }); tearDown(() async { @@ -52,7 +57,7 @@ void main() { group('Group-Game Tests', () { test('Game has group works correctly', () async { await database.gameDao.addGame(game: testgameWithPlayers); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); var gameHasGroup = await database.groupGameDao.gameHasGroup( gameId: testgameWithPlayers.id, @@ -61,8 +66,8 @@ void main() { expect(gameHasGroup, false); await database.groupGameDao.addGroupToGame( - testgameWithPlayers.id, - testgroup.id, + gameId: testgameWithPlayers.id, + groupId: testGroup1.id, ); gameHasGroup = await database.groupGameDao.gameHasGroup( @@ -74,15 +79,15 @@ void main() { test('Adding a group to a game works correctly', () async { await database.gameDao.addGame(game: testgameWithPlayers); - await database.groupDao.addGroup(group: testgroup); + await database.groupDao.addGroup(group: testGroup1); await database.groupGameDao.addGroupToGame( - testgameWithPlayers.id, - testgroup.id, + gameId: testgameWithPlayers.id, + groupId: testGroup1.id, ); var groupAdded = await database.groupGameDao.isGroupInGame( gameId: testgameWithPlayers.id, - groupId: testgroup.id, + groupId: testGroup1.id, ); expect(groupAdded, true); @@ -120,14 +125,55 @@ void main() { fail('Group should not be null'); } - expect(group.id, testgroup.id); - expect(group.name, testgroup.name); - expect(group.createdAt, testgroup.createdAt); - expect(group.members.length, testgroup.members.length); + expect(group.id, testGroup1.id); + expect(group.name, testGroup1.name); + expect(group.createdAt, testGroup1.createdAt); + expect(group.members.length, testGroup1.members.length); for (int i = 0; i < group.members.length; i++) { - expect(group.members[i].id, testgroup.members[i].id); - expect(group.members[i].name, testgroup.members[i].name); - expect(group.members[i].createdAt, testgroup.members[i].createdAt); + expect(group.members[i].id, testGroup1.members[i].id); + expect(group.members[i].name, testGroup1.members[i].name); + expect(group.members[i].createdAt, testGroup1.members[i].createdAt); + } + }); + + test('Updating the group of a game works correctly', () async { + await database.gameDao.addGame(game: testgameWithGroup); + + var group = await database.groupGameDao.getGroupOfGame( + gameId: testgameWithGroup.id, + ); + + if (group == null) { + fail('Initial group should not be null'); + } else { + expect(group.id, testGroup1.id); + expect(group.name, testGroup1.name); + expect(group.createdAt, testGroup1.createdAt); + expect(group.members.length, testGroup1.members.length); + } + + await database.groupDao.addGroup(group: testGroup2); + await database.groupGameDao.updateGroupOfGame( + gameId: testgameWithGroup.id, + newGroupId: testGroup2.id, + ); + + group = await database.groupGameDao.getGroupOfGame( + gameId: testgameWithGroup.id, + ); + + if (group == null) { + fail('Updated group should not be null'); + } else { + expect(group.id, testGroup2.id); + expect(group.name, testGroup2.name); + expect(group.createdAt, testGroup2.createdAt); + expect(group.members.length, testGroup2.members.length); + for (int i = 0; i < group.members.length; i++) { + expect(group.members[i].id, testGroup2.members[i].id); + expect(group.members[i].name, testGroup2.members[i].name); + expect(group.members[i].createdAt, testGroup2.members[i].createdAt); + } } }); }); -- 2.49.1 From 499415e0c5cf5fa0bf9f9eb3a26494b09fec9757 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 14:39:38 +0100 Subject: [PATCH 365/563] Added updatePlayersFromGame(), added docs & tests --- lib/data/dao/player_game_dao.dart | 46 +++++++++++++++++++++++++++++ test/db_tests/player_game_test.dart | 44 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_game_dao.dart index ef15a80..b7f253f 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_game_dao.dart @@ -79,4 +79,50 @@ class PlayerGameDao extends DatabaseAccessor final rowsAffected = await query.go(); return rowsAffected > 0; } + + /// Updates the players associated with a game based on the provided + /// [newPlayer] list. It adds new players and removes players that are no + /// longer associated with the game. + Future updatePlayersFromGame({ + required String gameId, + required List newPlayer, + }) async { + final currentPlayers = await getPlayersOfGame(gameId: gameId); + // Create sets of player IDs for easy comparison + final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {}; + final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); + + // Determine players to add and remove + final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds); + final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet); + + db.transaction(() async { + // Remove old players + if (playersToRemove.isNotEmpty) { + await (delete(playerGameTable)..where( + (pg) => + pg.gameId.equals(gameId) & + pg.playerId.isIn(playersToRemove.toList()), + )) + .go(); + } + + // Add new players + if (playersToAdd.isNotEmpty) { + final inserts = playersToAdd + .map( + (id) => + PlayerGameTableCompanion.insert(playerId: id, gameId: gameId), + ) + .toList(); + await Future.wait( + inserts.map( + (c) => into( + playerGameTable, + ).insert(c, mode: InsertMode.insertOrReplace), + ), + ); + } + }); + } } diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index e8fd707..4c3bc77 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -136,5 +136,49 @@ void main() { expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); } }); + + test('Updating the games players works coreclty', () async { + await database.gameDao.addGame(game: testGameOnlyPlayers); + + final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; + await database.playerDao.addPlayers(players: newPlayers); + + // First, remove all existing players + final existingPlayers = await database.playerGameDao.getPlayersOfGame( + gameId: testGameOnlyPlayers.id, + ); + + print('existingPlayers: $existingPlayers'); + if (existingPlayers == null || existingPlayers.isEmpty) { + fail('Existing players should not be null or empty'); + } + + await database.playerGameDao.updatePlayersFromGame( + gameId: testGameOnlyPlayers.id, + newPlayer: newPlayers, + ); + + final updatedPlayers = await database.playerGameDao.getPlayersOfGame( + gameId: testGameOnlyPlayers.id, + ); + + if (updatedPlayers == null) { + fail('Updated players should not be null'); + } + + expect(updatedPlayers.length, newPlayers.length); + + /// Create a map of new players for easy lookup + final testPlayers = {for (var p in newPlayers) p.id: p}; + + /// Verify each updated player matches the new players + for (final player in updatedPlayers) { + final testPlayer = testPlayers[player.id]!; + + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); + } + }); }); } -- 2.49.1 From 2a34243e692910641910dbc22483c12abbc3d310 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 14:42:17 +0100 Subject: [PATCH 366/563] Renamed methods for better distinction --- lib/data/dao/game_dao.dart | 4 ++-- lib/data/dao/group_dao.dart | 2 +- lib/data/dao/player_dao.dart | 2 +- lib/services/data_transfer_service.dart | 6 +++--- test/db_tests/game_test.dart | 2 +- test/db_tests/group_test.dart | 2 +- test/db_tests/player_game_test.dart | 2 +- test/db_tests/player_test.dart | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 6d9f316..6409ba5 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -79,7 +79,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { ); if (game.players != null) { - await db.playerDao.addPlayers(players: game.players!); + await db.playerDao.addPlayersAsList(players: game.players!); for (final p in game.players ?? []) { await db.playerGameDao.addPlayerToGame( gameId: game.id, @@ -102,7 +102,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Also adds associated players and groups if they exist. /// If the [games] list is empty, the method returns immediately. /// If a game, player, or group already exists, it will be replaced. - Future addGames({required List games}) async { + Future addGamesAsList({required List games}) async { if (games.isEmpty) return; await db.transaction(() async { // Add all games in batch diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index fbb4d6f..643bc88 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -84,7 +84,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Adds multiple groups to the database. /// Also adds the group's members to the [PlayerGroupTable]. - Future addGroups({required List groups}) async { + Future addGroupsAsList({required List groups}) async { if (groups.isEmpty) return; await db.transaction(() async { // Deduplicate groups by id - keep first occurrence diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 53e251f..8a58504 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -50,7 +50,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Adds multiple [players] to the database in a batch operation. - Future addPlayers({required List players}) async { + Future addPlayersAsList({required List players}) async { if (players.isEmpty) return false; await db.batch( diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index eaa9633..f040b0a 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -110,9 +110,9 @@ class DataTransferService { .toList() ?? []; - await db.playerDao.addPlayers(players: importedPlayers); - await db.groupDao.addGroups(groups: importedGroups); - await db.gameDao.addGames(games: importedGames); + await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.gameDao.addGamesAsList(games: importedGames); } else { return ImportResult.invalidSchema; } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index ff13892..28126cc 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -112,7 +112,7 @@ void main() { }); test('Adding and fetching multiple games works correctly', () async { - await database.gameDao.addGames( + await database.gameDao.addGamesAsList( games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], ); diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart index a1832c1..5104c65 100644 --- a/test/db_tests/group_test.dart +++ b/test/db_tests/group_test.dart @@ -81,7 +81,7 @@ void main() { }); test('Adding and fetching multiple groups works correctly', () async { - await database.groupDao.addGroups( + await database.groupDao.addGroupsAsList( groups: [testGroup1, testGroup2, testGroup3, testGroup4], ); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 4c3bc77..6fd87c2 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -141,7 +141,7 @@ void main() { await database.gameDao.addGame(game: testGameOnlyPlayers); final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; - await database.playerDao.addPlayers(players: newPlayers); + await database.playerDao.addPlayersAsList(players: newPlayers); // First, remove all existing players final existingPlayers = await database.playerGameDao.getPlayersOfGame( diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart index 9a1ba16..5bd10ad 100644 --- a/test/db_tests/player_test.dart +++ b/test/db_tests/player_test.dart @@ -56,7 +56,7 @@ void main() { }); test('Adding and fetching multiple players works correctly', () async { - await database.playerDao.addPlayers( + await database.playerDao.addPlayersAsList( players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); -- 2.49.1 From dc0e53622134d7a47c66c844701294680d013b77 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 26 Nov 2025 14:44:41 +0100 Subject: [PATCH 367/563] Implemented updateGameName() and tests for it --- lib/data/dao/game_dao.dart | 13 +++++++++++++ test/db_tests/game_test.dart | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 6409ba5..daee4b7 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -306,4 +306,17 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { final result = await query.getSingleOrNull(); return result != null; } + + /// Changes the title of the game with the given [gameId] to [newName]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateGameName({ + required String gameId, + required String newName, + }) async { + final query = update(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.write( + GameTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; + } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 28126cc..4a5cc32 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -308,5 +308,23 @@ void main() { expect(removedWinner, null); }); + + test('Renaming a game works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + var fetchedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(fetchedGame.name, testGame1.name); + + const newName = 'Updated Game Name'; + await database.gameDao.updateGameName( + gameId: testGame1.id, + newName: newName, + ); + + fetchedGame = await database.gameDao.getGameById(gameId: testGame1.id); + expect(fetchedGame.name, newName); + }); }); } -- 2.49.1 From 099e587d458049ff4fe6c51ad92181eefffc8e79 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 16:45:47 +0100 Subject: [PATCH 368/563] remove useless skeleton data --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 60dcd90..df4b7a0 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -19,7 +19,7 @@ class _GameHistoryViewState extends State { late final AppDatabase db; late final List skeletonData = List.filled( - 10, + 4, Game( name: 'Skeleton Game', group: Group( -- 2.49.1 From b443230285f3f9844d02781aa798cbf621da70b3 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 16:58:02 +0100 Subject: [PATCH 369/563] fixed loading too fast --- lib/presentation/views/main_menu/game_history_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index df4b7a0..f6f47ef 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -17,6 +17,7 @@ class GameHistoryView extends StatefulWidget { class _GameHistoryViewState extends State { late Future> _gameListFuture; late final AppDatabase db; + late bool isLoading = true; late final List skeletonData = List.filled( 4, @@ -47,6 +48,9 @@ class _GameHistoryViewState extends State { Future.wait([_gameListFuture]).then((result) async { await Future.delayed(const Duration(milliseconds: 250)); + setState(() { + isLoading = false; + }); }); } @@ -71,7 +75,6 @@ class _GameHistoryViewState extends State { ); } - final bool isLoading = snapshot.connectionState == ConnectionState.waiting; final List games = (isLoading ? skeletonData : (snapshot.data ?? []) -- 2.49.1 From ae348499d410c7e584cb06d971c94a8adb30b9cc Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:00:13 +0100 Subject: [PATCH 370/563] moved functionality methods to the bottom of the file --- .../widgets/tiles/game_history_tile.dart | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index fdc9584..83e0ba0 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -17,47 +17,6 @@ class GameHistoryTile extends StatefulWidget { } class _GameHistoryTileState extends State { - String _formatDate(DateTime dateTime) { - final now = DateTime.now(); - final difference = now.difference(dateTime); - - if (difference.inDays == 0) { - return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; - } else if (difference.inDays == 1) { - return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; - } else if (difference.inDays < 7) { - return '${difference.inDays} days ago'; - } else { - return DateFormat('MMM d, yyyy').format(dateTime); - } - } - - List _getAllPlayers() { - final allPlayers = []; - final playerIds = {}; - - // Add players from game.players - if (widget.game.players != null) { - for (var player in widget.game.players!) { - if (!playerIds.contains(player.id)) { - allPlayers.add(player); - playerIds.add(player.id); - } - } - } - - // Add players from game.group.players - if (widget.game.group?.members != null) { - for (var player in widget.game.group!.members) { - if (!playerIds.contains(player.id)) { - allPlayers.add(player); - playerIds.add(player.id); - } - } - } - - return allPlayers; - } @override Widget build(BuildContext context) { @@ -172,7 +131,6 @@ class _GameHistoryTileState extends State { spacing: 6, runSpacing: 6, children: allPlayers.map((player) { - final isWinner = winner != null && player.id == winner.id; return TextIconTile( text: player.name, iconEnabled: false, @@ -184,4 +142,47 @@ class _GameHistoryTileState extends State { ), ); } + + String _formatDate(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays == 0) { + return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays == 1) { + return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM d, yyyy').format(dateTime); + } + } + + List _getAllPlayers() { + final allPlayers = []; + final playerIds = {}; + + // Add players from game.players + if (widget.game.players != null) { + for (var player in widget.game.players!) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + // Add players from game.group.players + if (widget.game.group?.members != null) { + for (var player in widget.game.group!.members) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + return allPlayers; + } + } \ No newline at end of file -- 2.49.1 From cc50e497c9e671e669fdc9d519cfdfbde26834fd Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:04:08 +0100 Subject: [PATCH 371/563] merge duplicate if statements for group and winner sections --- .../widgets/tiles/game_history_tile.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 83e0ba0..066603e 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -60,7 +60,7 @@ class _GameHistoryTileState extends State { const SizedBox(height: 8), - if (group != null) + if (group != null) ...[ Row( children: [ const Icon( @@ -81,10 +81,10 @@ class _GameHistoryTileState extends State { ), ], ), + const SizedBox(height: 12), + ], - if (group != null) const SizedBox(height: 12), - - if (winner != null) + if (winner != null) ...[ Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: BoxDecoration( @@ -114,8 +114,8 @@ class _GameHistoryTileState extends State { ], ), ), - - if (winner != null) const SizedBox(height: 12), + const SizedBox(height: 12), + ], if (allPlayers.isNotEmpty) ...[ const Text( -- 2.49.1 From 8c005d6e5e074fcbe5aa23d632786f6bf30e2d12 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:06:32 +0100 Subject: [PATCH 372/563] fix possible render overflow --- .../widgets/tiles/game_history_tile.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 066603e..684ba48 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -103,12 +103,15 @@ class _GameHistoryTileState extends State { color: Colors.amber, ), const SizedBox(width: 8), - Text( - 'Winner: ${winner.name}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, ), ), ], -- 2.49.1 From a29123c964cd9c95f22456e7ba987cce6987091f Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 17:25:58 +0100 Subject: [PATCH 373/563] fix error messages --- lib/presentation/views/main_menu/game_history_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index f6f47ef..b5813cf 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -63,7 +63,7 @@ class _GameHistoryViewState extends State { return const Center( heightFactor: 4, child: Text( - 'Error while loading recent games.', + 'Error while loading games.', ), ); } @@ -71,7 +71,7 @@ class _GameHistoryViewState extends State { (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( heightFactor: 4, - child: Text('No recent games available.'), + child: Text('No games available.'), ); } -- 2.49.1 From aa208bb2efabe86109b2202df069d3db7b6c42db Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 22:56:22 +0100 Subject: [PATCH 374/563] use standardized TopCenteredMessage --- .../views/main_menu/game_history_view.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b5813cf..3acc186 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -61,17 +62,21 @@ class _GameHistoryViewState extends State { builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return const Center( - heightFactor: 4, - child: Text( - 'Error while loading games.', + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', ), ); } if (snapshot.connectionState == ConnectionState.done && (!snapshot.hasData || snapshot.data!.isEmpty)) { return const Center( - heightFactor: 4, - child: Text('No games available.'), + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), ); } -- 2.49.1 From 9ee9da2ac8015bb77b0c14debd34a453122265ab Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Thu, 27 Nov 2025 22:59:09 +0100 Subject: [PATCH 375/563] Made space at the bottom of the list smaller --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3acc186..7f7e943 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -107,7 +107,7 @@ class _GameHistoryViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == games.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 20, + height: MediaQuery.paddingOf(context).bottom - 80, ); } return GameHistoryTile(game: games[index]); -- 2.49.1 From 516c2afd1ee3b56b9dc5c6eff6548dfdb1489b2e Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 12:14:22 +0100 Subject: [PATCH 376/563] remove colon behind players --- lib/presentation/widgets/tiles/game_history_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 684ba48..3cdd1ad 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -122,7 +122,7 @@ class _GameHistoryTileState extends State { if (allPlayers.isNotEmpty) ...[ const Text( - 'Players:', + 'Players', style: TextStyle( fontSize: 13, color: Colors.grey, -- 2.49.1 From 40a3c1b82e27e065ea1170955aa7b4733298357d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 13:56:24 +0100 Subject: [PATCH 377/563] Removed comment --- lib/data/dao/group_game_dao.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 3f7a146..12bd1ce 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -11,8 +11,7 @@ class GroupGameDao extends DatabaseAccessor GroupGameDao(super.db); /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. If there is already group associated to the game, - /// it will be replaced. + /// [GroupGameTable]. Future addGroupToGame({ required String gameId, required String groupId, -- 2.49.1 From 126dc7ed9723f8374aede9bd2edae38c768a4c2f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:04 +0100 Subject: [PATCH 378/563] Added exception --- lib/data/dao/group_game_dao.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart index 12bd1ce..da95607 100644 --- a/lib/data/dao/group_game_dao.dart +++ b/lib/data/dao/group_game_dao.dart @@ -16,6 +16,9 @@ class GroupGameDao extends DatabaseAccessor required String gameId, required String groupId, }) async { + if (await gameHasGroup(gameId: gameId)) { + throw Exception('Game already has a group'); + } await into(groupGameTable).insert( GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), mode: InsertMode.insertOrReplace, -- 2.49.1 From d2d6852f31b71aa24d1648a430f48f62a77166e9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:26 +0100 Subject: [PATCH 379/563] removed comment --- lib/data/dao/game_dao.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index daee4b7..5d05914 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -65,7 +65,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Adds a new [Game] to the database. /// Also adds associated players and group if they exist. - /// If a game, player, or group already exists, it will be replaced. Future addGame({required Game game}) async { await db.transaction(() async { await into(gameTable).insert( -- 2.49.1 From 71b2f30d290b31ad3c78887a3b17921065ed8519 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:00:36 +0100 Subject: [PATCH 380/563] removed comment --- lib/data/dao/game_dao.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 5d05914..c941499 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -100,7 +100,6 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Adds multiple [Game]s to the database in a batch operation. /// Also adds associated players and groups if they exist. /// If the [games] list is empty, the method returns immediately. - /// If a game, player, or group already exists, it will be replaced. Future addGamesAsList({required List games}) async { if (games.isEmpty) return; await db.transaction(() async { -- 2.49.1 From 94b113eb95fe7f84d52161d323d707ac0a5583ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:07:42 +0100 Subject: [PATCH 381/563] Renamed widget --- .../views/main_menu/create_game/choose_ruleset_view.dart | 4 ++-- ...uleset_list_tile.dart => title_description_list_tile.dart} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/presentation/widgets/tiles/{ruleset_list_tile.dart => title_description_list_tile.dart} (94%) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index b54f56e..647cdc4 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/presentation/widgets/tiles/ruleset_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { final List<(Ruleset, String, String)> rulesets; @@ -85,7 +85,7 @@ class _ChooseRulesetViewState extends State { padding: const EdgeInsets.only(bottom: 85), itemCount: widget.rulesets.length, itemBuilder: (BuildContext context, int index) { - return RulesetListTile( + return TitleDescriptionListTile( onPressed: () async { setState(() { selectedRulesetIndex = index; diff --git a/lib/presentation/widgets/tiles/ruleset_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart similarity index 94% rename from lib/presentation/widgets/tiles/ruleset_list_tile.dart rename to lib/presentation/widgets/tiles/title_description_list_tile.dart index 13eaf82..5b370d4 100644 --- a/lib/presentation/widgets/tiles/ruleset_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -class RulesetListTile extends StatelessWidget { +class TitleDescriptionListTile extends StatelessWidget { final String title; final String description; final VoidCallback? onPressed; final bool isHighlighted; - const RulesetListTile({ + const TitleDescriptionListTile({ super.key, required this.title, required this.description, -- 2.49.1 From f713bd6fb78cd54e1f8fd0819932689cb33d92cf Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 14:35:20 +0100 Subject: [PATCH 382/563] use custom app skeleton --- .../views/main_menu/game_history_view.dart | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 7f7e943..b5d894d 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,8 +5,8 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; // Add this import import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -86,21 +86,8 @@ class _GameHistoryViewState extends State { ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .toList(); - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), itemCount: games.length + 1, -- 2.49.1 From 236a737fd1ffce9d2cd13db86e46435f6e32a8e7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:41:50 +0100 Subject: [PATCH 383/563] Implemented choose game view --- lib/core/enums.dart | 18 +++- .../create_game/choose_game_view.dart | 65 +++++++++++++ .../create_game/choose_ruleset_view.dart | 92 ++++--------------- .../create_game/create_game_view.dart | 46 +++++++--- .../tiles/title_description_list_tile.dart | 44 +++++++-- 5 files changed, 167 insertions(+), 98 deletions(-) create mode 100644 lib/presentation/views/main_menu/create_game/choose_game_view.dart diff --git a/lib/core/enums.dart b/lib/core/enums.dart index af1f4a6..68752fb 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -27,5 +27,19 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.singleWinner]: The game is won by a single player /// - [Ruleset.singleLoser]: The game is lost by a single player /// - [Ruleset.mostPoints]: The player with the most points wins. -/// - [Ruleset.lastPoints]: The player with the fewest points wins. -enum Ruleset { singleWinner, singleLoser, mostPoints, lastPoints } +/// - [Ruleset.leastPoints]: The player with the fewest points wins. +enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } + +/// Translates a [Ruleset] enum value to its corresponding string representation. +String translateRulesetToString(Ruleset ruleset) { + switch (ruleset) { + case Ruleset.singleWinner: + return 'Single Winner'; + case Ruleset.singleLoser: + return 'Single Loser'; + case Ruleset.mostPoints: + return 'Most Points'; + case Ruleset.leastPoints: + return 'Least Points'; + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart new file mode 100644 index 0000000..1c75b32 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; + +class ChooseGameView extends StatefulWidget { + final List<(String, String, Ruleset)> games; + final int initialGameIndex; + + const ChooseGameView({ + super.key, + required this.games, + required this.initialGameIndex, + }); + + @override + State createState() => _ChooseGameViewState(); +} + +class _ChooseGameViewState extends State { + late int selectedGameIndex; + + @override + void initState() { + selectedGameIndex = widget.initialGameIndex; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Game', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(selectedGameIndex); + }); + }, + ); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 647cdc4..2e45a7c 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/presentation/widgets/tiles/title_description_list_t class ChooseRulesetView extends StatefulWidget { final List<(Ruleset, String, String)> rulesets; final int initialRulesetIndex; + const ChooseRulesetView({ super.key, required this.rulesets, @@ -41,78 +42,25 @@ class _ChooseRulesetViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Container( - color: CustomTheme.backgroundColor, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: TabBar( - padding: const EdgeInsets.symmetric(horizontal: 5), - // Label Settings - labelStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - labelColor: Colors.white, - unselectedLabelStyle: const TextStyle(fontSize: 14), - unselectedLabelColor: Colors.white70, - // Indicator Settings - indicator: CustomTheme.standardBoxDecoration, - indicatorSize: TabBarIndicatorSize.tab, - indicatorWeight: 1, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 0, - ), - // Divider Settings - dividerHeight: 0, - tabs: const [ - Tab(text: 'Rulesets'), - Tab(text: 'Gametypes'), - ], - ), - ), - const Divider( - indent: 30, - endIndent: 30, - thickness: 3, - radius: BorderRadius.all(Radius.circular(12)), - ), - Expanded( - child: TabBarView( - children: [ - ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - selectedRulesetIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of( - context, - ).pop(widget.rulesets[index].$1); - }); - }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, - isHighlighted: selectedRulesetIndex == index, - ); - }, - ), - const Center( - child: Text( - 'No gametypes available', - style: TextStyle(color: Colors.white70), - ), - ), - ], - ), - ), - ], + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + onPressed: () async { + setState(() { + selectedRulesetIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(widget.rulesets[index].$1); + }); + }, + title: widget.rulesets[index].$2, + description: widget.rulesets[index].$3, + isHighlighted: selectedRulesetIndex == index, + ); + }, ), ), ); diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 485118b..68883d0 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -5,6 +5,7 @@ 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/create_game/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -53,6 +54,8 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + int selectedGameIndex = -1; + /// The currently selected players List? selectedPlayers; @@ -75,12 +78,17 @@ class _CreateGameViewState extends State { 'Traditional ruleset: the player with the most points wins.', ), ( - Ruleset.lastPoints, + Ruleset.leastPoints, 'Least Points', 'Inverse scoring: the player with the fewest points wins.', ), ]; + List<(String, String, Ruleset)> games = [ + ('Cabo', 'A memory card game', Ruleset.leastPoints), + ('Uno', 'The Classic', Ruleset.singleWinner), + ]; + @override void initState() { super.initState(); @@ -122,6 +130,27 @@ class _CreateGameViewState extends State { }, ), ), + ChooseTile( + title: 'Game', + trailingText: selectedGameIndex == -1 + ? 'None' + : games[selectedGameIndex].$1, + onPressed: () async { + selectedGameIndex = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGameView( + games: games, + initialGameIndex: selectedGameIndex, + ), + ), + ); + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + setState(() {}); + }, + ), ChooseTile( title: 'Ruleset', trailingText: selectedRuleset == null @@ -139,6 +168,7 @@ class _CreateGameViewState extends State { selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); + selectedGameIndex = -1; setState(() {}); }, ), @@ -207,20 +237,6 @@ class _CreateGameViewState extends State { ); } - /// Translates a [Ruleset] enum value to its corresponding string representation. - String translateRulesetToString(Ruleset ruleset) { - switch (ruleset) { - case Ruleset.singleWinner: - return 'Single Winner'; - case Ruleset.singleLoser: - return 'Single Loser'; - case Ruleset.mostPoints: - return 'Most Points'; - case Ruleset.lastPoints: - return 'Least Points'; - } - } - /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 5b370d4..ac20b3f 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -6,6 +6,8 @@ class TitleDescriptionListTile extends StatelessWidget { final String description; final VoidCallback? onPressed; final bool isHighlighted; + final String? badgeText; + final Color? badgeColor; const TitleDescriptionListTile({ super.key, @@ -13,6 +15,8 @@ class TitleDescriptionListTile extends StatelessWidget { required this.description, this.onPressed, this.isHighlighted = false, + this.badgeText, + this.badgeColor, }); @override @@ -28,20 +32,42 @@ class TitleDescriptionListTile extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Flexible( - child: Text( - title, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + Text( + title, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, ), ), + if (badgeText != null) ...[ + const Spacer(), + Container( + margin: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 6, + ), + decoration: BoxDecoration( + color: badgeColor ?? CustomTheme.primaryColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + badgeText!, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ], ), const SizedBox(height: 5), -- 2.49.1 From fb28de5772ec1de22934a4dc8f28499999377e57 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 28 Nov 2025 14:44:24 +0100 Subject: [PATCH 384/563] add create game button --- .../views/main_menu/game_history_view.dart | 115 +++++++++++------- 1 file changed, 73 insertions(+), 42 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index b5d894d..5689c7b 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; // Add this import +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:provider/provider.dart'; class GameHistoryView extends StatefulWidget { @@ -57,51 +60,79 @@ class _GameHistoryViewState extends State { @override Widget build(BuildContext context) { - return FutureBuilder>( - future: _gameListFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'No Games Available', - ), - ); - } - - final List games = (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .toList(); - - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == games.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + body: Stack( + alignment: Alignment.center, + children: [ + FutureBuilder>( + future: _gameListFuture, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', + ), ); } - return GameHistoryTile(game: games[index]); + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), + ); + } + + final List games = (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .toList(); + + return AppSkeleton( + enabled: isLoading, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, + ); + } + return GameHistoryTile(game: games[index]); // Placeholder + }, + ), + ); }, ), - ); - }, + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: CustomWidthButton( + text: 'Create Game', + sizeRelativeToWidth: 0.90, + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); + }, + ), + ), + ], + ), ); } } \ No newline at end of file -- 2.49.1 From 4dbc106e38de013499e282059200e4c29d3bb301 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:14:13 +0100 Subject: [PATCH 385/563] Added searchbar --- .../create_game/choose_game_view.dart | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 1c75b32..8d8279e 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseGameView extends StatefulWidget { @@ -19,6 +20,7 @@ class ChooseGameView extends StatefulWidget { class _ChooseGameViewState extends State { late int selectedGameIndex; + final TextEditingController searchBarController = TextEditingController(); @override void initState() { @@ -39,26 +41,36 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(selectedGameIndex); - }); - }, - ); - }, + body: Column( + children: [ + CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', + ), + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(selectedGameIndex); + }); + }, + ); + }, + ), + ), + ], ), ); } -- 2.49.1 From bcd7bf751b7ff6ecb90c84798cc837598873ba5a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:14:29 +0100 Subject: [PATCH 386/563] Added todo & example data --- .../views/main_menu/create_game/create_game_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 68883d0..eb5468c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -61,6 +61,7 @@ class _CreateGameViewState extends State { /// List of available rulesets with their display names and descriptions /// as tuples of (Ruleset, String, String) + /// TODO: Replace when rulesets are implemented List<(Ruleset, String, String)> rulesets = [ ( Ruleset.singleWinner, @@ -84,9 +85,10 @@ class _CreateGameViewState extends State { ), ]; + // TODO: Replace when games are implemented List<(String, String, Ruleset)> games = [ - ('Cabo', 'A memory card game', Ruleset.leastPoints), - ('Uno', 'The Classic', Ruleset.singleWinner), + ('Example Game 1', 'This is a discription', Ruleset.leastPoints), + ('Example Game 2', '', Ruleset.singleWinner), ]; @override -- 2.49.1 From b4ccb567b508e85c6d83f79e12f6efe6a2419106 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:15:08 +0100 Subject: [PATCH 387/563] Added conditional description --- .../widgets/tiles/title_description_list_tile.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index ac20b3f..7bc4221 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -32,7 +32,7 @@ class TitleDescriptionListTile extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, @@ -70,9 +70,11 @@ class TitleDescriptionListTile extends StatelessWidget { ], ], ), - const SizedBox(height: 5), - Text(description, style: const TextStyle(fontSize: 14)), - const SizedBox(height: 2.5), + if (description.isNotEmpty) ...[ + const SizedBox(height: 5), + Text(description, style: const TextStyle(fontSize: 14)), + const SizedBox(height: 2.5), + ], ], ), ), -- 2.49.1 From 5ce4964c32b5a1231ab4c2784b938713eef7390a Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 29 Nov 2025 20:04:49 +0100 Subject: [PATCH 388/563] deleted double_row_info_tile --- .../widgets/tiles/double_row_info_tile.dart | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 lib/presentation/widgets/tiles/double_row_info_tile.dart diff --git a/lib/presentation/widgets/tiles/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart deleted file mode 100644 index 57404ff..0000000 --- a/lib/presentation/widgets/tiles/double_row_info_tile.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -Widget doubleRowInfoTile( - String titleOneUpperLeft, - String titleTwoUpperLeft, - String titleUpperRight, - String titleLowerLeft, - String titleLowerRight, -) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: CustomTheme.secondaryColor, - ), - child: Column( - children: [ - Row( - children: [ - Expanded( - flex: 10, - child: Text( - '$titleOneUpperLeft $titleTwoUpperLeft', - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Text( - titleUpperRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - Row( - children: [ - Expanded( - flex: 10, - child: Text( - titleLowerLeft, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 4, - child: Text( - titleLowerRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - ], - ), - ); -} -- 2.49.1 From ec902c6196c283baeb8a18ceaf330c4917688418 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 5 Dec 2025 18:23:58 +0100 Subject: [PATCH 389/563] Removed print --- test/db_tests/player_game_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 6fd87c2..4e2616e 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -148,7 +148,6 @@ void main() { gameId: testGameOnlyPlayers.id, ); - print('existingPlayers: $existingPlayers'); if (existingPlayers == null || existingPlayers.isEmpty) { fail('Existing players should not be null or empty'); } -- 2.49.1 From 3169eebd14c0d29d805f31a369aea82031103ef7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 5 Dec 2025 18:24:06 +0100 Subject: [PATCH 390/563] Import formatting --- .../views/main_menu/game_history_view.dart | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index af18a73..ea5ddd7 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,12 +5,10 @@ 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/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class GameHistoryView extends StatefulWidget { @@ -40,9 +38,7 @@ class _GameHistoryViewState extends State { ], ), winner: Player(name: 'Skeleton Player 1'), - players: [ - Player(name: 'Skeleton Player 6') - ], + players: [Player(name: 'Skeleton Player 6')], ), ); @@ -69,49 +65,53 @@ class _GameHistoryViewState extends State { children: [ FutureBuilder>( future: _gameListFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'No Games Available', - ), - ); - } + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), + ); + } - final List games = (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .toList(); + final List games = + (isLoading ? skeletonData : (snapshot.data ?? []) + ..sort( + (a, b) => b.createdAt.compareTo(a.createdAt), + )) + .toList(); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == games.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, - ); - } - return GameHistoryTile(game: games[index]); // Placeholder - }, - ), - ); - }, + return AppSkeleton( + enabled: isLoading, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, + ); + } + return GameHistoryTile( + game: games[index], + ); // Placeholder + }, + ), + ); + }, ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -137,4 +137,4 @@ class _GameHistoryViewState extends State { ), ); } -} \ No newline at end of file +} -- 2.49.1 From e77896c1d4f39a49b59a33dc7d91de6980149357 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:25:31 +0100 Subject: [PATCH 391/563] removed settings icon leading to game result view --- .../main_menu/custom_navigation_bar.dart | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 4003a5e..7b08017 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.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/game_history_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -59,26 +56,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute( - builder: (_) => GameResultView( - game: Game( - name: 'Test Game', - players: [ - Player(name: 'Petrus'), - Player(name: 'Peter'), - Player(name: 'Petra'), - ], - group: Group( - name: 'Die Petris', - members: [ - Player(name: 'Petralia'), - Player(name: 'Petrenlia'), - Player(name: 'Petrumlia'), - ], - ), - ), - ), - ), //TODO Replace with Settingsview + MaterialPageRoute(builder: (_) => SettingsView()), ); setState(() { tabKeyCount++; -- 2.49.1 From 9ac6b6e04c6e68dd9728e8faccb90aaa25d41c53 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:25:52 +0100 Subject: [PATCH 392/563] added ontap feature & argument --- .../widgets/tiles/game_history_tile.dart | 193 +++++++++--------- 1 file changed, 91 insertions(+), 102 deletions(-) diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 3cdd1ad..83da859 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -6,142 +6,132 @@ import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { final Game game; + final VoidCallback onTap; - const GameHistoryTile({ - super.key, - required this.game, - }); + const GameHistoryTile({super.key, required this.game, required this.onTap}); @override State createState() => _GameHistoryTileState(); } class _GameHistoryTileState extends State { - @override Widget build(BuildContext context) { final group = widget.game.group; final winner = widget.game.winner; final allPlayers = _getAllPlayers(); - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.game.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Text( - _formatDate(widget.game.createdAt), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - - const SizedBox(height: 8), - - if (group != null) ...[ + return GestureDetector( + onTap: widget.onTap, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon( - Icons.group, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 6), Expanded( child: Text( - group.name, + widget.game.name, style: const TextStyle( - fontSize: 14, - color: Colors.grey, + fontSize: 18, + fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), - const SizedBox(height: 12), - ], - if (winner != null) ...[ - Container( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: BoxDecoration( - color: Colors.green.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.green.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Row( + const SizedBox(height: 8), + + if (group != null) ...[ + Row( children: [ - const Icon( - Icons.emoji_events, - size: 20, - color: Colors.amber, - ), - const SizedBox(width: 8), + const Icon(Icons.group, size: 16, color: Colors.grey), + const SizedBox(width: 6), Expanded( child: Text( - 'Winner: ${winner.name}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + group.name, + style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), ), ], ), - ), - const SizedBox(height: 12), - ], + const SizedBox(height: 12), + ], - if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', - style: TextStyle( - fontSize: 13, - color: Colors.grey, - fontWeight: FontWeight.w500, + if (winner != null) ...[ + Container( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - ), - const SizedBox(height: 6), - Wrap( - spacing: 6, - runSpacing: 6, - children: allPlayers.map((player) { - return TextIconTile( - text: player.name, - iconEnabled: false, - ); - }).toList(), - ), + const SizedBox(height: 12), + ], + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + return TextIconTile(text: player.name, iconEnabled: false); + }).toList(), + ), + ], ], - ], + ), ), ); } @@ -187,5 +177,4 @@ class _GameHistoryTileState extends State { return allPlayers; } - -} \ No newline at end of file +} -- 2.49.1 From f7f97fcdcbfabd5ec31875327370078d6504e08f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:26:47 +0100 Subject: [PATCH 393/563] made tapping on game tile redirect to GameResultView --- .../views/main_menu/game_history_view.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index ea5ddd7..713b720 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,6 +5,7 @@ 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/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; @@ -106,6 +107,15 @@ class _GameHistoryViewState extends State { ); } return GameHistoryTile( + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + GameResultView(game: games[index]), + ), + ); + }, game: games[index], ); // Placeholder }, -- 2.49.1 From 8f2c7493d081342bf26e01b07f3c399777258f8a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:35:04 +0100 Subject: [PATCH 394/563] re-set gameListFuture to reload the games after changing the winner --- lib/presentation/views/main_menu/game_history_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 713b720..1356ee4 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -115,9 +115,12 @@ class _GameHistoryViewState extends State { GameResultView(game: games[index]), ), ); + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); }, game: games[index], - ); // Placeholder + ); }, ), ); -- 2.49.1 From d8551b3a27d96886ceeb0ccedb9752d8c10ca5cb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:35:14 +0100 Subject: [PATCH 395/563] add db functionality --- .../views/main_menu/game_result_view.dart | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f86ded1..f74910f 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { final Game game; @@ -17,11 +19,16 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; + late final AppDatabase db; Player? _player; //TODO: Set last winner as selected @override void initState() { + db = Provider.of(context, listen: false); allPlayers = getAllPlayers(widget.game); + if (widget.game.winner != null) { + _player = allPlayers.firstWhere((p) => p.id == widget.game.winner!.id); + } super.initState(); } @@ -112,11 +119,29 @@ class _GameResultViewState extends State { text: 'Save', sizeRelativeToWidth: 0.95, onPressed: _player != null - ? () { - print( - 'Selected Winner: ${_player!.name}', - ); //TODO: Add winner to db - Navigator.pop(context); + ? () async { + print('Selected Winner: ${_player!.name}'); + bool success = await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _player!.id, + ); + if (!context.mounted) return; + if (success) { + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: const Center( + child: Text( + 'Error while setting winner, please try again', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + } + setState(() {}); } : null, ), -- 2.49.1 From dba448b9c1debc43222b2946416c6ca47f3a3f71 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:38:28 +0100 Subject: [PATCH 396/563] added const --- .../views/main_menu/custom_navigation_bar.dart | 2 +- lib/presentation/views/main_menu/game_history_view.dart | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 7b08017..71a072e 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -56,7 +56,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute(builder: (_) => SettingsView()), + MaterialPageRoute(builder: (_) => const SettingsView()), ); setState(() { tabKeyCount++; diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 1356ee4..aea958d 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -22,7 +22,6 @@ class GameHistoryView extends StatefulWidget { class _GameHistoryViewState extends State { late Future> _gameListFuture; late final AppDatabase db; - late bool isLoading = true; late final List skeletonData = List.filled( 4, @@ -51,9 +50,6 @@ class _GameHistoryViewState extends State { Future.wait([_gameListFuture]).then((result) async { await Future.delayed(const Duration(milliseconds: 250)); - setState(() { - isLoading = false; - }); }); } @@ -87,14 +83,14 @@ class _GameHistoryViewState extends State { ), ); } - + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; final List games = (isLoading ? skeletonData : (snapshot.data ?? []) ..sort( (a, b) => b.createdAt.compareTo(a.createdAt), )) .toList(); - return AppSkeleton( enabled: isLoading, child: ListView.builder( -- 2.49.1 From e3ac91bf48be1b29b568c74042f7b12ee9b2c2b0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:41:01 +0100 Subject: [PATCH 397/563] remove todo --- lib/presentation/views/main_menu/game_result_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f74910f..7468adb 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -20,7 +20,7 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; late final AppDatabase db; - Player? _player; //TODO: Set last winner as selected + Player? _player; @override void initState() { -- 2.49.1 From f5842f9c4ab3390bb605caae48acdd61020f1ded Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 19:44:36 +0100 Subject: [PATCH 398/563] remove print --- lib/presentation/views/main_menu/game_result_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 7468adb..a32ba95 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -120,7 +120,6 @@ class _GameResultViewState extends State { sizeRelativeToWidth: 0.95, onPressed: _player != null ? () async { - print('Selected Winner: ${_player!.name}'); bool success = await db.gameDao.setWinner( gameId: widget.game.id, winnerId: _player!.id, -- 2.49.1 From 7323f52153bfc85502336b79e06b727c1a5d1624 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Fri, 5 Dec 2025 20:33:27 +0100 Subject: [PATCH 399/563] add delay and change empty games message --- .../views/main_menu/game_history_view.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index aea958d..e4e135f 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -46,11 +46,10 @@ class _GameHistoryViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = db.gameDao.getAllGames(); - - Future.wait([_gameListFuture]).then((result) async { - await Future.delayed(const Duration(milliseconds: 250)); - }); + _gameListFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.gameDao.getAllGames(), + ); } @override @@ -78,8 +77,8 @@ class _GameHistoryViewState extends State { return const Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Error', - message: 'No Games Available', + title: 'Info', + message: 'No games created yet.', ), ); } -- 2.49.1 From 03035138acb3d9580713c67296cfbc9e0f3696df Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 10:00:59 +0100 Subject: [PATCH 400/563] refactor game result view variable naming and layout --- .../views/main_menu/game_result_view.dart | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index a32ba95..e07b788 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -5,7 +5,6 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { @@ -20,14 +19,16 @@ class GameResultView extends StatefulWidget { class _GameResultViewState extends State { late final List allPlayers; late final AppDatabase db; - Player? _player; + Player? _selectedPlayer; @override void initState() { db = Provider.of(context, listen: false); allPlayers = getAllPlayers(widget.game); if (widget.game.winner != null) { - _player = allPlayers.firstWhere((p) => p.id == widget.game.winner!.id); + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.game.winner!.id, + ); } super.initState(); } @@ -78,36 +79,28 @@ class _GameResultViewState extends State { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 2), - Visibility( - visible: allPlayers.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players in this game.', - ), - child: Expanded( - child: RadioGroup( - groupValue: _player, - onChanged: (Player? value) { - setState(() { - _player = value; - }); + const SizedBox(height: 10), + Expanded( + child: RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) { + setState(() { + _selectedPlayer = value; + }); + }, + child: ListView.builder( + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) { + setState(() { + _selectedPlayer = value; + }); + }, + ); }, - child: ListView.builder( - itemCount: allPlayers.length, - itemBuilder: (context, index) { - return CustomRadioListTile( - text: allPlayers[index].name, - value: allPlayers[index], - onContainerTap: (value) { - setState(() { - _player = value; - }); - }, - ); - }, - ), ), ), ), @@ -118,11 +111,11 @@ class _GameResultViewState extends State { CustomWidthButton( text: 'Save', sizeRelativeToWidth: 0.95, - onPressed: _player != null + onPressed: _selectedPlayer != null ? () async { bool success = await db.gameDao.setWinner( gameId: widget.game.id, - winnerId: _player!.id, + winnerId: _selectedPlayer!.id, ); if (!context.mounted) return; if (success) { -- 2.49.1 From 306a783d67f8fa2b82c90d4b68893bdabb30bbc7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 11:22:34 +0100 Subject: [PATCH 401/563] removed dots after TopCenteredMessage text --- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index e4e135f..3cecd2f 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -78,7 +78,7 @@ class _GameHistoryViewState extends State { child: TopCenteredMessage( icon: Icons.report, title: 'Info', - message: 'No games created yet.', + message: 'No games created yet', ), ); } diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 4601ef9..ce47f90 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -56,7 +56,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.report, title: 'Error', - message: 'Group data couldn\'t\nbe loaded.', + message: 'Group data couldn\'t\nbe loaded', ), ); } @@ -66,7 +66,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: 'No groups created yet.', + message: 'No groups created yet', ), ); } -- 2.49.1 From 697767f0de46ffb39853892bbd9091c38cd7d27e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 13:31:08 +0100 Subject: [PATCH 402/563] made winner deselectable and added auto save on select --- .../views/main_menu/game_result_view.dart | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index e07b788..80a0774 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -3,7 +3,6 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; @@ -94,10 +93,24 @@ class _GameResultViewState extends State { return CustomRadioListTile( text: allPlayers[index].name, value: allPlayers[index], - onContainerTap: (value) { + onContainerTap: (value) async { setState(() { - _selectedPlayer = value; + if (_selectedPlayer == value) { + _selectedPlayer = null; + } else { + _selectedPlayer = value; + } }); + if (_selectedPlayer == null) { + await db.gameDao.removeWinner( + gameId: widget.game.id, + ); + } else { + await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _selectedPlayer!.id, + ); + } }, ); }, @@ -108,36 +121,6 @@ class _GameResultViewState extends State { ), ), ), - CustomWidthButton( - text: 'Save', - sizeRelativeToWidth: 0.95, - onPressed: _selectedPlayer != null - ? () async { - bool success = await db.gameDao.setWinner( - gameId: widget.game.id, - winnerId: _selectedPlayer!.id, - ); - if (!context.mounted) return; - if (success) { - Navigator.pop(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: const Center( - child: Text( - 'Error while setting winner, please try again', - style: TextStyle(color: Colors.white), - ), - ), - ), - ); - } - setState(() {}); - } - : null, - ), - const SizedBox(height: 10), ], ), ), -- 2.49.1 From b1bb8b919f883fdc2ca54a4805f295314ce08d5a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 13:34:11 +0100 Subject: [PATCH 403/563] changed MaterialPageRoute to CupertinoPageRoute for ios animation --- lib/presentation/views/main_menu/game_history_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3cecd2f..31d1b56 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -105,7 +106,8 @@ class _GameHistoryViewState extends State { onTap: () async { await Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( + fullscreenDialog: true, builder: (context) => GameResultView(game: games[index]), ), -- 2.49.1 From 91a727396419933c6c9533793034a432cdb0f385 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:12:34 +0100 Subject: [PATCH 404/563] madio radio list tile toggleable --- lib/presentation/widgets/tiles/custom_radio_list_tile.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 5081bad..11e8b40 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -27,7 +27,11 @@ class CustomRadioListTile extends StatelessWidget { ), child: Row( children: [ - Radio(value: value, activeColor: CustomTheme.primaryColor), + Radio( + value: value, + activeColor: CustomTheme.primaryColor, + toggleable: true, + ), Expanded( child: Text( text, -- 2.49.1 From 1ed6290628104b1170939423093398f773e963ac Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:20:37 +0100 Subject: [PATCH 405/563] Refactor winner selection and persistence logic in GameResultView --- .../views/main_menu/game_result_view.dart | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 80a0774..8b093c1 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -82,10 +82,11 @@ class _GameResultViewState extends State { Expanded( child: RadioGroup( groupValue: _selectedPlayer, - onChanged: (Player? value) { + onChanged: (Player? value) async { setState(() { _selectedPlayer = value; }); + await _handleWinnerSaving(); }, child: ListView.builder( itemCount: allPlayers.length, @@ -95,22 +96,15 @@ class _GameResultViewState extends State { value: allPlayers[index], onContainerTap: (value) async { setState(() { + // Check if the already selected player is the same as the newly tapped player. if (_selectedPlayer == value) { + // If yes deselected the player by setting it to null. _selectedPlayer = null; - } else { - _selectedPlayer = value; - } + } else + // If no assign the newly tapped player to the selected player. + (_selectedPlayer = value); }); - if (_selectedPlayer == null) { - await db.gameDao.removeWinner( - gameId: widget.game.id, - ); - } else { - await db.gameDao.setWinner( - gameId: widget.game.id, - winnerId: _selectedPlayer!.id, - ); - } + await _handleWinnerSaving(); }, ); }, @@ -127,6 +121,17 @@ class _GameResultViewState extends State { ); } + Future _handleWinnerSaving() async { + if (_selectedPlayer == null) { + await db.gameDao.removeWinner(gameId: widget.game.id); + } else { + await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _selectedPlayer!.id, + ); + } + } + List getAllPlayers(Game game) { if (game.group == null && game.players != null) { return [...game.players!]; -- 2.49.1 From dbbe04d4ccf32a28f790f327546a5eb91b994932 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 14:21:17 +0100 Subject: [PATCH 406/563] add braces to if/else statement in `game_result_view.dart` --- lib/presentation/views/main_menu/game_result_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index 8b093c1..f13553b 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -100,9 +100,10 @@ class _GameResultViewState extends State { if (_selectedPlayer == value) { // If yes deselected the player by setting it to null. _selectedPlayer = null; - } else + } else { // If no assign the newly tapped player to the selected player. (_selectedPlayer = value); + } }); await _handleWinnerSaving(); }, -- 2.49.1 From 6ae7166d344a0fdde863d75103834b7b52c89fea Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 15:18:41 +0100 Subject: [PATCH 407/563] Update state when selecting players and require at least two players to create a group --- lib/presentation/views/main_menu/create_group_view.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index cbaee6d..f20fb4e 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -66,7 +66,9 @@ class _CreateGroupViewState extends State { Expanded( child: PlayerSelection( onChanged: (value) { - selectedPlayers = [...value]; + setState(() { + selectedPlayers = [...value]; + }); }, ), ), @@ -75,7 +77,8 @@ class _CreateGroupViewState extends State { sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: - (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) + (_groupNameController.text.isEmpty || + (selectedPlayers.length < 2)) ? null : () async { bool success = await db.groupDao.addGroup( -- 2.49.1 From 75b62d0854fb9b6dce79dee74743925ecfabbca9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 16:58:12 +0100 Subject: [PATCH 408/563] Replaced temp navigator --- lib/presentation/views/main_menu/game_history_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 31d1b56..303ea67 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,7 +5,7 @@ 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/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -133,7 +133,7 @@ class _GameHistoryViewState extends State { context, MaterialPageRoute( builder: (context) { - return const CreateGroupView(); + return const CreateGameView(); }, ), ); -- 2.49.1 From 10aad4712438188a0d7bb798df2db8b05bf49446 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:13:33 +0100 Subject: [PATCH 409/563] Updated choosing mechanism --- .../create_game/choose_game_view.dart | 66 +++++++++++-------- .../create_game/choose_group_view.dart | 21 ++++-- .../create_game/choose_ruleset_view.dart | 20 ++++-- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 8d8279e..5b2463b 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -35,42 +35,50 @@ class _ChooseGameViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of( + context, + ).pop(selectedGameIndex == -1 ? null : selectedGameIndex); + }, + ), title: const Text( 'Choose Game', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), - body: Column( - children: [ - CustomSearchBar( - controller: searchBarController, - hintText: 'Game Name', - ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(selectedGameIndex); - }); - }, - ); - }, + body: Container( + decoration: CustomTheme.standardBoxDecoration, + padding: const EdgeInsets.all(10), + child: Column( + children: [ + CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', ), - ), - ], + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + }, + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index c98ce6d..de512bf 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -33,6 +33,16 @@ class _ChooseGroupViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop( + selectedGroupIndex == -1 + ? null + : widget.groups[selectedGroupIndex], + ); + }, + ), title: const Text( 'Choose Group', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -46,12 +56,11 @@ class _ChooseGroupViewState extends State { return GestureDetector( onTap: () { setState(() { - selectedGroupIndex = index; - }); - - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(widget.groups[index]); + if (selectedGroupIndex == index) { + selectedGroupIndex = -1; + } else { + selectedGroupIndex = index; + } }); }, child: GroupTile( diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 2e45a7c..13a9647 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -36,6 +36,16 @@ class _ChooseRulesetViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop( + selectedRulesetIndex == -1 + ? null + : widget.rulesets[selectedRulesetIndex].$1, + ); + }, + ), title: const Text( 'Choose Ruleset', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -49,11 +59,11 @@ class _ChooseRulesetViewState extends State { return TitleDescriptionListTile( onPressed: () async { setState(() { - selectedRulesetIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(widget.rulesets[index].$1); + if (selectedRulesetIndex == index) { + selectedRulesetIndex = -1; + } else { + selectedRulesetIndex = index; + } }); }, title: widget.rulesets[index].$2, -- 2.49.1 From 3d12f0c160c09d6fa22d4c87470095be7fff7b34 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:23:20 +0100 Subject: [PATCH 410/563] Fixed width problem in choose game view --- .../create_game/choose_game_view.dart | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 5b2463b..992f02b 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -49,36 +49,35 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: Container( - decoration: CustomTheme.standardBoxDecoration, - padding: const EdgeInsets.all(10), - child: Column( - children: [ - CustomSearchBar( + body: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( controller: searchBarController, hintText: 'Game Name', ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - }, - ); - }, - ), + ), + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + }, + ); + }, ), - ], - ), + ), + ], ), ); } -- 2.49.1 From a1a995777bdb288abf679e2b6c77dc0c2404f5c7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:23:40 +0100 Subject: [PATCH 411/563] Adjusted margin --- .../views/main_menu/create_game/create_game_view.dart | 4 +--- .../widgets/tiles/title_description_list_tile.dart | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index eb5468c..f83d723 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -123,7 +123,7 @@ class _CreateGameViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _gameNameController, hintText: 'Game name', @@ -213,7 +213,6 @@ class _CreateGameViewState extends State { }, ), ), - CustomWidthButton( text: 'Create game', sizeRelativeToWidth: 0.95, @@ -232,7 +231,6 @@ class _CreateGameViewState extends State { } : null, ), - const SizedBox(height: 20), ], ), ), diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 7bc4221..2d517f2 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -24,7 +24,7 @@ class TitleDescriptionListTile extends StatelessWidget { return GestureDetector( onTap: onPressed, child: AnimatedContainer( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), decoration: isHighlighted ? CustomTheme.highlightedBoxDecoration -- 2.49.1 From 6ae0471fa214ab4ee1f900464facf3b1ce2dd6a8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:42:11 +0100 Subject: [PATCH 412/563] Implemented final navigation to GameResultView --- .../create_game/create_game_view.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index f83d723..bbb392b 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; @@ -8,6 +9,7 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; @@ -225,9 +227,20 @@ class _CreateGameViewState extends State { group: selectedGroup!, players: selectedPlayers, ); - // TODO: Replace with navigation to GameResultView() - print('Created game: $game'); - Navigator.pop(context); + final db = Provider.of( + context, + listen: false, + ); + await db.gameDao.addGame(game: game); + if (context.mounted) { + Navigator.pushReplacement( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => GameResultView(game: game), + ), + ); + } } : null, ), -- 2.49.1 From e37a98fdccca68cc20edac23f91c9e6de62df170 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 21:53:24 +0100 Subject: [PATCH 413/563] hart besoffenen fehler gefixt? --- lib/presentation/widgets/text_input/custom_search_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index e3fe976..35c11e1 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -30,7 +30,7 @@ class CustomSearchBar extends StatelessWidget { constraints: constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: hintText, - onChanged: trailingButtonEnabled ? onChanged : null, + onChanged: onChanged, hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), trailing: [ -- 2.49.1 From 7ecccb13c2f00df72627767327692ceccd6c368d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 13:04:30 +0100 Subject: [PATCH 414/563] Updated skeleton data --- .../views/main_menu/game_history_view.dart | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 31d1b56..a4b8cca 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -27,19 +27,13 @@ class _GameHistoryViewState extends State { late final List skeletonData = List.filled( 4, Game( - name: 'Skeleton Game', + name: 'Skeleton Gamename', group: Group( - name: 'Skeleton Group', - members: [ - Player(name: 'Player 1'), - Player(name: 'Player 2'), - Player(name: 'Player 3'), - Player(name: 'Long Name Player 4'), - Player(name: 'Player 5'), - ], + name: 'Groupname', + members: List.generate(5, (index) => Player(name: 'Player')), ), - winner: Player(name: 'Skeleton Player 1'), - players: [Player(name: 'Skeleton Player 6')], + winner: Player(name: 'Player'), + players: [Player(name: 'Player')], ), ); -- 2.49.1 From d2d0a82c9b2cf3a86f478ad5d838cd196c65c986 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 18:58:49 +0100 Subject: [PATCH 415/563] Implemented deselecting game --- .../main_menu/create_game/choose_game_view.dart | 10 ++++++---- .../main_menu/create_game/create_game_view.dart | 13 ++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 992f02b..53a4fcb 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -38,9 +38,7 @@ class _ChooseGameViewState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { - Navigator.of( - context, - ).pop(selectedGameIndex == -1 ? null : selectedGameIndex); + Navigator.of(context).pop(selectedGameIndex); }, ), title: const Text( @@ -70,7 +68,11 @@ class _ChooseGameViewState extends State { isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { - selectedGameIndex = index; + if (selectedGameIndex == index) { + selectedGameIndex = -1; + } else { + selectedGameIndex = index; + } }); }, ); diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index bbb392b..4bf05e7 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -148,11 +148,14 @@ class _CreateGameViewState extends State { ), ), ); - selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - setState(() {}); + if (selectedGameIndex != -1) { + setState(() { + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + }); + } }, ), ChooseTile( -- 2.49.1 From f60c11fc08a2d5b912cae2930c5aa2fd66afbc1f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:01:19 +0100 Subject: [PATCH 416/563] Added searchbar for choose_group_view --- .../create_game/choose_group_view.dart | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index de512bf..f805d6e 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { @@ -19,6 +20,8 @@ class ChooseGroupView extends StatefulWidget { class _ChooseGroupViewState extends State { late int selectedGroupIndex; + final TextEditingController controller = TextEditingController(); + final String hintText = 'Group Name'; @override void initState() { @@ -49,26 +52,36 @@ class _ChooseGroupViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.groups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupIndex == index) { - selectedGroupIndex = -1; - } else { - selectedGroupIndex = index; - } - }); - }, - child: GroupTile( - group: widget.groups[index], - isHighlighted: selectedGroupIndex == index, + body: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar(controller: controller, hintText: hintText), + ), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.groups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupIndex == index) { + selectedGroupIndex = -1; + } else { + selectedGroupIndex = index; + } + }); + }, + child: GroupTile( + group: widget.groups[index], + isHighlighted: selectedGroupIndex == index, + ), + ); + }, ), - ); - }, + ), + ], ), ); } -- 2.49.1 From b4ba4f8d74bcb842bda144f5b90da00b407f43db Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:12:43 +0100 Subject: [PATCH 417/563] Implemented search bar in choose_group_view --- .../create_game/choose_group_view.dart | 56 ++++++++++++++----- .../create_game/create_game_view.dart | 8 +-- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index f805d6e..e27adf3 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -6,12 +6,12 @@ import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { final List groups; - final int initialGroupIndex; + final String initialGroupId; const ChooseGroupView({ super.key, required this.groups, - required this.initialGroupIndex, + required this.initialGroupId, }); @override @@ -19,13 +19,15 @@ class ChooseGroupView extends StatefulWidget { } class _ChooseGroupViewState extends State { - late int selectedGroupIndex; + late String selectedGroupId; final TextEditingController controller = TextEditingController(); final String hintText = 'Group Name'; + late final List filteredGroups; @override void initState() { - selectedGroupIndex = widget.initialGroupIndex; + selectedGroupId = widget.initialGroupId; + filteredGroups = [...widget.groups]; super.initState(); } @@ -40,9 +42,11 @@ class _ChooseGroupViewState extends State { icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop( - selectedGroupIndex == -1 + selectedGroupId == '' ? null - : widget.groups[selectedGroupIndex], + : widget.groups.firstWhere( + (group) => group.id == selectedGroupId, + ), ); }, ), @@ -56,26 +60,34 @@ class _ChooseGroupViewState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar(controller: controller, hintText: hintText), + child: CustomSearchBar( + controller: controller, + hintText: hintText, + onChanged: (value) { + setState(() { + filterGroups(value); + }); + }, + ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.groups.length, + itemCount: filteredGroups.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { setState(() { - if (selectedGroupIndex == index) { - selectedGroupIndex = -1; + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; } else { - selectedGroupIndex = index; + selectedGroupId = ''; } }); }, child: GroupTile( - group: widget.groups[index], - isHighlighted: selectedGroupIndex == index, + group: filteredGroups[index], + isHighlighted: selectedGroupId == filteredGroups[index].id, ), ); }, @@ -85,4 +97,22 @@ class _ChooseGroupViewState extends State { ), ); } + + /// Filters the groups based on the search query. + /// TODO: Maybe implement also targetting player names? + void filterGroups(String query) { + setState(() { + if (query.isEmpty) { + filteredGroups.clear(); + filteredGroups.addAll(widget.groups); + } else { + filteredGroups.clear(); + filteredGroups.addAll( + widget.groups.where( + (group) => group.name.toLowerCase().contains(query.toLowerCase()), + ), + ); + } + }); + } } diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4bf05e7..fdae05c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -47,7 +47,7 @@ class _CreateGameViewState extends State { /// The index of the currently selected group in [groupsList] to mark it in /// the [ChooseGroupView] - int selectedGroupIndex = -1; + String selectedGroupId = ''; /// The currently selected ruleset Ruleset? selectedRuleset; @@ -189,13 +189,11 @@ class _CreateGameViewState extends State { MaterialPageRoute( builder: (context) => ChooseGroupView( groups: groupsList, - initialGroupIndex: selectedGroupIndex, + initialGroupId: selectedGroupId, ), ), ); - selectedGroupIndex = groupsList.indexWhere( - (g) => g.id == selectedGroup?.id, - ); + selectedGroupId = selectedGroup?.id ?? ''; setState(() {}); }, ), -- 2.49.1 From 6c0cb92e567d6ccb9a42326153c9d64fe658fe7e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:15:49 +0100 Subject: [PATCH 418/563] Removed unneccesary title in tupe --- .../main_menu/create_game/choose_ruleset_view.dart | 6 +++--- .../views/main_menu/create_game/create_game_view.dart | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 13a9647..537f749 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { - final List<(Ruleset, String, String)> rulesets; + final List<(Ruleset, String)> rulesets; final int initialRulesetIndex; const ChooseRulesetView({ @@ -66,8 +66,8 @@ class _ChooseRulesetViewState extends State { } }); }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, + title: translateRulesetToString(widget.rulesets[index].$1), + description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); }, diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index fdae05c..0a6a505 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -61,28 +61,24 @@ class _CreateGameViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their display names and descriptions - /// as tuples of (Ruleset, String, String) + /// List of available rulesets with their descriptions + /// as tuples of (Ruleset, String) /// TODO: Replace when rulesets are implemented - List<(Ruleset, String, String)> rulesets = [ + List<(Ruleset, String)> rulesets = [ ( Ruleset.singleWinner, - 'Single Winner', 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', ), ( Ruleset.singleLoser, - 'Single Loser', 'Exactly one loser is determined; last place receives the penalty or consequence.', ), ( Ruleset.mostPoints, - 'Most Points', 'Traditional ruleset: the player with the most points wins.', ), ( Ruleset.leastPoints, - 'Least Points', 'Inverse scoring: the player with the fewest points wins.', ), ]; -- 2.49.1 From c214a26c5401e25353b9dacaca497ea86f6e4800 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:17:20 +0100 Subject: [PATCH 419/563] Removed onChanged --- .../views/main_menu/create_game/create_game_view.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 0a6a505..2d227d1 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -125,9 +125,6 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', - onChanged: (value) { - setState(() {}); - }, ), ), ChooseTile( -- 2.49.1 From 45abc79f9515a71e0b63f998b08feb96160f65f3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:17:56 +0100 Subject: [PATCH 420/563] Removed unneccesary db declaration --- .../views/main_menu/create_game/create_game_view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 2d227d1..4e7c227 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -221,10 +221,6 @@ class _CreateGameViewState extends State { group: selectedGroup!, players: selectedPlayers, ); - final db = Provider.of( - context, - listen: false, - ); await db.gameDao.addGame(game: game); if (context.mounted) { Navigator.pushReplacement( -- 2.49.1 From a1ed17355a2a1581a784985044774ffd9d742d2e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:23:38 +0100 Subject: [PATCH 421/563] Minimum of 2 players required to create a game, unless group is selected --- .../views/main_menu/create_game/create_game_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4e7c227..59e3c0d 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -245,7 +245,7 @@ class _CreateGameViewState extends State { bool _enableCreateGameButton() { return _gameNameController.text.isNotEmpty && (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.isNotEmpty)) && + (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; } } -- 2.49.1 From 7bc75d60a72c4a25e05d791e742f400e42951586 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:31:11 +0100 Subject: [PATCH 422/563] Fixed text overflow --- .../tiles/title_description_list_tile.dart | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 2d517f2..7a138a0 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -38,17 +38,23 @@ class TitleDescriptionListTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - title, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + SizedBox( + width: 230, + child: Text( + title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), ), if (badgeText != null) ...[ const Spacer(), Container( + constraints: const BoxConstraints(maxWidth: 100), margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, @@ -60,6 +66,9 @@ class TitleDescriptionListTile extends StatelessWidget { ), child: Text( badgeText!, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, style: const TextStyle( color: Colors.white, fontSize: 12, -- 2.49.1 From f1f3fd7b6ed0f564a2a5aa07b17726fccb266b5f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:03:28 +0100 Subject: [PATCH 423/563] Fixed deselecting game bug --- .../views/main_menu/create_game/create_game_view.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 59e3c0d..7a7d904 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -141,14 +141,17 @@ class _CreateGameViewState extends State { ), ), ); - if (selectedGameIndex != -1) { - setState(() { + setState(() { + if (selectedGameIndex != -1) { + print('selectedGameIndex: $selectedGameIndex'); selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); - }); - } + } else { + selectedRuleset = null; + } + }); }, ), ChooseTile( -- 2.49.1 From 708157df54232352ac41384a8a3d2ffb8651e03d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:15:19 +0100 Subject: [PATCH 424/563] Implemented leaving players in the player selection and filtering those who are in the group --- .../create_game/create_game_view.dart | 3 ++- .../widgets/player_selection.dart | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 7a7d904..6643a52 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -196,7 +196,8 @@ class _CreateGameViewState extends State { Expanded( child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialPlayers: selectedGroup == null + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null ? playerList : playerList .where( diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 092a613..d48e241 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,12 +11,14 @@ import 'package:provider/provider.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; - final List initialPlayers; + final List availablePlayers; + final List? initialSelectedPlayers; const PlayerSelection({ super.key, required this.onChanged, - this.initialPlayers = const [], + this.availablePlayers = const [], + this.initialSelectedPlayers, }); @override @@ -51,10 +53,19 @@ class _PlayerSelectionState extends State { suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { - if (widget.initialPlayers.isNotEmpty) { - allPlayers = [...widget.initialPlayers]; - suggestedPlayers = [...widget.initialPlayers]; + // If a list of available players is provided, use that list. + if (widget.availablePlayers.isNotEmpty) { + allPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...widget.availablePlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where((p) => widget.availablePlayers.contains(p)) + .toList(); + } } else { + // Otherwise, use the loaded players from the database. loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = [...loadedPlayers]; suggestedPlayers = [...loadedPlayers]; -- 2.49.1 From 7cff48ebc07429c0805cb3a75bbf0c50824b4f93 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:23:43 +0100 Subject: [PATCH 425/563] Added empty message --- .../create_game/choose_group_view.dart | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index e27adf3..4e81a86 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class ChooseGroupView extends StatefulWidget { final List groups; @@ -71,26 +72,35 @@ class _ChooseGroupViewState extends State { ), ), Expanded( - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: filteredGroups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupId != filteredGroups[index].id) { - selectedGroupId = filteredGroups[index].id; - } else { - selectedGroupId = ''; - } - }); - }, - child: GroupTile( - group: filteredGroups[index], - isHighlighted: selectedGroupId == filteredGroups[index].id, - ), - ); - }, + child: Visibility( + visible: filteredGroups.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'No group', + message: 'There is no group matching your search.', + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: filteredGroups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; + } else { + selectedGroupId = ''; + } + }); + }, + child: GroupTile( + group: filteredGroups[index], + isHighlighted: + selectedGroupId == filteredGroups[index].id, + ), + ); + }, + ), ), ), ], -- 2.49.1 From 062c2681bf0216f942ac807fe085e694e55f9012 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 18:10:31 +0100 Subject: [PATCH 426/563] Corrected info message --- .../views/main_menu/create_game/choose_group_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index 4e81a86..c30d6ef 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -76,8 +76,8 @@ class _ChooseGroupViewState extends State { visible: filteredGroups.isNotEmpty, replacement: const TopCenteredMessage( icon: Icons.info, - title: 'No group', - message: 'There is no group matching your search.', + title: 'Info', + message: 'There is no group matching your search', ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), -- 2.49.1 From 701500c7e2db2d358da6dcdc25b5a84460851f1e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 18:26:05 +0100 Subject: [PATCH 427/563] Fixed state problem with games list --- .../create_game/create_game_view.dart | 8 ++++-- .../views/main_menu/game_history_view.dart | 27 ++++++++++--------- .../views/main_menu/game_result_view.dart | 4 ++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 6643a52..8b3271c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -17,7 +17,8 @@ import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; class CreateGameView extends StatefulWidget { - const CreateGameView({super.key}); + final VoidCallback? onWinnerChanged; + const CreateGameView({super.key, this.onWinnerChanged}); @override State createState() => _CreateGameViewState(); @@ -231,7 +232,10 @@ class _CreateGameViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView(game: game), + builder: (context) => GameResultView( + game: game, + onWinnerChanged: widget.onWinnerChanged, + ), ), ); } diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 5d52ac8..46cb1db 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -98,17 +98,16 @@ class _GameHistoryViewState extends State { } return GameHistoryTile( onTap: () async { - await Navigator.push( + Navigator.push( context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => - GameResultView(game: games[index]), + builder: (context) => GameResultView( + game: games[index], + onWinnerChanged: refreshGameList, + ), ), ); - setState(() { - _gameListFuture = db.gameDao.getAllGames(); - }); }, game: games[index], ); @@ -123,17 +122,13 @@ class _GameHistoryViewState extends State { text: 'Create Game', sizeRelativeToWidth: 0.90, onPressed: () async { - await Navigator.push( + Navigator.push( context, MaterialPageRoute( - builder: (context) { - return const CreateGameView(); - }, + builder: (context) => + CreateGameView(onWinnerChanged: refreshGameList), ), ); - setState(() { - _gameListFuture = db.gameDao.getAllGames(); - }); }, ), ), @@ -141,4 +136,10 @@ class _GameHistoryViewState extends State { ), ); } + + void refreshGameList() { + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); + } } diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f13553b..6e60410 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -9,8 +9,9 @@ import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { final Game game; - const GameResultView({super.key, required this.game}); + final VoidCallback? onWinnerChanged; + const GameResultView({super.key, required this.game, this.onWinnerChanged}); @override State createState() => _GameResultViewState(); } @@ -131,6 +132,7 @@ class _GameResultViewState extends State { winnerId: _selectedPlayer!.id, ); } + widget.onWinnerChanged?.call(); } List getAllPlayers(Game game) { -- 2.49.1 From c4094a547e40de2989d713544a01f7a6e52a0d00 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 20:52:14 +0100 Subject: [PATCH 428/563] Fixed button state problem --- .../views/main_menu/create_game/create_game_view.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 8b3271c..b9a8141 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -126,6 +126,11 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', + onChanged: (value) { + setState(() { + _gameNameController; + }); + }, ), ), ChooseTile( @@ -144,7 +149,6 @@ class _CreateGameViewState extends State { ); setState(() { if (selectedGameIndex != -1) { - print('selectedGameIndex: $selectedGameIndex'); selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, @@ -251,9 +255,12 @@ class _CreateGameViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _gameNameController.text.isNotEmpty && + final value = + _gameNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; + print('button: $value'); + return value; } } -- 2.49.1 From 27ff599a888fa8ae38e10e1ba7fa6969e4748fde Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 20:52:38 +0100 Subject: [PATCH 429/563] Reverted method --- .../views/main_menu/create_game/create_game_view.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index b9a8141..d7e89d6 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -255,12 +255,9 @@ class _CreateGameViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - final value = - _gameNameController.text.isNotEmpty && + return _gameNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; - print('button: $value'); - return value; } } -- 2.49.1 From 0d0806dfbb6b90fdb7b580cfd9d36f68e28694c3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 22:25:45 +0100 Subject: [PATCH 430/563] Implemented listener --- .../views/main_menu/create_game/create_game_view.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index d7e89d6..427ebf2 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -93,6 +93,10 @@ class _CreateGameViewState extends State { @override void initState() { super.initState(); + _gameNameController.addListener(() { + setState(() {}); + }); + db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); @@ -126,11 +130,6 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', - onChanged: (value) { - setState(() { - _gameNameController; - }); - }, ), ), ChooseTile( -- 2.49.1 From 5d2fed74ac345a3e360d68021af8aa6a3cebc152 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 23:59:34 +0100 Subject: [PATCH 431/563] Fixed sorting problem --- lib/presentation/widgets/player_selection.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d48e241..8007d39 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -55,8 +55,9 @@ class _PlayerSelectionState extends State { setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { + widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = [...widget.availablePlayers]; - suggestedPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...allPlayers]; if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. -- 2.49.1 From 23cdddfbd96d495c2d71afc405aa2b34d5dbe2d1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 12:38:39 +0100 Subject: [PATCH 432/563] Fixed problem with player selection --- .../create_game/create_game_view.dart | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 427ebf2..b1165b5 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -197,25 +197,30 @@ class _CreateGameViewState extends State { setState(() {}); }, ), - Expanded( - child: PlayerSelection( - key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), - onChanged: (value) { - setState(() { - selectedPlayers = value; - }); - }, - ), + FutureBuilder( + future: _allPlayersFuture, + builder: + (BuildContext context, AsyncSnapshot snapshot) => + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null + ? playerList + : playerList + .where( + (p) => !selectedGroup!.members.any( + (m) => m.id == p.id, + ), + ) + .toList(), + onChanged: (value) { + setState(() { + selectedPlayers = value; + }); + }, + ), + ), ), CustomWidthButton( text: 'Create game', -- 2.49.1 From 93ced81e7e5efdbac0cc2a7fca70cf817339b9f0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 13:55:31 +0100 Subject: [PATCH 433/563] Fixed filtering problem with object reference --- lib/presentation/widgets/player_selection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 8007d39..e2114b2 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -62,7 +62,11 @@ class _PlayerSelectionState extends State { if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. selectedPlayers = widget.initialSelectedPlayers! - .where((p) => widget.availablePlayers.contains(p)) + .where( + (p) => widget.availablePlayers.any( + (available) => available.id == p.id, + ), + ) .toList(); } } else { -- 2.49.1 From e1626225ac57e526b0ade59b792b5e562c564b45 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 13:56:03 +0100 Subject: [PATCH 434/563] Removed future builder --- .../create_game/create_game_view.dart | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index b1165b5..427ebf2 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -197,30 +197,25 @@ class _CreateGameViewState extends State { setState(() {}); }, ), - FutureBuilder( - future: _allPlayersFuture, - builder: - (BuildContext context, AsyncSnapshot snapshot) => - Expanded( - child: PlayerSelection( - key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), - onChanged: (value) { - setState(() { - selectedPlayers = value; - }); - }, - ), - ), + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null + ? playerList + : playerList + .where( + (p) => !selectedGroup!.members.any( + (m) => m.id == p.id, + ), + ) + .toList(), + onChanged: (value) { + setState(() { + selectedPlayers = value; + }); + }, + ), ), CustomWidthButton( text: 'Create game', -- 2.49.1 From 3b3d298ff55118b2bb7df3826d9535eb72c17455 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 14:04:15 +0100 Subject: [PATCH 435/563] Small refactoring --- .../create_game/create_game_view.dart | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 427ebf2..8b6f516 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -43,6 +43,12 @@ class _CreateGameViewState extends State { /// List of all players from the database List playerList = []; + /// List of players filtered based on the selected group + /// If a group is selected, this list contains all players from [playerList] + /// who are not members of the selected group. If no group is selected, + /// this list is identical to [playerList]. + List filteredPlayerList = []; + /// The currently selected group Group? selectedGroup; @@ -57,6 +63,8 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + /// The index of the currently selected game in [games] to mark it in + /// the [ChooseGameView] int selectedGameIndex = -1; /// The currently selected players @@ -105,6 +113,7 @@ class _CreateGameViewState extends State { Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; + filteredPlayerList = List.from(playerList); }); } @@ -194,6 +203,15 @@ class _CreateGameViewState extends State { ), ); selectedGroupId = selectedGroup?.id ?? ''; + if (selectedGroup != null) { + filteredPlayerList = playerList + .where( + (p) => !selectedGroup!.members.any((m) => m.id == p.id), + ) + .toList(); + } else { + filteredPlayerList = List.from(playerList); + } setState(() {}); }, ), @@ -201,15 +219,7 @@ class _CreateGameViewState extends State { child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), + availablePlayers: filteredPlayerList, onChanged: (value) { setState(() { selectedPlayers = value; @@ -226,7 +236,7 @@ class _CreateGameViewState extends State { Game game = Game( name: _gameNameController.text.trim(), createdAt: DateTime.now(), - group: selectedGroup!, + group: selectedGroup, players: selectedPlayers, ); await db.gameDao.addGame(game: game); -- 2.49.1 From d0059b44a8c62928e03c3691a01081b2c986cba3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 21:41:37 +0100 Subject: [PATCH 436/563] Moved statement --- .../views/main_menu/create_game/create_game_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 8b6f516..eef2de6 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -113,8 +113,9 @@ class _CreateGameViewState extends State { Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; - filteredPlayerList = List.from(playerList); }); + + filteredPlayerList = List.from(playerList); } @override -- 2.49.1 From 99cea1e70318a6cf2be25b0ea41a3a8a64f63da1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:07:32 +0100 Subject: [PATCH 437/563] Renamed every instance of "game" to "match" --- lib/core/enums.dart | 6 +- lib/data/dao/game_dao.dart | 320 ----- lib/data/dao/game_dao.g.dart | 8 - lib/data/dao/group_dao.dart | 3 +- lib/data/dao/group_dao.g.dart | 3 + lib/data/dao/group_game_dao.dart | 98 -- lib/data/dao/group_game_dao.g.dart | 10 - lib/data/dao/group_match_dao.dart | 98 ++ lib/data/dao/group_match_dao.g.dart | 10 + lib/data/dao/match_dao.dart | 322 +++++ lib/data/dao/match_dao.g.dart | 8 + lib/data/dao/player_game_dao.g.dart | 10 - lib/data/dao/player_group_dao.dart | 3 +- ...er_game_dao.dart => player_match_dao.dart} | 90 +- lib/data/dao/player_match_dao.g.dart | 11 + lib/data/db/database.dart | 24 +- lib/data/db/database.g.dart | 1064 +++++++++-------- lib/data/db/tables/group_game_table.dart | 13 - lib/data/db/tables/group_match_table.dart | 13 + .../{game_table.dart => match_table.dart} | 2 +- lib/data/db/tables/player_game_table.dart | 13 - lib/data/db/tables/player_match_table.dart | 13 + lib/data/dto/{game.dart => match.dart} | 12 +- .../main_menu/custom_navigation_bar.dart | 12 +- .../{ => group_view}/create_group_view.dart | 0 .../{ => group_view}/groups_view.dart | 4 +- .../views/main_menu/home_view.dart | 76 +- .../create_match}/choose_game_view.dart | 0 .../create_match}/choose_group_view.dart | 0 .../create_match}/choose_ruleset_view.dart | 0 .../create_match/create_match_view.dart} | 24 +- .../match_result_view.dart} | 22 +- .../match_view.dart} | 42 +- .../views/main_menu/statistics_view.dart | 78 +- .../widgets/tiles/game_history_tile.dart | 22 +- lib/presentation/widgets/tiles/game_tile.dart | 20 +- .../widgets/tiles/statistics_tile.dart | 6 +- lib/services/data_transfer_service.dart | 19 +- test/db_tests/game_test.dart | 312 ++--- test/db_tests/group_game_test.dart | 91 +- test/db_tests/player_game_test.dart | 102 +- 41 files changed, 1525 insertions(+), 1459 deletions(-) delete mode 100644 lib/data/dao/game_dao.dart delete mode 100644 lib/data/dao/game_dao.g.dart delete mode 100644 lib/data/dao/group_game_dao.dart delete mode 100644 lib/data/dao/group_game_dao.g.dart create mode 100644 lib/data/dao/group_match_dao.dart create mode 100644 lib/data/dao/group_match_dao.g.dart create mode 100644 lib/data/dao/match_dao.dart create mode 100644 lib/data/dao/match_dao.g.dart delete mode 100644 lib/data/dao/player_game_dao.g.dart rename lib/data/dao/{player_game_dao.dart => player_match_dao.dart} (53%) create mode 100644 lib/data/dao/player_match_dao.g.dart delete mode 100644 lib/data/db/tables/group_game_table.dart create mode 100644 lib/data/db/tables/group_match_table.dart rename lib/data/db/tables/{game_table.dart => match_table.dart} (88%) delete mode 100644 lib/data/db/tables/player_game_table.dart create mode 100644 lib/data/db/tables/player_match_table.dart rename lib/data/dto/{game.dart => match.dart} (81%) rename lib/presentation/views/main_menu/{ => group_view}/create_group_view.dart (100%) rename lib/presentation/views/main_menu/{ => group_view}/groups_view.dart (97%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_game_view.dart (100%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_group_view.dart (100%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_ruleset_view.dart (100%) rename lib/presentation/views/main_menu/{create_game/create_game_view.dart => match_view/create_match/create_match_view.dart} (91%) rename lib/presentation/views/main_menu/{game_result_view.dart => match_view/match_result_view.dart} (90%) rename lib/presentation/views/main_menu/{game_history_view.dart => match_view/match_view.dart} (79%) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 68752fb..737882e 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -23,9 +23,9 @@ enum ImportResult { /// - [ExportResult.unknownException]: An exception occurred during export. enum ExportResult { success, canceled, unknownException } -/// Different rulesets available for games -/// - [Ruleset.singleWinner]: The game is won by a single player -/// - [Ruleset.singleLoser]: The game is lost by a single player +/// Different rulesets available for matches +/// - [Ruleset.singleWinner]: The match is won by a single player +/// - [Ruleset.singleLoser]: The match is lost by a single player /// - [Ruleset.mostPoints]: The player with the most points wins. /// - [Ruleset.leastPoints]: The player with the fewest points wins. enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart deleted file mode 100644 index c941499..0000000 --- a/lib/data/dao/game_dao.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -part 'game_dao.g.dart'; - -@DriftAccessor(tables: [GameTable]) -class GameDao extends DatabaseAccessor with _$GameDaoMixin { - GameDao(super.db); - - /// Retrieves all games from the database. - Future> getAllGames() async { - final query = select(gameTable); - final result = await query.get(); - - return Future.wait( - result.map((row) async { - final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); - final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); - final winner = row.winnerId != null - ? await db.playerDao.getPlayerById(playerId: row.winnerId!) - : null; - return Game( - id: row.id, - name: row.name, - group: group, - players: players, - createdAt: row.createdAt, - winner: winner, - ); - }), - ); - } - - /// Retrieves a [Game] by its [gameId]. - Future getGameById({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingle(); - - List? players; - if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) { - players = await db.playerGameDao.getPlayersOfGame(gameId: gameId); - } - Group? group; - if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { - group = await db.groupGameDao.getGroupOfGame(gameId: gameId); - } - Player? winner; - if (result.winnerId != null) { - winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); - } - - return Game( - id: result.id, - name: result.name, - players: players, - group: group, - winner: winner, - createdAt: result.createdAt, - ); - } - - /// Adds a new [Game] to the database. - /// Also adds associated players and group if they exist. - Future addGame({required Game game}) async { - await db.transaction(() async { - await into(gameTable).insert( - GameTableCompanion.insert( - id: game.id, - name: game.name, - winnerId: Value(game.winner?.id), - createdAt: game.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - - if (game.players != null) { - await db.playerDao.addPlayersAsList(players: game.players!); - for (final p in game.players ?? []) { - await db.playerGameDao.addPlayerToGame( - gameId: game.id, - playerId: p.id, - ); - } - } - - if (game.group != null) { - await db.groupDao.addGroup(group: game.group!); - await db.groupGameDao.addGroupToGame( - gameId: game.id, - groupId: game.group!.id, - ); - } - }); - } - - /// Adds multiple [Game]s to the database in a batch operation. - /// Also adds associated players and groups if they exist. - /// If the [games] list is empty, the method returns immediately. - Future addGamesAsList({required List games}) async { - if (games.isEmpty) return; - await db.transaction(() async { - // Add all games in batch - await db.batch( - (b) => b.insertAll( - gameTable, - games - .map( - (game) => GameTableCompanion.insert( - id: game.id, - name: game.name, - createdAt: game.createdAt, - winnerId: Value(game.winner?.id), - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - - // Add all groups of the games in batch - await db.batch( - (b) => b.insertAll( - db.groupTable, - games - .where((game) => game.group != null) - .map( - (game) => GroupTableCompanion.insert( - id: game.group!.id, - name: game.group!.name, - createdAt: game.group!.createdAt, - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - - // Add all players of the games in batch (unique) - final uniquePlayers = {}; - for (final game in games) { - if (game.players != null) { - for (final p in game.players!) { - uniquePlayers[p.id] = p; - } - } - // Also include members of groups - if (game.group != null) { - for (final m in game.group!.members) { - uniquePlayers[m.id] = m; - } - } - } - - if (uniquePlayers.isNotEmpty) { - await db.batch( - (b) => b.insertAll( - db.playerTable, - uniquePlayers.values - .map( - (p) => PlayerTableCompanion.insert( - id: p.id, - name: p.name, - createdAt: p.createdAt, - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } - - // Add all player-game associations in batch - await db.batch((b) { - for (final game in games) { - if (game.players != null) { - for (final p in game.players ?? []) { - b.insert( - db.playerGameTable, - PlayerGameTableCompanion.insert( - gameId: game.id, - playerId: p.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); - - // Add all player-group associations in batch - await db.batch((b) { - for (final game in games) { - if (game.group != null) { - for (final m in game.group!.members) { - b.insert( - db.playerGroupTable, - PlayerGroupTableCompanion.insert( - playerId: m.id, - groupId: game.group!.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); - - // Add all group-game associations in batch - await db.batch((b) { - for (final game in games) { - if (game.group != null) { - b.insert( - db.groupGameTable, - GroupGameTableCompanion.insert( - gameId: game.id, - groupId: game.group!.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - }); - }); - } - - /// Deletes the game with the given [gameId] from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteGame({required String gameId}) async { - final query = delete(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Retrieves the number of games in the database. - Future getGameCount() async { - final count = - await (selectOnly(gameTable)..addColumns([gameTable.id.count()])) - .map((row) => row.read(gameTable.id.count())) - .getSingle(); - return count ?? 0; - } - - /// Checks if a game with the given [gameId] exists in the database. - /// Returns `true` if the game exists, otherwise `false`. - Future gameExists({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Deletes all games from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteAllGames() async { - final query = delete(gameTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Sets the winner of the game with the given [gameId] to the player with - /// the given [winnerId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future setWinner({ - required String gameId, - required String winnerId, - }) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - GameTableCompanion(winnerId: Value(winnerId)), - ); - return rowsAffected > 0; - } - - /// Retrieves the winner of the game with the given [gameId]. - /// Returns the [Player] who won the game, or `null` if no winner is set. - Future getWinner({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - if (result == null || result.winnerId == null) { - return null; - } - final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); - return winner; - } - - /// Removes the winner of the game with the given [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removeWinner({required String gameId}) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - const GameTableCompanion(winnerId: Value(null)), - ); - return rowsAffected > 0; - } - - /// Checks if the game with the given [gameId] has a winner set. - /// Returns `true` if a winner is set, otherwise `false`. - Future hasWinner({required String gameId}) async { - final query = select(gameTable) - ..where((g) => g.id.equals(gameId) & g.winnerId.isNotNull()); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Changes the title of the game with the given [gameId] to [newName]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateGameName({ - required String gameId, - required String newName, - }) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - GameTableCompanion(name: Value(newName)), - ); - return rowsAffected > 0; - } -} diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart deleted file mode 100644 index b5a29fe..0000000 --- a/lib/data/dao/game_dao.g.dart +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$GameDaoMixin on DatabaseAccessor { - $GameTableTable get gameTable => attachedDatabase.gameTable; -} diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 643bc88..9802203 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -1,12 +1,13 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_group_table.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; part 'group_dao.g.dart'; -@DriftAccessor(tables: [GroupTable]) +@DriftAccessor(tables: [GroupTable, PlayerGroupTable]) class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { GroupDao(super.db); diff --git a/lib/data/dao/group_dao.g.dart b/lib/data/dao/group_dao.g.dart index 4a09208..b9534b4 100644 --- a/lib/data/dao/group_dao.g.dart +++ b/lib/data/dao/group_dao.g.dart @@ -5,4 +5,7 @@ part of 'group_dao.dart'; // ignore_for_file: type=lint mixin _$GroupDaoMixin on DatabaseAccessor { $GroupTableTable get groupTable => attachedDatabase.groupTable; + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $PlayerGroupTableTable get playerGroupTable => + attachedDatabase.playerGroupTable; } diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart deleted file mode 100644 index da95607..0000000 --- a/lib/data/dao/group_game_dao.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/group_game_table.dart'; -import 'package:game_tracker/data/dto/group.dart'; - -part 'group_game_dao.g.dart'; - -@DriftAccessor(tables: [GroupGameTable]) -class GroupGameDao extends DatabaseAccessor - with _$GroupGameDaoMixin { - GroupGameDao(super.db); - - /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. - Future addGroupToGame({ - required String gameId, - required String groupId, - }) async { - if (await gameHasGroup(gameId: gameId)) { - throw Exception('Game already has a group'); - } - await into(groupGameTable).insert( - GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), - mode: InsertMode.insertOrReplace, - ); - } - - /// Retrieves the [Group] associated with the given [gameId]. - /// Returns `null` if no group is found. - Future getGroupOfGame({required String gameId}) async { - final result = await (select( - groupGameTable, - )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); - - if (result == null) { - return null; - } - - final group = await db.groupDao.getGroupById(groupId: result.groupId); - return group; - } - - /// Checks if there is a group associated with the given [gameId]. - /// Returns `true` if there is a group, otherwise `false`. - Future gameHasGroup({required String gameId}) async { - final count = - await (selectOnly(groupGameTable) - ..where(groupGameTable.gameId.equals(gameId)) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; - } - - /// Checks if a specific group is associated with a specific game. - /// Returns `true` if the group is in the game, otherwise `false`. - Future isGroupInGame({ - required String gameId, - required String groupId, - }) async { - final count = - await (selectOnly(groupGameTable) - ..where( - groupGameTable.gameId.equals(gameId) & - groupGameTable.groupId.equals(groupId), - ) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; - } - - /// Removes the association of a group from a game based on [groupId] and - /// [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removeGroupFromGame({ - required String gameId, - required String groupId, - }) async { - final query = delete(groupGameTable) - ..where((g) => g.gameId.equals(gameId) & g.groupId.equals(groupId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Updates the group associated with a game to [newGroupId] based on - /// [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateGroupOfGame({ - required String gameId, - required String newGroupId, - }) async { - final updatedRows = - await (update(groupGameTable)..where((g) => g.gameId.equals(gameId))) - .write(GroupGameTableCompanion(groupId: Value(newGroupId))); - return updatedRows > 0; - } -} diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart deleted file mode 100644 index 735a35f..0000000 --- a/lib/data/dao/group_game_dao.g.dart +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'group_game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$GroupGameDaoMixin on DatabaseAccessor { - $GroupTableTable get groupTable => attachedDatabase.groupTable; - $GameTableTable get gameTable => attachedDatabase.gameTable; - $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; -} diff --git a/lib/data/dao/group_match_dao.dart b/lib/data/dao/group_match_dao.dart new file mode 100644 index 0000000..3c16c83 --- /dev/null +++ b/lib/data/dao/group_match_dao.dart @@ -0,0 +1,98 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/group_match_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; + +part 'group_match_dao.g.dart'; + +@DriftAccessor(tables: [GroupMatchTable]) +class GroupMatchDao extends DatabaseAccessor + with _$GroupMatchDaoMixin { + GroupMatchDao(super.db); + + /// Associates a group with a match by inserting a record into the + /// [GroupMatchTable]. + Future addGroupToMatch({ + required String matchId, + required String groupId, + }) async { + if (await matchHasGroup(matchId: matchId)) { + throw Exception('Match already has a group'); + } + await into(groupMatchTable).insert( + GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId), + mode: InsertMode.insertOrReplace, + ); + } + + /// Retrieves the [Group] associated with the given [matchId]. + /// Returns `null` if no group is found. + Future getGroupOfMatch({required String matchId}) async { + final result = await (select( + groupMatchTable, + )..where((g) => g.matchId.equals(matchId))).getSingleOrNull(); + + if (result == null) { + return null; + } + + final group = await db.groupDao.getGroupById(groupId: result.groupId); + return group; + } + + /// Checks if there is a group associated with the given [matchId]. + /// Returns `true` if there is a group, otherwise `false`. + Future matchHasGroup({required String matchId}) async { + final count = + await (selectOnly(groupMatchTable) + ..where(groupMatchTable.matchId.equals(matchId)) + ..addColumns([groupMatchTable.groupId.count()])) + .map((row) => row.read(groupMatchTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific group is associated with a specific match. + /// Returns `true` if the group is in the match, otherwise `false`. + Future isGroupInMatch({ + required String matchId, + required String groupId, + }) async { + final count = + await (selectOnly(groupMatchTable) + ..where( + groupMatchTable.matchId.equals(matchId) & + groupMatchTable.groupId.equals(groupId), + ) + ..addColumns([groupMatchTable.groupId.count()])) + .map((row) => row.read(groupMatchTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Removes the association of a group from a match based on [groupId] and + /// [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeGroupFromMatch({ + required String matchId, + required String groupId, + }) async { + final query = delete(groupMatchTable) + ..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Updates the group associated with a match to [newGroupId] based on + /// [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateGroupOfMatch({ + required String matchId, + required String newGroupId, + }) async { + final updatedRows = + await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId))) + .write(GroupMatchTableCompanion(groupId: Value(newGroupId))); + return updatedRows > 0; + } +} diff --git a/lib/data/dao/group_match_dao.g.dart b/lib/data/dao/group_match_dao.g.dart new file mode 100644 index 0000000..5cc0b82 --- /dev/null +++ b/lib/data/dao/group_match_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'group_match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$GroupMatchDaoMixin on DatabaseAccessor { + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $MatchTableTable get matchTable => attachedDatabase.matchTable; + $GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable; +} diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart new file mode 100644 index 0000000..32bd323 --- /dev/null +++ b/lib/data/dao/match_dao.dart @@ -0,0 +1,322 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +part 'match_dao.g.dart'; + +@DriftAccessor(tables: [MatchTable]) +class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { + MatchDao(super.db); + + /// Retrieves all matches from the database. + Future> getAllMatches() async { + final query = select(matchTable); + final result = await query.get(); + + return Future.wait( + result.map((row) async { + final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id); + final players = await db.playerMatchDao.getPlayersOfMatch( + matchId: row.id, + ); + final winner = row.winnerId != null + ? await db.playerDao.getPlayerById(playerId: row.winnerId!) + : null; + return Match( + id: row.id, + name: row.name, + group: group, + players: players, + createdAt: row.createdAt, + winner: winner, + ); + }), + ); + } + + /// Retrieves a [Match] by its [matchId]. + Future getMatchById({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingle(); + + List? players; + if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) { + players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId); + } + Group? group; + if (await db.groupMatchDao.matchHasGroup(matchId: matchId)) { + group = await db.groupMatchDao.getGroupOfMatch(matchId: matchId); + } + Player? winner; + if (result.winnerId != null) { + winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + } + + return Match( + id: result.id, + name: result.name, + players: players, + group: group, + winner: winner, + createdAt: result.createdAt, + ); + } + + /// Adds a new [Match] to the database. + /// Also adds associated players and group if they exist. + Future addMatch({required Match match}) async { + await db.transaction(() async { + await into(matchTable).insert( + MatchTableCompanion.insert( + id: match.id, + name: match.name, + winnerId: Value(match.winner?.id), + createdAt: match.createdAt, + ), + mode: InsertMode.insertOrReplace, + ); + + if (match.players != null) { + await db.playerDao.addPlayersAsList(players: match.players!); + for (final p in match.players ?? []) { + await db.playerMatchDao.addPlayerToMatch( + matchId: match.id, + playerId: p.id, + ); + } + } + + if (match.group != null) { + await db.groupDao.addGroup(group: match.group!); + await db.groupMatchDao.addGroupToMatch( + matchId: match.id, + groupId: match.group!.id, + ); + } + }); + } + + /// Adds multiple [Match]s to the database in a batch operation. + /// Also adds associated players and groups if they exist. + /// If the [matches] list is empty, the method returns immediately. + Future addMatchAsList({required List matches}) async { + if (matches.isEmpty) return; + await db.transaction(() async { + // Add all matches in batch + await db.batch( + (b) => b.insertAll( + matchTable, + matches + .map( + (match) => MatchTableCompanion.insert( + id: match.id, + name: match.name, + createdAt: match.createdAt, + winnerId: Value(match.winner?.id), + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all groups of the matches in batch + await db.batch( + (b) => b.insertAll( + db.groupTable, + matches + .where((match) => match.group != null) + .map( + (matches) => GroupTableCompanion.insert( + id: matches.group!.id, + name: matches.group!.name, + createdAt: matches.group!.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all players of the matches in batch (unique) + final uniquePlayers = {}; + for (final match in matches) { + if (match.players != null) { + for (final p in match.players!) { + uniquePlayers[p.id] = p; + } + } + // Also include members of groups + if (match.group != null) { + for (final m in match.group!.members) { + uniquePlayers[m.id] = m; + } + } + } + + if (uniquePlayers.isNotEmpty) { + await db.batch( + (b) => b.insertAll( + db.playerTable, + uniquePlayers.values + .map( + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + + // Add all player-match associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.players != null) { + for (final p in match.players ?? []) { + b.insert( + db.playerMatchTable, + PlayerMatchTableCompanion.insert( + matchId: match.id, + playerId: p.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all player-group associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.group != null) { + for (final m in match.group!.members) { + b.insert( + db.playerGroupTable, + PlayerGroupTableCompanion.insert( + playerId: m.id, + groupId: match.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all group-match associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.group != null) { + b.insert( + db.groupMatchTable, + GroupMatchTableCompanion.insert( + matchId: match.id, + groupId: match.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + }); + }); + } + + /// Deletes the match with the given [matchId] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteMatch({required String matchId}) async { + final query = delete(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Retrieves the number of matches in the database. + Future getMatchCount() async { + final count = + await (selectOnly(matchTable)..addColumns([matchTable.id.count()])) + .map((row) => row.read(matchTable.id.count())) + .getSingle(); + return count ?? 0; + } + + /// Checks if a match with the given [matchId] exists in the database. + /// Returns `true` if the match exists, otherwise `false`. + Future matchExists({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Deletes all matches from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllMatches() async { + final query = delete(matchTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Sets the winner of the match with the given [matchId] to the player with + /// the given [winnerId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future setWinner({ + required String matchId, + required String winnerId, + }) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + MatchTableCompanion(winnerId: Value(winnerId)), + ); + return rowsAffected > 0; + } + + /// Retrieves the winner of the match with the given [matchId]. + /// Returns the [Player] who won the match, or `null` if no winner is set. + Future getWinner({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingleOrNull(); + if (result == null || result.winnerId == null) { + return null; + } + final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + return winner; + } + + /// Removes the winner of the match with the given [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeWinner({required String matchId}) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + const MatchTableCompanion(winnerId: Value(null)), + ); + return rowsAffected > 0; + } + + /// Checks if the match with the given [matchId] has a winner set. + /// Returns `true` if a winner is set, otherwise `false`. + Future hasWinner({required String matchId}) async { + final query = select(matchTable) + ..where((g) => g.id.equals(matchId) & g.winnerId.isNotNull()); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Changes the title of the match with the given [matchId] to [newName]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateMatchName({ + required String matchId, + required String newName, + }) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + MatchTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; + } +} diff --git a/lib/data/dao/match_dao.g.dart b/lib/data/dao/match_dao.g.dart new file mode 100644 index 0000000..a9f6f4c --- /dev/null +++ b/lib/data/dao/match_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$MatchDaoMixin on DatabaseAccessor { + $MatchTableTable get matchTable => attachedDatabase.matchTable; +} diff --git a/lib/data/dao/player_game_dao.g.dart b/lib/data/dao/player_game_dao.g.dart deleted file mode 100644 index 4d0a192..0000000 --- a/lib/data/dao/player_game_dao.g.dart +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'player_game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$PlayerGameDaoMixin on DatabaseAccessor { - $PlayerTableTable get playerTable => attachedDatabase.playerTable; - $GameTableTable get gameTable => attachedDatabase.gameTable; - $PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable; -} diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 8cf96c2..db45735 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -1,11 +1,12 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:game_tracker/data/dto/player.dart'; part 'player_group_dao.g.dart'; -@DriftAccessor(tables: [PlayerGroupTable]) +@DriftAccessor(tables: [PlayerGroupTable, PlayerTable]) class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_match_dao.dart similarity index 53% rename from lib/data/dao/player_game_dao.dart rename to lib/data/dao/player_match_dao.dart index b7f253f..f42b8bb 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -1,33 +1,33 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/player_game_table.dart'; +import 'package:game_tracker/data/db/tables/player_match_table.dart'; import 'package:game_tracker/data/dto/player.dart'; -part 'player_game_dao.g.dart'; +part 'player_match_dao.g.dart'; -@DriftAccessor(tables: [PlayerGameTable]) -class PlayerGameDao extends DatabaseAccessor - with _$PlayerGameDaoMixin { - PlayerGameDao(super.db); +@DriftAccessor(tables: [PlayerMatchTable]) +class PlayerMatchDao extends DatabaseAccessor + with _$PlayerMatchDaoMixin { + PlayerMatchDao(super.db); - /// Associates a player with a game by inserting a record into the - /// [PlayerGameTable]. - Future addPlayerToGame({ - required String gameId, + /// Associates a player with a match by inserting a record into the + /// [PlayerMatchTable]. + Future addPlayerToMatch({ + required String matchId, required String playerId, }) async { - await into(playerGameTable).insert( - PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + await into(playerMatchTable).insert( + PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId), mode: InsertMode.insertOrReplace, ); } - /// Retrieves a list of [Player]s associated with the given [gameId]. + /// Retrieves a list of [Player]s associated with the given [matchId]. /// Returns null if no players are found. - Future?> getPlayersOfGame({required String gameId}) async { + Future?> getPlayersOfMatch({required String matchId}) async { final result = await (select( - playerGameTable, - )..where((p) => p.gameId.equals(gameId))).get(); + playerMatchTable, + )..where((p) => p.matchId.equals(matchId))).get(); if (result.isEmpty) return null; @@ -38,43 +38,43 @@ class PlayerGameDao extends DatabaseAccessor return players; } - /// Checks if there are any players associated with the given [gameId]. + /// Checks if there are any players associated with the given [matchId]. /// Returns `true` if there are players, otherwise `false`. - Future gameHasPlayers({required String gameId}) async { + Future matchHasPlayers({required String matchId}) async { final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } - /// Checks if a specific player is associated with a specific game. - /// Returns `true` if the player is in the game, otherwise `false`. - Future isPlayerInGame({ - required String gameId, + /// Checks if a specific player is associated with a specific match. + /// Returns `true` if the player is in the match, otherwise `false`. + Future isPlayerInMatch({ + required String matchId, required String playerId, }) async { final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..where(playerGameTable.playerId.equals(playerId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..where(playerMatchTable.playerId.equals(playerId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } /// Removes the association of a player with a game by deleting the record - /// from the [PlayerGameTable]. + /// from the [PlayerMatchTable]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromGame({ - required String gameId, + Future removePlayerFromMatch({ + required String matchId, required String playerId, }) async { - final query = delete(playerGameTable) - ..where((pg) => pg.gameId.equals(gameId)) + final query = delete(playerMatchTable) + ..where((pg) => pg.matchId.equals(matchId)) ..where((pg) => pg.playerId.equals(playerId)); final rowsAffected = await query.go(); return rowsAffected > 0; @@ -83,11 +83,11 @@ class PlayerGameDao extends DatabaseAccessor /// Updates the players associated with a game based on the provided /// [newPlayer] list. It adds new players and removes players that are no /// longer associated with the game. - Future updatePlayersFromGame({ - required String gameId, + Future updatePlayersFromMatch({ + required String matchId, required List newPlayer, }) async { - final currentPlayers = await getPlayersOfGame(gameId: gameId); + final currentPlayers = await getPlayersOfMatch(matchId: matchId); // Create sets of player IDs for easy comparison final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {}; final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); @@ -99,9 +99,9 @@ class PlayerGameDao extends DatabaseAccessor db.transaction(() async { // Remove old players if (playersToRemove.isNotEmpty) { - await (delete(playerGameTable)..where( + await (delete(playerMatchTable)..where( (pg) => - pg.gameId.equals(gameId) & + pg.matchId.equals(matchId) & pg.playerId.isIn(playersToRemove.toList()), )) .go(); @@ -111,14 +111,16 @@ class PlayerGameDao extends DatabaseAccessor if (playersToAdd.isNotEmpty) { final inserts = playersToAdd .map( - (id) => - PlayerGameTableCompanion.insert(playerId: id, gameId: gameId), + (id) => PlayerMatchTableCompanion.insert( + playerId: id, + matchId: matchId, + ), ) .toList(); await Future.wait( inserts.map( (c) => into( - playerGameTable, + playerMatchTable, ).insert(c, mode: InsertMode.insertOrReplace), ), ); diff --git a/lib/data/dao/player_match_dao.g.dart b/lib/data/dao/player_match_dao.g.dart new file mode 100644 index 0000000..bcc8ef7 --- /dev/null +++ b/lib/data/dao/player_match_dao.g.dart @@ -0,0 +1,11 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PlayerMatchDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $MatchTableTable get matchTable => attachedDatabase.matchTable; + $PlayerMatchTableTable get playerMatchTable => + attachedDatabase.playerMatchTable; +} diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 704e1f0..e6c322f 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -1,16 +1,16 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; -import 'package:game_tracker/data/dao/game_dao.dart'; import 'package:game_tracker/data/dao/group_dao.dart'; -import 'package:game_tracker/data/dao/group_game_dao.dart'; +import 'package:game_tracker/data/dao/group_match_dao.dart'; +import 'package:game_tracker/data/dao/match_dao.dart'; import 'package:game_tracker/data/dao/player_dao.dart'; -import 'package:game_tracker/data/dao/player_game_dao.dart'; import 'package:game_tracker/data/dao/player_group_dao.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/group_game_table.dart'; +import 'package:game_tracker/data/dao/player_match_dao.dart'; +import 'package:game_tracker/data/db/tables/group_match_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; -import 'package:game_tracker/data/db/tables/player_game_table.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/db/tables/player_match_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:path_provider/path_provider.dart'; @@ -20,18 +20,18 @@ part 'database.g.dart'; tables: [ PlayerTable, GroupTable, - GameTable, + MatchTable, PlayerGroupTable, - PlayerGameTable, - GroupGameTable, + PlayerMatchTable, + GroupMatchTable, ], daos: [ PlayerDao, GroupDao, - GameDao, + MatchDao, PlayerGroupDao, - PlayerGameDao, - GroupGameDao, + PlayerMatchDao, + GroupMatchDao, ], ) class AppDatabase extends _$AppDatabase { diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index f211d0c..6bc493c 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -521,12 +521,12 @@ class GroupTableCompanion extends UpdateCompanion { } } -class $GameTableTable extends GameTable - with TableInfo<$GameTableTable, GameTableData> { +class $MatchTableTable extends MatchTable + with TableInfo<$MatchTableTable, MatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $GameTableTable(this.attachedDatabase, [this._alias]); + $MatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( @@ -573,10 +573,10 @@ class $GameTableTable extends GameTable String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'game_table'; + static const String $name = 'match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -614,9 +614,9 @@ class $GameTableTable extends GameTable @override Set get $primaryKey => {id}; @override - GameTableData map(Map data, {String? tablePrefix}) { + MatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GameTableData( + return MatchTableData( id: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}id'], @@ -637,17 +637,17 @@ class $GameTableTable extends GameTable } @override - $GameTableTable createAlias(String alias) { - return $GameTableTable(attachedDatabase, alias); + $MatchTableTable createAlias(String alias) { + return $MatchTableTable(attachedDatabase, alias); } } -class GameTableData extends DataClass implements Insertable { +class MatchTableData extends DataClass implements Insertable { final String id; final String name; final String? winnerId; final DateTime createdAt; - const GameTableData({ + const MatchTableData({ required this.id, required this.name, this.winnerId, @@ -665,8 +665,8 @@ class GameTableData extends DataClass implements Insertable { return map; } - GameTableCompanion toCompanion(bool nullToAbsent) { - return GameTableCompanion( + MatchTableCompanion toCompanion(bool nullToAbsent) { + return MatchTableCompanion( id: Value(id), name: Value(name), winnerId: winnerId == null && nullToAbsent @@ -676,12 +676,12 @@ class GameTableData extends DataClass implements Insertable { ); } - factory GameTableData.fromJson( + factory MatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return GameTableData( + return MatchTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), winnerId: serializer.fromJson(json['winnerId']), @@ -699,19 +699,19 @@ class GameTableData extends DataClass implements Insertable { }; } - GameTableData copyWith({ + MatchTableData copyWith({ String? id, String? name, Value winnerId = const Value.absent(), DateTime? createdAt, - }) => GameTableData( + }) => MatchTableData( id: id ?? this.id, name: name ?? this.name, winnerId: winnerId.present ? winnerId.value : this.winnerId, createdAt: createdAt ?? this.createdAt, ); - GameTableData copyWithCompanion(GameTableCompanion data) { - return GameTableData( + MatchTableData copyWithCompanion(MatchTableCompanion data) { + return MatchTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, @@ -721,7 +721,7 @@ class GameTableData extends DataClass implements Insertable { @override String toString() { - return (StringBuffer('GameTableData(') + return (StringBuffer('MatchTableData(') ..write('id: $id, ') ..write('name: $name, ') ..write('winnerId: $winnerId, ') @@ -735,27 +735,27 @@ class GameTableData extends DataClass implements Insertable { @override bool operator ==(Object other) => identical(this, other) || - (other is GameTableData && + (other is MatchTableData && other.id == this.id && other.name == this.name && other.winnerId == this.winnerId && other.createdAt == this.createdAt); } -class GameTableCompanion extends UpdateCompanion { +class MatchTableCompanion extends UpdateCompanion { final Value id; final Value name; final Value winnerId; final Value createdAt; final Value rowid; - const GameTableCompanion({ + const MatchTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), this.winnerId = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); - GameTableCompanion.insert({ + MatchTableCompanion.insert({ required String id, required String name, this.winnerId = const Value.absent(), @@ -764,7 +764,7 @@ class GameTableCompanion extends UpdateCompanion { }) : id = Value(id), name = Value(name), createdAt = Value(createdAt); - static Insertable custom({ + static Insertable custom({ Expression? id, Expression? name, Expression? winnerId, @@ -780,14 +780,14 @@ class GameTableCompanion extends UpdateCompanion { }); } - GameTableCompanion copyWith({ + MatchTableCompanion copyWith({ Value? id, Value? name, Value? winnerId, Value? createdAt, Value? rowid, }) { - return GameTableCompanion( + return MatchTableCompanion( id: id ?? this.id, name: name ?? this.name, winnerId: winnerId ?? this.winnerId, @@ -819,7 +819,7 @@ class GameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('GameTableCompanion(') + return (StringBuffer('MatchTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') ..write('winnerId: $winnerId, ') @@ -1055,12 +1055,12 @@ class PlayerGroupTableCompanion extends UpdateCompanion { } } -class $PlayerGameTableTable extends PlayerGameTable - with TableInfo<$PlayerGameTableTable, PlayerGameTableData> { +class $PlayerMatchTableTable extends PlayerMatchTable + with TableInfo<$PlayerMatchTableTable, PlayerMatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $PlayerGameTableTable(this.attachedDatabase, [this._alias]); + $PlayerMatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _playerIdMeta = const VerificationMeta( 'playerId', ); @@ -1075,28 +1075,30 @@ class $PlayerGameTableTable extends PlayerGameTable 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); - static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + static const VerificationMeta _matchIdMeta = const VerificationMeta( + 'matchId', + ); @override - late final GeneratedColumn gameId = GeneratedColumn( - 'game_id', + late final GeneratedColumn matchId = GeneratedColumn( + 'match_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES game_table (id) ON DELETE CASCADE', + 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); @override - List get $columns => [playerId, gameId]; + List get $columns => [playerId, matchId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'player_game_table'; + static const String $name = 'player_match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -1109,68 +1111,68 @@ class $PlayerGameTableTable extends PlayerGameTable } else if (isInserting) { context.missing(_playerIdMeta); } - if (data.containsKey('game_id')) { + if (data.containsKey('match_id')) { context.handle( - _gameIdMeta, - gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + _matchIdMeta, + matchId.isAcceptableOrUnknown(data['match_id']!, _matchIdMeta), ); } else if (isInserting) { - context.missing(_gameIdMeta); + context.missing(_matchIdMeta); } return context; } @override - Set get $primaryKey => {playerId, gameId}; + Set get $primaryKey => {playerId, matchId}; @override - PlayerGameTableData map(Map data, {String? tablePrefix}) { + PlayerMatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlayerGameTableData( + return PlayerMatchTableData( playerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}player_id'], )!, - gameId: attachedDatabase.typeMapping.read( + matchId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}game_id'], + data['${effectivePrefix}match_id'], )!, ); } @override - $PlayerGameTableTable createAlias(String alias) { - return $PlayerGameTableTable(attachedDatabase, alias); + $PlayerMatchTableTable createAlias(String alias) { + return $PlayerMatchTableTable(attachedDatabase, alias); } } -class PlayerGameTableData extends DataClass - implements Insertable { +class PlayerMatchTableData extends DataClass + implements Insertable { final String playerId; - final String gameId; - const PlayerGameTableData({required this.playerId, required this.gameId}); + final String matchId; + const PlayerMatchTableData({required this.playerId, required this.matchId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['player_id'] = Variable(playerId); - map['game_id'] = Variable(gameId); + map['match_id'] = Variable(matchId); return map; } - PlayerGameTableCompanion toCompanion(bool nullToAbsent) { - return PlayerGameTableCompanion( + PlayerMatchTableCompanion toCompanion(bool nullToAbsent) { + return PlayerMatchTableCompanion( playerId: Value(playerId), - gameId: Value(gameId), + matchId: Value(matchId), ); } - factory PlayerGameTableData.fromJson( + factory PlayerMatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return PlayerGameTableData( + return PlayerMatchTableData( playerId: serializer.fromJson(json['playerId']), - gameId: serializer.fromJson(json['gameId']), + matchId: serializer.fromJson(json['matchId']), ); } @override @@ -1178,76 +1180,76 @@ class PlayerGameTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'playerId': serializer.toJson(playerId), - 'gameId': serializer.toJson(gameId), + 'matchId': serializer.toJson(matchId), }; } - PlayerGameTableData copyWith({String? playerId, String? gameId}) => - PlayerGameTableData( + PlayerMatchTableData copyWith({String? playerId, String? matchId}) => + PlayerMatchTableData( playerId: playerId ?? this.playerId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, ); - PlayerGameTableData copyWithCompanion(PlayerGameTableCompanion data) { - return PlayerGameTableData( + PlayerMatchTableData copyWithCompanion(PlayerMatchTableCompanion data) { + return PlayerMatchTableData( playerId: data.playerId.present ? data.playerId.value : this.playerId, - gameId: data.gameId.present ? data.gameId.value : this.gameId, + matchId: data.matchId.present ? data.matchId.value : this.matchId, ); } @override String toString() { - return (StringBuffer('PlayerGameTableData(') + return (StringBuffer('PlayerMatchTableData(') ..write('playerId: $playerId, ') - ..write('gameId: $gameId') + ..write('matchId: $matchId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(playerId, gameId); + int get hashCode => Object.hash(playerId, matchId); @override bool operator ==(Object other) => identical(this, other) || - (other is PlayerGameTableData && + (other is PlayerMatchTableData && other.playerId == this.playerId && - other.gameId == this.gameId); + other.matchId == this.matchId); } -class PlayerGameTableCompanion extends UpdateCompanion { +class PlayerMatchTableCompanion extends UpdateCompanion { final Value playerId; - final Value gameId; + final Value matchId; final Value rowid; - const PlayerGameTableCompanion({ + const PlayerMatchTableCompanion({ this.playerId = const Value.absent(), - this.gameId = const Value.absent(), + this.matchId = const Value.absent(), this.rowid = const Value.absent(), }); - PlayerGameTableCompanion.insert({ + PlayerMatchTableCompanion.insert({ required String playerId, - required String gameId, + required String matchId, this.rowid = const Value.absent(), }) : playerId = Value(playerId), - gameId = Value(gameId); - static Insertable custom({ + matchId = Value(matchId); + static Insertable custom({ Expression? playerId, - Expression? gameId, + Expression? matchId, Expression? rowid, }) { return RawValuesInsertable({ if (playerId != null) 'player_id': playerId, - if (gameId != null) 'game_id': gameId, + if (matchId != null) 'match_id': matchId, if (rowid != null) 'rowid': rowid, }); } - PlayerGameTableCompanion copyWith({ + PlayerMatchTableCompanion copyWith({ Value? playerId, - Value? gameId, + Value? matchId, Value? rowid, }) { - return PlayerGameTableCompanion( + return PlayerMatchTableCompanion( playerId: playerId ?? this.playerId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, rowid: rowid ?? this.rowid, ); } @@ -1258,8 +1260,8 @@ class PlayerGameTableCompanion extends UpdateCompanion { if (playerId.present) { map['player_id'] = Variable(playerId.value); } - if (gameId.present) { - map['game_id'] = Variable(gameId.value); + if (matchId.present) { + map['match_id'] = Variable(matchId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -1269,21 +1271,21 @@ class PlayerGameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('PlayerGameTableCompanion(') + return (StringBuffer('PlayerMatchTableCompanion(') ..write('playerId: $playerId, ') - ..write('gameId: $gameId, ') + ..write('matchId: $matchId, ') ..write('rowid: $rowid') ..write(')')) .toString(); } } -class $GroupGameTableTable extends GroupGameTable - with TableInfo<$GroupGameTableTable, GroupGameTableData> { +class $GroupMatchTableTable extends GroupMatchTable + with TableInfo<$GroupMatchTableTable, GroupMatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $GroupGameTableTable(this.attachedDatabase, [this._alias]); + $GroupMatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _groupIdMeta = const VerificationMeta( 'groupId', ); @@ -1298,28 +1300,30 @@ class $GroupGameTableTable extends GroupGameTable 'REFERENCES group_table (id) ON DELETE CASCADE', ), ); - static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + static const VerificationMeta _matchIdMeta = const VerificationMeta( + 'matchId', + ); @override - late final GeneratedColumn gameId = GeneratedColumn( - 'game_id', + late final GeneratedColumn matchId = GeneratedColumn( + 'match_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES game_table (id) ON DELETE CASCADE', + 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); @override - List get $columns => [groupId, gameId]; + List get $columns => [groupId, matchId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'group_game_table'; + static const String $name = 'group_match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -1332,68 +1336,68 @@ class $GroupGameTableTable extends GroupGameTable } else if (isInserting) { context.missing(_groupIdMeta); } - if (data.containsKey('game_id')) { + if (data.containsKey('match_id')) { context.handle( - _gameIdMeta, - gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + _matchIdMeta, + matchId.isAcceptableOrUnknown(data['match_id']!, _matchIdMeta), ); } else if (isInserting) { - context.missing(_gameIdMeta); + context.missing(_matchIdMeta); } return context; } @override - Set get $primaryKey => {groupId, gameId}; + Set get $primaryKey => {groupId, matchId}; @override - GroupGameTableData map(Map data, {String? tablePrefix}) { + GroupMatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GroupGameTableData( + return GroupMatchTableData( groupId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}group_id'], )!, - gameId: attachedDatabase.typeMapping.read( + matchId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}game_id'], + data['${effectivePrefix}match_id'], )!, ); } @override - $GroupGameTableTable createAlias(String alias) { - return $GroupGameTableTable(attachedDatabase, alias); + $GroupMatchTableTable createAlias(String alias) { + return $GroupMatchTableTable(attachedDatabase, alias); } } -class GroupGameTableData extends DataClass - implements Insertable { +class GroupMatchTableData extends DataClass + implements Insertable { final String groupId; - final String gameId; - const GroupGameTableData({required this.groupId, required this.gameId}); + final String matchId; + const GroupMatchTableData({required this.groupId, required this.matchId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['group_id'] = Variable(groupId); - map['game_id'] = Variable(gameId); + map['match_id'] = Variable(matchId); return map; } - GroupGameTableCompanion toCompanion(bool nullToAbsent) { - return GroupGameTableCompanion( + GroupMatchTableCompanion toCompanion(bool nullToAbsent) { + return GroupMatchTableCompanion( groupId: Value(groupId), - gameId: Value(gameId), + matchId: Value(matchId), ); } - factory GroupGameTableData.fromJson( + factory GroupMatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return GroupGameTableData( + return GroupMatchTableData( groupId: serializer.fromJson(json['groupId']), - gameId: serializer.fromJson(json['gameId']), + matchId: serializer.fromJson(json['matchId']), ); } @override @@ -1401,76 +1405,76 @@ class GroupGameTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'groupId': serializer.toJson(groupId), - 'gameId': serializer.toJson(gameId), + 'matchId': serializer.toJson(matchId), }; } - GroupGameTableData copyWith({String? groupId, String? gameId}) => - GroupGameTableData( + GroupMatchTableData copyWith({String? groupId, String? matchId}) => + GroupMatchTableData( groupId: groupId ?? this.groupId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, ); - GroupGameTableData copyWithCompanion(GroupGameTableCompanion data) { - return GroupGameTableData( + GroupMatchTableData copyWithCompanion(GroupMatchTableCompanion data) { + return GroupMatchTableData( groupId: data.groupId.present ? data.groupId.value : this.groupId, - gameId: data.gameId.present ? data.gameId.value : this.gameId, + matchId: data.matchId.present ? data.matchId.value : this.matchId, ); } @override String toString() { - return (StringBuffer('GroupGameTableData(') + return (StringBuffer('GroupMatchTableData(') ..write('groupId: $groupId, ') - ..write('gameId: $gameId') + ..write('matchId: $matchId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(groupId, gameId); + int get hashCode => Object.hash(groupId, matchId); @override bool operator ==(Object other) => identical(this, other) || - (other is GroupGameTableData && + (other is GroupMatchTableData && other.groupId == this.groupId && - other.gameId == this.gameId); + other.matchId == this.matchId); } -class GroupGameTableCompanion extends UpdateCompanion { +class GroupMatchTableCompanion extends UpdateCompanion { final Value groupId; - final Value gameId; + final Value matchId; final Value rowid; - const GroupGameTableCompanion({ + const GroupMatchTableCompanion({ this.groupId = const Value.absent(), - this.gameId = const Value.absent(), + this.matchId = const Value.absent(), this.rowid = const Value.absent(), }); - GroupGameTableCompanion.insert({ + GroupMatchTableCompanion.insert({ required String groupId, - required String gameId, + required String matchId, this.rowid = const Value.absent(), }) : groupId = Value(groupId), - gameId = Value(gameId); - static Insertable custom({ + matchId = Value(matchId); + static Insertable custom({ Expression? groupId, - Expression? gameId, + Expression? matchId, Expression? rowid, }) { return RawValuesInsertable({ if (groupId != null) 'group_id': groupId, - if (gameId != null) 'game_id': gameId, + if (matchId != null) 'match_id': matchId, if (rowid != null) 'rowid': rowid, }); } - GroupGameTableCompanion copyWith({ + GroupMatchTableCompanion copyWith({ Value? groupId, - Value? gameId, + Value? matchId, Value? rowid, }) { - return GroupGameTableCompanion( + return GroupMatchTableCompanion( groupId: groupId ?? this.groupId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, rowid: rowid ?? this.rowid, ); } @@ -1481,8 +1485,8 @@ class GroupGameTableCompanion extends UpdateCompanion { if (groupId.present) { map['group_id'] = Variable(groupId.value); } - if (gameId.present) { - map['game_id'] = Variable(gameId.value); + if (matchId.present) { + map['match_id'] = Variable(matchId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -1492,9 +1496,9 @@ class GroupGameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('GroupGameTableCompanion(') + return (StringBuffer('GroupMatchTableCompanion(') ..write('groupId: $groupId, ') - ..write('gameId: $gameId, ') + ..write('matchId: $matchId, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -1506,22 +1510,26 @@ abstract class _$AppDatabase extends GeneratedDatabase { $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $PlayerTableTable playerTable = $PlayerTableTable(this); late final $GroupTableTable groupTable = $GroupTableTable(this); - late final $GameTableTable gameTable = $GameTableTable(this); + late final $MatchTableTable matchTable = $MatchTableTable(this); late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); - late final $PlayerGameTableTable playerGameTable = $PlayerGameTableTable( + late final $PlayerMatchTableTable playerMatchTable = $PlayerMatchTableTable( + this, + ); + late final $GroupMatchTableTable groupMatchTable = $GroupMatchTableTable( this, ); - late final $GroupGameTableTable groupGameTable = $GroupGameTableTable(this); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GroupDao groupDao = GroupDao(this as AppDatabase); - late final GameDao gameDao = GameDao(this as AppDatabase); + late final MatchDao matchDao = MatchDao(this as AppDatabase); late final PlayerGroupDao playerGroupDao = PlayerGroupDao( this as AppDatabase, ); - late final PlayerGameDao playerGameDao = PlayerGameDao(this as AppDatabase); - late final GroupGameDao groupGameDao = GroupGameDao(this as AppDatabase); + late final PlayerMatchDao playerMatchDao = PlayerMatchDao( + this as AppDatabase, + ); + late final GroupMatchDao groupMatchDao = GroupMatchDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -1529,10 +1537,10 @@ abstract class _$AppDatabase extends GeneratedDatabase { List get allSchemaEntities => [ playerTable, groupTable, - gameTable, + matchTable, playerGroupTable, - playerGameTable, - groupGameTable, + playerMatchTable, + groupMatchTable, ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ @@ -1555,28 +1563,28 @@ abstract class _$AppDatabase extends GeneratedDatabase { 'player_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( - 'game_table', + 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( 'group_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( - 'game_table', + 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], ), ]); } @@ -1623,23 +1631,23 @@ final class $$PlayerTableTableReferences ); } - static MultiTypedResultKey<$PlayerGameTableTable, List> - _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.playerGameTable, + static MultiTypedResultKey<$PlayerMatchTableTable, List> + _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerMatchTable, aliasName: $_aliasNameGenerator( db.playerTable.id, - db.playerGameTable.playerId, + db.playerMatchTable.playerId, ), ); - $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { - final manager = $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableProcessedTableManager get playerMatchTableRefs { + final manager = $$PlayerMatchTableTableTableManager( $_db, - $_db.playerGameTable, + $_db.playerMatchTable, ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull( - _playerGameTableRefsTable($_db), + _playerMatchTableRefsTable($_db), ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), @@ -1696,22 +1704,22 @@ class $$PlayerTableTableFilterComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableFilterComposer f) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableFilterComposer f) f, ) { - final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + final $$PlayerMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, + referencedTable: $db.playerMatchTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableFilterComposer( + }) => $$PlayerMatchTableTableFilterComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -1790,22 +1798,22 @@ class $$PlayerTableTableAnnotationComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, ) { - final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + final $$PlayerMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, + referencedTable: $db.playerMatchTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableAnnotationComposer( + }) => $$PlayerMatchTableTableAnnotationComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -1831,7 +1839,7 @@ class $$PlayerTableTableTableManager PlayerTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, + bool playerMatchTableRefs, }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) @@ -1878,12 +1886,12 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, playerGameTableRefs = false}) { + ({playerGroupTableRefs = false, playerMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, - if (playerGameTableRefs) db.playerGameTable, + if (playerMatchTableRefs) db.playerMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -1909,21 +1917,21 @@ class $$PlayerTableTableTableManager ), typedResults: items, ), - if (playerGameTableRefs) + if (playerMatchTableRefs) await $_getPrefetchedData< PlayerTableData, $PlayerTableTable, - PlayerGameTableData + PlayerMatchTableData >( currentTable: table, referencedTable: $$PlayerTableTableReferences - ._playerGameTableRefsTable(db), + ._playerMatchTableRefsTable(db), managerFromTypedResult: (p0) => $$PlayerTableTableReferences( db, table, p0, - ).playerGameTableRefs, + ).playerMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.playerId == item.id, @@ -1952,7 +1960,7 @@ typedef $$PlayerTableTableProcessedTableManager = PlayerTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, + bool playerMatchTableRefs, }) >; typedef $$GroupTableTableCreateCompanionBuilder = @@ -1997,22 +2005,24 @@ final class $$GroupTableTableReferences ); } - static MultiTypedResultKey<$GroupGameTableTable, List> - _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupGameTable, + static MultiTypedResultKey<$GroupMatchTableTable, List> + _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupMatchTable, aliasName: $_aliasNameGenerator( db.groupTable.id, - db.groupGameTable.groupId, + db.groupMatchTable.groupId, ), ); - $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { - final manager = $$GroupGameTableTableTableManager( + $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { + final manager = $$GroupMatchTableTableTableManager( $_db, - $_db.groupGameTable, + $_db.groupMatchTable, ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _groupMatchTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); @@ -2068,22 +2078,22 @@ class $$GroupTableTableFilterComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableFilterComposer f) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableFilterComposer f) f, ) { - final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, + referencedTable: $db.groupMatchTable, getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableFilterComposer( + }) => $$GroupMatchTableTableFilterComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2162,22 +2172,22 @@ class $$GroupTableTableAnnotationComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableAnnotationComposer a) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableAnnotationComposer a) f, ) { - final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, + referencedTable: $db.groupMatchTable, getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableAnnotationComposer( + }) => $$GroupMatchTableTableAnnotationComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2203,7 +2213,7 @@ class $$GroupTableTableTableManager GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool groupGameTableRefs, + bool groupMatchTableRefs, }) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) @@ -2250,12 +2260,12 @@ class $$GroupTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, groupGameTableRefs = false}) { + ({playerGroupTableRefs = false, groupMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, - if (groupGameTableRefs) db.groupGameTable, + if (groupMatchTableRefs) db.groupMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -2281,21 +2291,21 @@ class $$GroupTableTableTableManager ), typedResults: items, ), - if (groupGameTableRefs) + if (groupMatchTableRefs) await $_getPrefetchedData< GroupTableData, $GroupTableTable, - GroupGameTableData + GroupMatchTableData >( currentTable: table, referencedTable: $$GroupTableTableReferences - ._groupGameTableRefsTable(db), + ._groupMatchTableRefsTable(db), managerFromTypedResult: (p0) => $$GroupTableTableReferences( db, table, p0, - ).groupGameTableRefs, + ).groupMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.groupId == item.id, @@ -2324,19 +2334,19 @@ typedef $$GroupTableTableProcessedTableManager = GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool groupGameTableRefs, + bool groupMatchTableRefs, }) >; -typedef $$GameTableTableCreateCompanionBuilder = - GameTableCompanion Function({ +typedef $$MatchTableTableCreateCompanionBuilder = + MatchTableCompanion Function({ required String id, required String name, Value winnerId, required DateTime createdAt, Value rowid, }); -typedef $$GameTableTableUpdateCompanionBuilder = - GameTableCompanion Function({ +typedef $$MatchTableTableUpdateCompanionBuilder = + MatchTableCompanion Function({ Value id, Value name, Value winnerId, @@ -2344,52 +2354,60 @@ typedef $$GameTableTableUpdateCompanionBuilder = Value rowid, }); -final class $$GameTableTableReferences - extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { - $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); +final class $$MatchTableTableReferences + extends BaseReferences<_$AppDatabase, $MatchTableTable, MatchTableData> { + $$MatchTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$PlayerGameTableTable, List> - _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.playerGameTable, - aliasName: $_aliasNameGenerator(db.gameTable.id, db.playerGameTable.gameId), + static MultiTypedResultKey<$PlayerMatchTableTable, List> + _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerMatchTable, + aliasName: $_aliasNameGenerator( + db.matchTable.id, + db.playerMatchTable.matchId, + ), ); - $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { - final manager = $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableProcessedTableManager get playerMatchTableRefs { + final manager = $$PlayerMatchTableTableTableManager( $_db, - $_db.playerGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + $_db.playerMatchTable, + ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull( - _playerGameTableRefsTable($_db), + _playerMatchTableRefsTable($_db), ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); } - static MultiTypedResultKey<$GroupGameTableTable, List> - _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupGameTable, - aliasName: $_aliasNameGenerator(db.gameTable.id, db.groupGameTable.gameId), + static MultiTypedResultKey<$GroupMatchTableTable, List> + _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupMatchTable, + aliasName: $_aliasNameGenerator( + db.matchTable.id, + db.groupMatchTable.matchId, + ), ); - $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { - final manager = $$GroupGameTableTableTableManager( + $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { + final manager = $$GroupMatchTableTableTableManager( $_db, - $_db.groupGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + $_db.groupMatchTable, + ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _groupMatchTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); } } -class $$GameTableTableFilterComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableFilterComposer({ +class $$MatchTableTableFilterComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2416,22 +2434,22 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableFilterComposer f) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableFilterComposer f) f, ) { - final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + final $$PlayerMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableFilterComposer( + }) => $$PlayerMatchTableTableFilterComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2441,22 +2459,22 @@ class $$GameTableTableFilterComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableFilterComposer f) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableFilterComposer f) f, ) { - final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.groupMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableFilterComposer( + }) => $$GroupMatchTableTableFilterComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2467,9 +2485,9 @@ class $$GameTableTableFilterComposer } } -class $$GameTableTableOrderingComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableOrderingComposer({ +class $$MatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2497,9 +2515,9 @@ class $$GameTableTableOrderingComposer ); } -class $$GameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableAnnotationComposer({ +class $$MatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2518,22 +2536,22 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, ) { - final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + final $$PlayerMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableAnnotationComposer( + }) => $$PlayerMatchTableTableAnnotationComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2543,22 +2561,22 @@ class $$GameTableTableAnnotationComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableAnnotationComposer a) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableAnnotationComposer a) f, ) { - final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.groupMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableAnnotationComposer( + }) => $$GroupMatchTableTableAnnotationComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2569,35 +2587,35 @@ class $$GameTableTableAnnotationComposer } } -class $$GameTableTableTableManager +class $$MatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - (GameTableData, $$GameTableTableReferences), - GameTableData, + $MatchTableTable, + MatchTableData, + $$MatchTableTableFilterComposer, + $$MatchTableTableOrderingComposer, + $$MatchTableTableAnnotationComposer, + $$MatchTableTableCreateCompanionBuilder, + $$MatchTableTableUpdateCompanionBuilder, + (MatchTableData, $$MatchTableTableReferences), + MatchTableData, PrefetchHooks Function({ - bool playerGameTableRefs, - bool groupGameTableRefs, + bool playerMatchTableRefs, + bool groupMatchTableRefs, }) > { - $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) + $$MatchTableTableTableManager(_$AppDatabase db, $MatchTableTable table) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$GameTableTableFilterComposer($db: db, $table: table), + $$MatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$GameTableTableOrderingComposer($db: db, $table: table), + $$MatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$GameTableTableAnnotationComposer($db: db, $table: table), + $$MatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), @@ -2605,7 +2623,7 @@ class $$GameTableTableTableManager Value winnerId = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), - }) => GameTableCompanion( + }) => MatchTableCompanion( id: id, name: name, winnerId: winnerId, @@ -2619,7 +2637,7 @@ class $$GameTableTableTableManager Value winnerId = const Value.absent(), required DateTime createdAt, Value rowid = const Value.absent(), - }) => GameTableCompanion.insert( + }) => MatchTableCompanion.insert( id: id, name: name, winnerId: winnerId, @@ -2630,60 +2648,60 @@ class $$GameTableTableTableManager .map( (e) => ( e.readTable(table), - $$GameTableTableReferences(db, table, e), + $$MatchTableTableReferences(db, table, e), ), ) .toList(), prefetchHooksCallback: - ({playerGameTableRefs = false, groupGameTableRefs = false}) { + ({playerMatchTableRefs = false, groupMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (playerGameTableRefs) db.playerGameTable, - if (groupGameTableRefs) db.groupGameTable, + if (playerMatchTableRefs) db.playerMatchTable, + if (groupMatchTableRefs) db.groupMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (playerGameTableRefs) + if (playerMatchTableRefs) await $_getPrefetchedData< - GameTableData, - $GameTableTable, - PlayerGameTableData + MatchTableData, + $MatchTableTable, + PlayerMatchTableData >( currentTable: table, - referencedTable: $$GameTableTableReferences - ._playerGameTableRefsTable(db), + referencedTable: $$MatchTableTableReferences + ._playerMatchTableRefsTable(db), managerFromTypedResult: (p0) => - $$GameTableTableReferences( + $$MatchTableTableReferences( db, table, p0, - ).playerGameTableRefs, + ).playerMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, + (e) => e.matchId == item.id, ), typedResults: items, ), - if (groupGameTableRefs) + if (groupMatchTableRefs) await $_getPrefetchedData< - GameTableData, - $GameTableTable, - GroupGameTableData + MatchTableData, + $MatchTableTable, + GroupMatchTableData >( currentTable: table, - referencedTable: $$GameTableTableReferences - ._groupGameTableRefsTable(db), + referencedTable: $$MatchTableTableReferences + ._groupMatchTableRefsTable(db), managerFromTypedResult: (p0) => - $$GameTableTableReferences( + $$MatchTableTableReferences( db, table, p0, - ).groupGameTableRefs, + ).groupMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, + (e) => e.matchId == item.id, ), typedResults: items, ), @@ -2695,21 +2713,21 @@ class $$GameTableTableTableManager ); } -typedef $$GameTableTableProcessedTableManager = +typedef $$MatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - (GameTableData, $$GameTableTableReferences), - GameTableData, + $MatchTableTable, + MatchTableData, + $$MatchTableTableFilterComposer, + $$MatchTableTableOrderingComposer, + $$MatchTableTableAnnotationComposer, + $$MatchTableTableCreateCompanionBuilder, + $$MatchTableTableUpdateCompanionBuilder, + (MatchTableData, $$MatchTableTableReferences), + MatchTableData, PrefetchHooks Function({ - bool playerGameTableRefs, - bool groupGameTableRefs, + bool playerMatchTableRefs, + bool groupMatchTableRefs, }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = @@ -3077,27 +3095,27 @@ typedef $$PlayerGroupTableTableProcessedTableManager = PlayerGroupTableData, PrefetchHooks Function({bool playerId, bool groupId}) >; -typedef $$PlayerGameTableTableCreateCompanionBuilder = - PlayerGameTableCompanion Function({ +typedef $$PlayerMatchTableTableCreateCompanionBuilder = + PlayerMatchTableCompanion Function({ required String playerId, - required String gameId, + required String matchId, Value rowid, }); -typedef $$PlayerGameTableTableUpdateCompanionBuilder = - PlayerGameTableCompanion Function({ +typedef $$PlayerMatchTableTableUpdateCompanionBuilder = + PlayerMatchTableCompanion Function({ Value playerId, - Value gameId, + Value matchId, Value rowid, }); -final class $$PlayerGameTableTableReferences +final class $$PlayerMatchTableTableReferences extends BaseReferences< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData + $PlayerMatchTableTable, + PlayerMatchTableData > { - $$PlayerGameTableTableReferences( + $$PlayerMatchTableTableReferences( super.$_db, super.$_table, super.$_typedResult, @@ -3105,7 +3123,7 @@ final class $$PlayerGameTableTableReferences static $PlayerTableTable _playerIdTable(_$AppDatabase db) => db.playerTable.createAlias( - $_aliasNameGenerator(db.playerGameTable.playerId, db.playerTable.id), + $_aliasNameGenerator(db.playerMatchTable.playerId, db.playerTable.id), ); $$PlayerTableTableProcessedTableManager get playerId { @@ -3122,19 +3140,19 @@ final class $$PlayerGameTableTableReferences ); } - static $GameTableTable _gameIdTable(_$AppDatabase db) => - db.gameTable.createAlias( - $_aliasNameGenerator(db.playerGameTable.gameId, db.gameTable.id), + static $MatchTableTable _matchIdTable(_$AppDatabase db) => + db.matchTable.createAlias( + $_aliasNameGenerator(db.playerMatchTable.matchId, db.matchTable.id), ); - $$GameTableTableProcessedTableManager get gameId { - final $_column = $_itemColumn('game_id')!; + $$MatchTableTableProcessedTableManager get matchId { + final $_column = $_itemColumn('match_id')!; - final manager = $$GameTableTableTableManager( + final manager = $$MatchTableTableTableManager( $_db, - $_db.gameTable, + $_db.matchTable, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -3142,9 +3160,9 @@ final class $$PlayerGameTableTableReferences } } -class $$PlayerGameTableTableFilterComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableFilterComposer({ +class $$PlayerMatchTableTableFilterComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3174,20 +3192,20 @@ class $$PlayerGameTableTableFilterComposer return composer; } - $$GameTableTableFilterComposer get gameId { - final $$GameTableTableFilterComposer composer = $composerBuilder( + $$MatchTableTableFilterComposer get matchId { + final $$MatchTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( + }) => $$MatchTableTableFilterComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3198,9 +3216,9 @@ class $$PlayerGameTableTableFilterComposer } } -class $$PlayerGameTableTableOrderingComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableOrderingComposer({ +class $$PlayerMatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3230,20 +3248,20 @@ class $$PlayerGameTableTableOrderingComposer return composer; } - $$GameTableTableOrderingComposer get gameId { - final $$GameTableTableOrderingComposer composer = $composerBuilder( + $$MatchTableTableOrderingComposer get matchId { + final $$MatchTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableOrderingComposer( + }) => $$MatchTableTableOrderingComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3254,9 +3272,9 @@ class $$PlayerGameTableTableOrderingComposer } } -class $$PlayerGameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableAnnotationComposer({ +class $$PlayerMatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3286,20 +3304,20 @@ class $$PlayerGameTableTableAnnotationComposer return composer; } - $$GameTableTableAnnotationComposer get gameId { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( + $$MatchTableTableAnnotationComposer get matchId { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( + }) => $$MatchTableTableAnnotationComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3310,63 +3328,63 @@ class $$PlayerGameTableTableAnnotationComposer } } -class $$PlayerGameTableTableTableManager +class $$PlayerMatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData, - $$PlayerGameTableTableFilterComposer, - $$PlayerGameTableTableOrderingComposer, - $$PlayerGameTableTableAnnotationComposer, - $$PlayerGameTableTableCreateCompanionBuilder, - $$PlayerGameTableTableUpdateCompanionBuilder, - (PlayerGameTableData, $$PlayerGameTableTableReferences), - PlayerGameTableData, - PrefetchHooks Function({bool playerId, bool gameId}) + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId}) > { - $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableTableManager( _$AppDatabase db, - $PlayerGameTableTable table, + $PlayerMatchTableTable table, ) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$PlayerGameTableTableFilterComposer($db: db, $table: table), + $$PlayerMatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$PlayerGameTableTableOrderingComposer($db: db, $table: table), + $$PlayerMatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$PlayerGameTableTableAnnotationComposer($db: db, $table: table), + $$PlayerMatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value playerId = const Value.absent(), - Value gameId = const Value.absent(), + Value matchId = const Value.absent(), Value rowid = const Value.absent(), - }) => PlayerGameTableCompanion( + }) => PlayerMatchTableCompanion( playerId: playerId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), createCompanionCallback: ({ required String playerId, - required String gameId, + required String matchId, Value rowid = const Value.absent(), - }) => PlayerGameTableCompanion.insert( + }) => PlayerMatchTableCompanion.insert( playerId: playerId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), withReferenceMapper: (p0) => p0 .map( (e) => ( e.readTable(table), - $$PlayerGameTableTableReferences(db, table, e), + $$PlayerMatchTableTableReferences(db, table, e), ), ) .toList(), - prefetchHooksCallback: ({playerId = false, gameId = false}) { + prefetchHooksCallback: ({playerId = false, matchId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -3392,26 +3410,26 @@ class $$PlayerGameTableTableTableManager currentTable: table, currentColumn: table.playerId, referencedTable: - $$PlayerGameTableTableReferences + $$PlayerMatchTableTableReferences ._playerIdTable(db), referencedColumn: - $$PlayerGameTableTableReferences + $$PlayerMatchTableTableReferences ._playerIdTable(db) .id, ) as T; } - if (gameId) { + if (matchId) { state = state.withJoin( currentTable: table, - currentColumn: table.gameId, + currentColumn: table.matchId, referencedTable: - $$PlayerGameTableTableReferences - ._gameIdTable(db), + $$PlayerMatchTableTableReferences + ._matchIdTable(db), referencedColumn: - $$PlayerGameTableTableReferences - ._gameIdTable(db) + $$PlayerMatchTableTableReferences + ._matchIdTable(db) .id, ) as T; @@ -3428,41 +3446,41 @@ class $$PlayerGameTableTableTableManager ); } -typedef $$PlayerGameTableTableProcessedTableManager = +typedef $$PlayerMatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData, - $$PlayerGameTableTableFilterComposer, - $$PlayerGameTableTableOrderingComposer, - $$PlayerGameTableTableAnnotationComposer, - $$PlayerGameTableTableCreateCompanionBuilder, - $$PlayerGameTableTableUpdateCompanionBuilder, - (PlayerGameTableData, $$PlayerGameTableTableReferences), - PlayerGameTableData, - PrefetchHooks Function({bool playerId, bool gameId}) + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId}) >; -typedef $$GroupGameTableTableCreateCompanionBuilder = - GroupGameTableCompanion Function({ +typedef $$GroupMatchTableTableCreateCompanionBuilder = + GroupMatchTableCompanion Function({ required String groupId, - required String gameId, + required String matchId, Value rowid, }); -typedef $$GroupGameTableTableUpdateCompanionBuilder = - GroupGameTableCompanion Function({ +typedef $$GroupMatchTableTableUpdateCompanionBuilder = + GroupMatchTableCompanion Function({ Value groupId, - Value gameId, + Value matchId, Value rowid, }); -final class $$GroupGameTableTableReferences +final class $$GroupMatchTableTableReferences extends BaseReferences< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData + $GroupMatchTableTable, + GroupMatchTableData > { - $$GroupGameTableTableReferences( + $$GroupMatchTableTableReferences( super.$_db, super.$_table, super.$_typedResult, @@ -3470,7 +3488,7 @@ final class $$GroupGameTableTableReferences static $GroupTableTable _groupIdTable(_$AppDatabase db) => db.groupTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.groupId, db.groupTable.id), + $_aliasNameGenerator(db.groupMatchTable.groupId, db.groupTable.id), ); $$GroupTableTableProcessedTableManager get groupId { @@ -3487,19 +3505,19 @@ final class $$GroupGameTableTableReferences ); } - static $GameTableTable _gameIdTable(_$AppDatabase db) => - db.gameTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.gameId, db.gameTable.id), + static $MatchTableTable _matchIdTable(_$AppDatabase db) => + db.matchTable.createAlias( + $_aliasNameGenerator(db.groupMatchTable.matchId, db.matchTable.id), ); - $$GameTableTableProcessedTableManager get gameId { - final $_column = $_itemColumn('game_id')!; + $$MatchTableTableProcessedTableManager get matchId { + final $_column = $_itemColumn('match_id')!; - final manager = $$GameTableTableTableManager( + final manager = $$MatchTableTableTableManager( $_db, - $_db.gameTable, + $_db.matchTable, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -3507,9 +3525,9 @@ final class $$GroupGameTableTableReferences } } -class $$GroupGameTableTableFilterComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableFilterComposer({ +class $$GroupMatchTableTableFilterComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3539,20 +3557,20 @@ class $$GroupGameTableTableFilterComposer return composer; } - $$GameTableTableFilterComposer get gameId { - final $$GameTableTableFilterComposer composer = $composerBuilder( + $$MatchTableTableFilterComposer get matchId { + final $$MatchTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( + }) => $$MatchTableTableFilterComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3563,9 +3581,9 @@ class $$GroupGameTableTableFilterComposer } } -class $$GroupGameTableTableOrderingComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableOrderingComposer({ +class $$GroupMatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3595,20 +3613,20 @@ class $$GroupGameTableTableOrderingComposer return composer; } - $$GameTableTableOrderingComposer get gameId { - final $$GameTableTableOrderingComposer composer = $composerBuilder( + $$MatchTableTableOrderingComposer get matchId { + final $$MatchTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableOrderingComposer( + }) => $$MatchTableTableOrderingComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3619,9 +3637,9 @@ class $$GroupGameTableTableOrderingComposer } } -class $$GroupGameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableAnnotationComposer({ +class $$GroupMatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3651,20 +3669,20 @@ class $$GroupGameTableTableAnnotationComposer return composer; } - $$GameTableTableAnnotationComposer get gameId { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( + $$MatchTableTableAnnotationComposer get matchId { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( + }) => $$MatchTableTableAnnotationComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3675,63 +3693,63 @@ class $$GroupGameTableTableAnnotationComposer } } -class $$GroupGameTableTableTableManager +class $$GroupMatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData, - $$GroupGameTableTableFilterComposer, - $$GroupGameTableTableOrderingComposer, - $$GroupGameTableTableAnnotationComposer, - $$GroupGameTableTableCreateCompanionBuilder, - $$GroupGameTableTableUpdateCompanionBuilder, - (GroupGameTableData, $$GroupGameTableTableReferences), - GroupGameTableData, - PrefetchHooks Function({bool groupId, bool gameId}) + $GroupMatchTableTable, + GroupMatchTableData, + $$GroupMatchTableTableFilterComposer, + $$GroupMatchTableTableOrderingComposer, + $$GroupMatchTableTableAnnotationComposer, + $$GroupMatchTableTableCreateCompanionBuilder, + $$GroupMatchTableTableUpdateCompanionBuilder, + (GroupMatchTableData, $$GroupMatchTableTableReferences), + GroupMatchTableData, + PrefetchHooks Function({bool groupId, bool matchId}) > { - $$GroupGameTableTableTableManager( + $$GroupMatchTableTableTableManager( _$AppDatabase db, - $GroupGameTableTable table, + $GroupMatchTableTable table, ) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$GroupGameTableTableFilterComposer($db: db, $table: table), + $$GroupMatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$GroupGameTableTableOrderingComposer($db: db, $table: table), + $$GroupMatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$GroupGameTableTableAnnotationComposer($db: db, $table: table), + $$GroupMatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value groupId = const Value.absent(), - Value gameId = const Value.absent(), + Value matchId = const Value.absent(), Value rowid = const Value.absent(), - }) => GroupGameTableCompanion( + }) => GroupMatchTableCompanion( groupId: groupId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), createCompanionCallback: ({ required String groupId, - required String gameId, + required String matchId, Value rowid = const Value.absent(), - }) => GroupGameTableCompanion.insert( + }) => GroupMatchTableCompanion.insert( groupId: groupId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), withReferenceMapper: (p0) => p0 .map( (e) => ( e.readTable(table), - $$GroupGameTableTableReferences(db, table, e), + $$GroupMatchTableTableReferences(db, table, e), ), ) .toList(), - prefetchHooksCallback: ({groupId = false, gameId = false}) { + prefetchHooksCallback: ({groupId = false, matchId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -3756,25 +3774,27 @@ class $$GroupGameTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.groupId, - referencedTable: $$GroupGameTableTableReferences - ._groupIdTable(db), + referencedTable: + $$GroupMatchTableTableReferences + ._groupIdTable(db), referencedColumn: - $$GroupGameTableTableReferences + $$GroupMatchTableTableReferences ._groupIdTable(db) .id, ) as T; } - if (gameId) { + if (matchId) { state = state.withJoin( currentTable: table, - currentColumn: table.gameId, - referencedTable: $$GroupGameTableTableReferences - ._gameIdTable(db), + currentColumn: table.matchId, + referencedTable: + $$GroupMatchTableTableReferences + ._matchIdTable(db), referencedColumn: - $$GroupGameTableTableReferences - ._gameIdTable(db) + $$GroupMatchTableTableReferences + ._matchIdTable(db) .id, ) as T; @@ -3791,19 +3811,19 @@ class $$GroupGameTableTableTableManager ); } -typedef $$GroupGameTableTableProcessedTableManager = +typedef $$GroupMatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData, - $$GroupGameTableTableFilterComposer, - $$GroupGameTableTableOrderingComposer, - $$GroupGameTableTableAnnotationComposer, - $$GroupGameTableTableCreateCompanionBuilder, - $$GroupGameTableTableUpdateCompanionBuilder, - (GroupGameTableData, $$GroupGameTableTableReferences), - GroupGameTableData, - PrefetchHooks Function({bool groupId, bool gameId}) + $GroupMatchTableTable, + GroupMatchTableData, + $$GroupMatchTableTableFilterComposer, + $$GroupMatchTableTableOrderingComposer, + $$GroupMatchTableTableAnnotationComposer, + $$GroupMatchTableTableCreateCompanionBuilder, + $$GroupMatchTableTableUpdateCompanionBuilder, + (GroupMatchTableData, $$GroupMatchTableTableReferences), + GroupMatchTableData, + PrefetchHooks Function({bool groupId, bool matchId}) >; class $AppDatabaseManager { @@ -3813,12 +3833,12 @@ class $AppDatabaseManager { $$PlayerTableTableTableManager(_db, _db.playerTable); $$GroupTableTableTableManager get groupTable => $$GroupTableTableTableManager(_db, _db.groupTable); - $$GameTableTableTableManager get gameTable => - $$GameTableTableTableManager(_db, _db.gameTable); + $$MatchTableTableTableManager get matchTable => + $$MatchTableTableTableManager(_db, _db.matchTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); - $$PlayerGameTableTableTableManager get playerGameTable => - $$PlayerGameTableTableTableManager(_db, _db.playerGameTable); - $$GroupGameTableTableTableManager get groupGameTable => - $$GroupGameTableTableTableManager(_db, _db.groupGameTable); + $$PlayerMatchTableTableTableManager get playerMatchTable => + $$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable); + $$GroupMatchTableTableTableManager get groupMatchTable => + $$GroupMatchTableTableTableManager(_db, _db.groupMatchTable); } diff --git a/lib/data/db/tables/group_game_table.dart b/lib/data/db/tables/group_game_table.dart deleted file mode 100644 index a16672e..0000000 --- a/lib/data/db/tables/group_game_table.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; - -class GroupGameTable extends Table { - TextColumn get groupId => - text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get gameId => - text().references(GameTable, #id, onDelete: KeyAction.cascade)(); - - @override - Set> get primaryKey => {groupId, gameId}; -} diff --git a/lib/data/db/tables/group_match_table.dart b/lib/data/db/tables/group_match_table.dart new file mode 100644 index 0000000..3f77dcb --- /dev/null +++ b/lib/data/db/tables/group_match_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; + +class GroupMatchTable extends Table { + TextColumn get groupId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get matchId => + text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {groupId, matchId}; +} diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/match_table.dart similarity index 88% rename from lib/data/db/tables/game_table.dart rename to lib/data/db/tables/match_table.dart index 1a37a73..96aff2a 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/match_table.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; -class GameTable extends Table { +class MatchTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); late final winnerId = text().nullable()(); diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart deleted file mode 100644 index 74c36fe..0000000 --- a/lib/data/db/tables/player_game_table.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; - -class PlayerGameTable extends Table { - TextColumn get playerId => - text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get gameId => - text().references(GameTable, #id, onDelete: KeyAction.cascade)(); - - @override - Set> get primaryKey => {playerId, gameId}; -} diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart new file mode 100644 index 0000000..e155cd5 --- /dev/null +++ b/lib/data/db/tables/player_match_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; + +class PlayerMatchTable extends Table { + TextColumn get playerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get matchId => + text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {playerId, matchId}; +} diff --git a/lib/data/dto/game.dart b/lib/data/dto/match.dart similarity index 81% rename from lib/data/dto/game.dart rename to lib/data/dto/match.dart index 48ef902..fcb4dae 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/match.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:uuid/uuid.dart'; -class Game { +class Match { final String id; final DateTime createdAt; final String name; @@ -11,7 +11,7 @@ class Game { final Group? group; final Player? winner; - Game({ + Match({ String? id, DateTime? createdAt, required this.name, @@ -23,11 +23,11 @@ class Game { @override String toString() { - return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; + return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; } - /// Creates a Game instance from a JSON object. - Game.fromJson(Map json) + /// Creates a Match instance from a JSON object. + Match.fromJson(Map json) : id = json['id'], name = json['name'], createdAt = DateTime.parse(json['createdAt']), @@ -39,7 +39,7 @@ class Game { group = json['group'] != null ? Group.fromJson(json['group']) : null, winner = json['winner'] != null ? Player.fromJson(json['winner']) : null; - /// Converts the Game instance to a JSON object. + /// Converts the Match instance to a JSON object. Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 71a072e..2ec28fa 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -30,8 +30,8 @@ class _CustomNavigationBarState extends State final List tabs = [ KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), KeyedSubtree( - key: ValueKey('games_$tabKeyCount'), - child: const GameHistoryView(), + key: ValueKey('matches_$tabKeyCount'), + child: const MatchView(), ), KeyedSubtree( key: ValueKey('groups_$tabKeyCount'), @@ -96,7 +96,7 @@ class _CustomNavigationBarState extends State index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: 'Games', + label: 'Matches', onTabTapped: onTabTapped, ), NavbarItem( @@ -133,7 +133,7 @@ class _CustomNavigationBarState extends State case 0: return 'Home'; case 1: - return 'Game History'; + return 'Matches'; case 2: return 'Groups'; case 3: diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_group_view.dart rename to lib/presentation/views/main_menu/group_view/create_group_view.dart diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart similarity index 97% rename from lib/presentation/views/main_menu/groups_view.dart rename to lib/presentation/views/main_menu/group_view/groups_view.dart index ce47f90..5fd5e4b 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; @@ -25,7 +25,7 @@ class _GroupsViewState extends State { late final List skeletonData = List.filled( 7, Group( - name: 'Skeleton Game', + name: 'Skeleton Match', members: [player, player, player, player, player, player], ), ); diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1667f2b..c6322d8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; @@ -18,15 +18,15 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { - late Future _gameCountFuture; + late Future _matchCountFuture; late Future _groupCountFuture; - late Future> _recentGamesFuture; + late Future> _recentMatchesFuture; bool isLoading = true; - late final List skeletonData = List.filled( + late final List skeletonData = List.filled( 2, - Game( - name: 'Skeleton Game', + Match( + name: 'Skeleton Match', group: Group( name: 'Skeleton Group', members: [ @@ -42,20 +42,22 @@ class _HomeViewState extends State { initState() { super.initState(); final db = Provider.of(context, listen: false); - _gameCountFuture = db.gameDao.getGameCount(); + _matchCountFuture = db.matchDao.getMatchCount(); _groupCountFuture = db.groupDao.getGroupCount(); - _recentGamesFuture = db.gameDao.getAllGames(); + _recentMatchesFuture = db.matchDao.getAllMatches(); - Future.wait([_gameCountFuture, _groupCountFuture, _recentGamesFuture]).then( - (_) async { - await Future.delayed(const Duration(milliseconds: 250)); - if (mounted) { - setState(() { - isLoading = false; - }); - } - }, - ); + Future.wait([ + _matchCountFuture, + _groupCountFuture, + _recentMatchesFuture, + ]).then((_) async { + await Future.delayed(const Duration(milliseconds: 250)); + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); } @override @@ -72,7 +74,7 @@ class _HomeViewState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ FutureBuilder( - future: _gameCountFuture, + future: _matchCountFuture, builder: (context, snapshot) { final int count = (snapshot.hasData) ? snapshot.data! @@ -115,11 +117,11 @@ class _HomeViewState extends State { content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: FutureBuilder( - future: _recentGamesFuture, + future: _recentMatchesFuture, builder: ( BuildContext context, - AsyncSnapshot> snapshot, + AsyncSnapshot> snapshot, ) { if (snapshot.hasError) { return const Center( @@ -129,7 +131,7 @@ class _HomeViewState extends State { ), ); } - final List games = + final List matches = (isLoading ? skeletonData : (snapshot.data ?? []) @@ -140,19 +142,19 @@ class _HomeViewState extends State { )) .take(2) .toList(); - if (games.isNotEmpty) { + if (matches.isNotEmpty) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - GameTile( - gameTitle: games[0].name, - gameType: 'Winner', + MatchTile( + matchTitle: matches[0].name, + game: 'Winner', ruleset: 'Ruleset', - players: _getPlayerText(games[0]), - winner: games[0].winner == null + players: _getPlayerText(matches[0]), + winner: matches[0].winner == null ? 'Game in progress...' - : games[0].winner!.name, + : matches[0].winner!.name, ), const Padding( padding: EdgeInsets.symmetric( @@ -160,15 +162,15 @@ class _HomeViewState extends State { ), child: Divider(), ), - if (games.length > 1) ...[ - GameTile( - gameTitle: games[1].name, - gameType: 'Winner', + if (matches.length > 1) ...[ + MatchTile( + matchTitle: matches[1].name, + game: 'Winner', ruleset: 'Ruleset', - players: _getPlayerText(games[1]), - winner: games[1].winner == null + players: _getPlayerText(matches[1]), + winner: matches[1].winner == null ? 'Game in progress...' - : games[1].winner!.name, + : matches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ @@ -249,7 +251,7 @@ class _HomeViewState extends State { ); } - String _getPlayerText(Game game) { + String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; return '$playerCount Players'; diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_game_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_group_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart similarity index 91% rename from lib/presentation/views/main_menu/create_game/create_game_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index eef2de6..03c081c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -3,28 +3,28 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_game_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; -class CreateGameView extends StatefulWidget { +class CreateMatchView extends StatefulWidget { final VoidCallback? onWinnerChanged; - const CreateGameView({super.key, this.onWinnerChanged}); + const CreateMatchView({super.key, this.onWinnerChanged}); @override - State createState() => _CreateGameViewState(); + State createState() => _CreateMatchViewState(); } -class _CreateGameViewState extends State { +class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; @@ -234,20 +234,20 @@ class _CreateGameViewState extends State { buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() ? () async { - Game game = Game( + Match match = Match( name: _gameNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, ); - await db.gameDao.addGame(game: game); + await db.matchDao.addMatch(match: match); if (context.mounted) { Navigator.pushReplacement( context, CupertinoPageRoute( fullscreenDialog: true, builder: (context) => GameResultView( - game: game, + match: match, onWinnerChanged: widget.onWinnerChanged, ), ), diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart similarity index 90% rename from lib/presentation/views/main_menu/game_result_view.dart rename to lib/presentation/views/main_menu/match_view/match_result_view.dart index 6e60410..58ff9ce 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { - final Game game; + final Match match; final VoidCallback? onWinnerChanged; - const GameResultView({super.key, required this.game, this.onWinnerChanged}); + const GameResultView({super.key, required this.match, this.onWinnerChanged}); @override State createState() => _GameResultViewState(); } @@ -24,10 +24,10 @@ class _GameResultViewState extends State { @override void initState() { db = Provider.of(context, listen: false); - allPlayers = getAllPlayers(widget.game); - if (widget.game.winner != null) { + allPlayers = getAllPlayers(widget.match); + if (widget.match.winner != null) { _selectedPlayer = allPlayers.firstWhere( - (p) => p.id == widget.game.winner!.id, + (p) => p.id == widget.match.winner!.id, ); } super.initState(); @@ -41,7 +41,7 @@ class _GameResultViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - widget.game.name, + widget.match.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -125,17 +125,17 @@ class _GameResultViewState extends State { Future _handleWinnerSaving() async { if (_selectedPlayer == null) { - await db.gameDao.removeWinner(gameId: widget.game.id); + await db.matchDao.removeWinner(matchId: widget.match.id); } else { - await db.gameDao.setWinner( - gameId: widget.game.id, + await db.matchDao.setWinner( + matchId: widget.match.id, winnerId: _selectedPlayer!.id, ); } widget.onWinnerChanged?.call(); } - List getAllPlayers(Game game) { + List getAllPlayers(Match game) { if (game.group == null && game.players != null) { return [...game.players!]; } else if (game.group != null && game.players != null) { diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart similarity index 79% rename from lib/presentation/views/main_menu/game_history_view.dart rename to lib/presentation/views/main_menu/match_view/match_view.dart index 46cb1db..92fd268 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,32 +1,34 @@ +import 'dart:core' hide Match; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -class GameHistoryView extends StatefulWidget { - const GameHistoryView({super.key}); +class MatchView extends StatefulWidget { + const MatchView({super.key}); @override - State createState() => _GameHistoryViewState(); + State createState() => _MatchViewState(); } -class _GameHistoryViewState extends State { - late Future> _gameListFuture; +class _MatchViewState extends State { + late Future> _gameListFuture; late final AppDatabase db; - late final List skeletonData = List.filled( + late final List skeletonData = List.filled( 4, - Game( + Match( name: 'Skeleton Gamename', group: Group( name: 'Groupname', @@ -43,7 +45,7 @@ class _GameHistoryViewState extends State { db = Provider.of(context, listen: false); _gameListFuture = Future.delayed( const Duration(milliseconds: 250), - () => db.gameDao.getAllGames(), + () => db.matchDao.getAllMatches(), ); } @@ -54,10 +56,10 @@ class _GameHistoryViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( + FutureBuilder>( future: _gameListFuture, builder: - (BuildContext context, AsyncSnapshot> snapshot) { + (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return const Center( child: TopCenteredMessage( @@ -79,7 +81,7 @@ class _GameHistoryViewState extends State { } final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List games = + final List matches = (isLoading ? skeletonData : (snapshot.data ?? []) ..sort( (a, b) => b.createdAt.compareTo(a.createdAt), @@ -89,9 +91,9 @@ class _GameHistoryViewState extends State { enabled: isLoading, child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, + itemCount: matches.length + 1, itemBuilder: (BuildContext context, int index) { - if (index == games.length) { + if (index == matches.length) { return SizedBox( height: MediaQuery.paddingOf(context).bottom - 80, ); @@ -103,13 +105,13 @@ class _GameHistoryViewState extends State { CupertinoPageRoute( fullscreenDialog: true, builder: (context) => GameResultView( - game: games[index], + match: matches[index], onWinnerChanged: refreshGameList, ), ), ); }, - game: games[index], + match: matches[index], ); }, ), @@ -126,7 +128,7 @@ class _GameHistoryViewState extends State { context, MaterialPageRoute( builder: (context) => - CreateGameView(onWinnerChanged: refreshGameList), + CreateMatchView(onWinnerChanged: refreshGameList), ), ); }, @@ -139,7 +141,7 @@ class _GameHistoryViewState extends State { void refreshGameList() { setState(() { - _gameListFuture = db.gameDao.getAllGames(); + _gameListFuture = db.matchDao.getAllMatches(); }); } } diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 564d0d5..17376b2 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,6 +1,6 @@ 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/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; @@ -14,10 +14,10 @@ class StatisticsView extends StatefulWidget { } class _StatisticsViewState extends State { - late Future> _gamesFuture; + late Future> _matchesFuture; late Future> _playersFuture; List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); - List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); bool isLoading = true; @@ -25,16 +25,16 @@ class _StatisticsViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - _gamesFuture = db.gameDao.getAllGames(); + _matchesFuture = db.matchDao.getAllMatches(); _playersFuture = db.playerDao.getAllPlayers(); - Future.wait([_gamesFuture, _playersFuture]).then((results) async { + Future.wait([_matchesFuture, _playersFuture]).then((results) async { await Future.delayed(const Duration(milliseconds: 250)); - final games = results[0] as List; + final matches = results[0] as List; final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(games, players); - gameCounts = _calculateGameAmountsForAllPlayers(games, players); - winRates = computeWinRatePercent(wins: winCounts, games: gameCounts); + winCounts = _calculateWinsForAllPlayers(matches, players); + matchCounts = _calculateGameAmountsForAllPlayers(matches, players); + winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); if (mounted) { setState(() { isLoading = false; @@ -78,9 +78,9 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Games per Player', + title: 'Match per Player', width: constraints.maxWidth * 0.95, - values: gameCounts, + values: matchCounts, itemCount: 10, barColor: Colors.green, ), @@ -97,7 +97,7 @@ class _StatisticsViewState extends State { /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) List<(String, int)> _calculateWinsForAllPlayers( - List games, + List games, List players, ) { List<(String, int)> winCounts = []; @@ -141,39 +141,39 @@ class _StatisticsViewState extends State { return winCounts; } - /// Calculates the number of games played for each player - /// and returns a sorted list of tuples (playerName, gameCount) + /// Calculates the number of matches played for each player + /// and returns a sorted list of tuples (playerName, matchCount) List<(String, int)> _calculateGameAmountsForAllPlayers( - List games, + List matches, List players, ) { - List<(String, int)> gameCounts = []; + List<(String, int)> matchCounts = []; - // Counting games for each player - for (var game in games) { - if (game.group != null) { - final members = game.group!.members.map((p) => p.id).toList(); + // Counting matches for each player + for (var match in matches) { + if (match.group != null) { + final members = match.group!.members.map((p) => p.id).toList(); for (var playerId in members) { - final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); // -1 means player not found in gameCounts if (index != -1) { - final current = gameCounts[index].$2; - gameCounts[index] = (playerId, current + 1); + final current = matchCounts[index].$2; + matchCounts[index] = (playerId, current + 1); } else { - gameCounts.add((playerId, 1)); + matchCounts.add((playerId, 1)); } } } - if (game.players != null) { - final members = game.players!.map((p) => p.id).toList(); + if (match.players != null) { + final members = match.players!.map((p) => p.id).toList(); for (var playerId in members) { - final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); // -1 means player not found in gameCounts if (index != -1) { - final current = gameCounts[index].$2; - gameCounts[index] = (playerId, current + 1); + final current = matchCounts[index].$2; + matchCounts[index] = (playerId, current + 1); } else { - gameCounts.add((playerId, 1)); + matchCounts.add((playerId, 1)); } } } @@ -181,35 +181,35 @@ class _StatisticsViewState extends State { // Adding all players with zero games for (var player in players) { - final index = gameCounts.indexWhere((entry) => entry.$1 == player.id); + final index = matchCounts.indexWhere((entry) => entry.$1 == player.id); // -1 means player not found in gameCounts if (index == -1) { - gameCounts.add((player.id, 0)); + matchCounts.add((player.id, 0)); } } // Replace player IDs with names - for (int i = 0; i < gameCounts.length; i++) { - final playerId = gameCounts[i].$1; + for (int i = 0; i < matchCounts.length; i++) { + final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, orElse: () => Player(id: playerId, name: 'N.a.'), ); - gameCounts[i] = (player.name, gameCounts[i].$2); + matchCounts[i] = (player.name, matchCounts[i].$2); } - gameCounts.sort((a, b) => b.$2.compareTo(a.$2)); + matchCounts.sort((a, b) => b.$2.compareTo(a.$2)); - return gameCounts; + return matchCounts; } // dart List<(String, double)> computeWinRatePercent({ required List<(String, int)> wins, - required List<(String, int)> games, + required List<(String, int)> matches, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; - final Map gamesMap = {for (var e in games) e.$1: e.$2}; + final Map gamesMap = {for (var e in matches) e.$1: e.$2}; // Get all unique player names final names = {...winsMap.keys, ...gamesMap.keys}; diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 83da859..f79edc3 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { - final Game game; + final Match match; final VoidCallback onTap; - const GameHistoryTile({super.key, required this.game, required this.onTap}); + const GameHistoryTile({super.key, required this.match, required this.onTap}); @override State createState() => _GameHistoryTileState(); @@ -17,8 +17,8 @@ class GameHistoryTile extends StatefulWidget { class _GameHistoryTileState extends State { @override Widget build(BuildContext context) { - final group = widget.game.group; - final winner = widget.game.winner; + final group = widget.match.group; + final winner = widget.match.winner; final allPlayers = _getAllPlayers(); return GestureDetector( @@ -39,7 +39,7 @@ class _GameHistoryTileState extends State { children: [ Expanded( child: Text( - widget.game.name, + widget.match.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -48,7 +48,7 @@ class _GameHistoryTileState extends State { ), ), Text( - _formatDate(widget.game.createdAt), + _formatDate(widget.match.createdAt), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -156,8 +156,8 @@ class _GameHistoryTileState extends State { final playerIds = {}; // Add players from game.players - if (widget.game.players != null) { - for (var player in widget.game.players!) { + if (widget.match.players != null) { + for (var player in widget.match.players!) { if (!playerIds.contains(player.id)) { allPlayers.add(player); playerIds.add(player.id); @@ -166,8 +166,8 @@ class _GameHistoryTileState extends State { } // Add players from game.group.players - if (widget.game.group?.members != null) { - for (var player in widget.game.group!.members) { + if (widget.match.group?.members != null) { + for (var player in widget.match.group!.members) { if (!playerIds.contains(player.id)) { allPlayers.add(player); playerIds.add(player.id); diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart index fcd2f65..f98f425 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/game_tile.dart @@ -2,27 +2,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class GameTile extends StatefulWidget { - final String gameTitle; - final String gameType; +class MatchTile extends StatefulWidget { + final String matchTitle; + final String game; final String ruleset; final String players; final String winner; - const GameTile({ + const MatchTile({ super.key, - required this.gameTitle, - required this.gameType, + required this.matchTitle, + required this.game, required this.ruleset, required this.players, required this.winner, }); @override - State createState() => _GameTileState(); + State createState() => _MatchTileState(); } -class _GameTileState extends State { +class _MatchTileState extends State { @override Widget build(BuildContext context) { return Column( @@ -31,12 +31,12 @@ class _GameTileState extends State { Row( children: [ Text( - widget.gameTitle, + widget.matchTitle, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(width: 5), Text( - widget.gameType, + widget.game, style: const TextStyle(fontSize: 14, color: Colors.grey), ), ], diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 6e3b9b2..582cf66 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -40,11 +40,11 @@ class StatisticsTile extends StatelessWidget { child: Column( children: List.generate(min(values.length, itemCount), (index) { /// The maximum wins among all players - final maxGames = values.isNotEmpty ? values[0].$2 : 0; + final maxMatches = values.isNotEmpty ? values[0].$2 : 0; /// Fraction of wins - final double fraction = (maxGames > 0) - ? (values[index].$2 / maxGames) + final double fraction = (maxMatches > 0) + ? (values[index].$2 / maxMatches) : 0.0; /// Calculated width for current the bar diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index f040b0a..957d8fd 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; @@ -16,7 +16,7 @@ class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { final db = Provider.of(context, listen: false); - await db.gameDao.deleteAllGames(); + await db.matchDao.deleteAllMatches(); await db.groupDao.deleteAllGroups(); await db.playerDao.deleteAllPlayers(); } @@ -25,13 +25,13 @@ class DataTransferService { /// Returns the JSON string representation of the data. static Future getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); - final games = await db.gameDao.getAllGames(); + final matches = await db.matchDao.getAllMatches(); final groups = await db.groupDao.getAllGroups(); final players = await db.playerDao.getAllPlayers(); // Construct a JSON representation of the data final Map jsonMap = { - 'games': games.map((game) => game.toJson()).toList(), + 'matches': matches.map((match) => match.toJson()).toList(), 'groups': groups.map((group) => group.toJson()).toList(), 'players': players.map((player) => player.toJson()).toList(), }; @@ -89,14 +89,15 @@ class DataTransferService { final Map jsonData = json.decode(jsonString) as Map; - final List? gamesJson = jsonData['games'] as List?; + final List? matchesJson = + jsonData['matches'] as List?; final List? groupsJson = jsonData['groups'] as List?; final List? playersJson = jsonData['players'] as List?; - final List importedGames = - gamesJson - ?.map((g) => Game.fromJson(g as Map)) + final List importedMatches = + matchesJson + ?.map((g) => Match.fromJson(g as Map)) .toList() ?? []; final List importedGroups = @@ -112,7 +113,7 @@ class DataTransferService { await db.playerDao.addPlayersAsList(players: importedPlayers); await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.gameDao.addGamesAsList(games: importedGames); + await db.matchDao.addMatchAsList(matches: importedMatches); } else { return ImportResult.invalidSchema; } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 4a5cc32..ca86a60 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,10 +16,10 @@ void main() { late Player testPlayer5; late Group testGroup1; late Group testGroup2; - late Game testGame1; - late Game testGame2; - late Game testGameOnlyPlayers; - late Game testGameOnlyGroup; + late Match testMatch1; + late Match testMatch2; + late Match testMatchOnlyPlayers; + late Match testMatchOnlyGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -46,46 +46,51 @@ void main() { name: 'Test Group 2', members: [testPlayer4, testPlayer5], ); - testGame1 = Game( - name: 'First Test Game', + testMatch1 = Match( + name: 'First Test Match', group: testGroup1, players: [testPlayer4, testPlayer5], winner: testPlayer4, ); - testGame2 = Game( - name: 'Second Test Game', + testMatch2 = Match( + name: 'Second Test Match', group: testGroup2, players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer2, ); - testGameOnlyPlayers = Game( - name: 'Test Game with Players', + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer3, ); - testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + group: testGroup2, + ); }); }); tearDown(() async { await database.close(); }); - group('Game Tests', () { - test('Adding and fetching single game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + group('Match Tests', () { + test('Adding and fetching single match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final result = await database.gameDao.getGameById(gameId: testGame1.id); + final result = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); - expect(result.id, testGame1.id); - expect(result.name, testGame1.name); - expect(result.createdAt, testGame1.createdAt); + expect(result.id, testMatch1.id); + expect(result.name, testMatch1.name); + expect(result.createdAt, testMatch1.createdAt); - if (result.winner != null && testGame1.winner != null) { - expect(result.winner!.id, testGame1.winner!.id); - expect(result.winner!.name, testGame1.winner!.name); - expect(result.winner!.createdAt, testGame1.winner!.createdAt); + if (result.winner != null && testMatch1.winner != null) { + expect(result.winner!.id, testMatch1.winner!.id); + expect(result.winner!.name, testMatch1.winner!.name); + expect(result.winner!.createdAt, testMatch1.winner!.createdAt); } else { - expect(result.winner, testGame1.winner); + expect(result.winner, testMatch1.winner); } if (result.group != null) { @@ -99,188 +104,203 @@ void main() { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testGame1.players!.length); + expect(result.players!.length, testMatch1.players!.length); - for (int i = 0; i < testGame1.players!.length; i++) { - expect(result.players![i].id, testGame1.players![i].id); - expect(result.players![i].name, testGame1.players![i].name); - expect(result.players![i].createdAt, testGame1.players![i].createdAt); + for (int i = 0; i < testMatch1.players!.length; i++) { + expect(result.players![i].id, testMatch1.players![i].id); + expect(result.players![i].name, testMatch1.players![i].name); + expect( + result.players![i].createdAt, + testMatch1.players![i].createdAt, + ); } } else { fail('Players is null'); } }); - test('Adding and fetching multiple games works correctly', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], + test('Adding and fetching multiple matches works correctly', () async { + await database.matchDao.addMatchAsList( + matches: [ + testMatch1, + testMatch2, + testMatchOnlyGroup, + testMatchOnlyPlayers, + ], ); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 4); + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches.length, 4); - final testGames = { - testGame1.id: testGame1, - testGame2.id: testGame2, - testGameOnlyGroup.id: testGameOnlyGroup, - testGameOnlyPlayers.id: testGameOnlyPlayers, + final testMatches = { + testMatch1.id: testMatch1, + testMatch2.id: testMatch2, + testMatchOnlyGroup.id: testMatchOnlyGroup, + testMatchOnlyPlayers.id: testMatchOnlyPlayers, }; - for (final game in allGames) { - final testGame = testGames[game.id]!; + for (final match in allMatches) { + final testMatch = testMatches[match.id]!; - // Game-Checks - expect(game.id, testGame.id); - expect(game.name, testGame.name); - expect(game.createdAt, testGame.createdAt); - if (game.winner != null && testGame.winner != null) { - expect(game.winner!.id, testGame.winner!.id); - expect(game.winner!.name, testGame.winner!.name); - expect(game.winner!.createdAt, testGame.winner!.createdAt); + // Match-Checks + expect(match.id, testMatch.id); + expect(match.name, testMatch.name); + expect(match.createdAt, testMatch.createdAt); + if (match.winner != null && testMatch.winner != null) { + expect(match.winner!.id, testMatch.winner!.id); + expect(match.winner!.name, testMatch.winner!.name); + expect(match.winner!.createdAt, testMatch.winner!.createdAt); } else { - expect(game.winner, testGame.winner); + expect(match.winner, testMatch.winner); } // Group-Checks - if (testGame.group != null) { - expect(game.group!.id, testGame.group!.id); - expect(game.group!.name, testGame.group!.name); - expect(game.group!.createdAt, testGame.group!.createdAt); + if (testMatch.group != null) { + expect(match.group!.id, testMatch.group!.id); + expect(match.group!.name, testMatch.group!.name); + expect(match.group!.createdAt, testMatch.group!.createdAt); // Group Members-Checks - expect(game.group!.members.length, testGame.group!.members.length); - for (int i = 0; i < testGame.group!.members.length; i++) { - expect(game.group!.members[i].id, testGame.group!.members[i].id); + expect(match.group!.members.length, testMatch.group!.members.length); + for (int i = 0; i < testMatch.group!.members.length; i++) { + expect(match.group!.members[i].id, testMatch.group!.members[i].id); expect( - game.group!.members[i].name, - testGame.group!.members[i].name, + match.group!.members[i].name, + testMatch.group!.members[i].name, ); expect( - game.group!.members[i].createdAt, - testGame.group!.members[i].createdAt, + match.group!.members[i].createdAt, + testMatch.group!.members[i].createdAt, ); } } else { - expect(game.group, null); + expect(match.group, null); } // Players-Checks - if (testGame.players != null) { - expect(game.players!.length, testGame.players!.length); - for (int i = 0; i < testGame.players!.length; i++) { - expect(game.players![i].id, testGame.players![i].id); - expect(game.players![i].name, testGame.players![i].name); - expect(game.players![i].createdAt, testGame.players![i].createdAt); + if (testMatch.players != null) { + expect(match.players!.length, testMatch.players!.length); + for (int i = 0; i < testMatch.players!.length; i++) { + expect(match.players![i].id, testMatch.players![i].id); + expect(match.players![i].name, testMatch.players![i].name); + expect( + match.players![i].createdAt, + testMatch.players![i].createdAt, + ); } } else { - expect(game.players, null); + expect(match.players, null); } } }); - test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame1); + test('Adding the same match twice does not create duplicates', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: testMatch1); - final gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + final matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); }); - test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(gameExists, false); - - await database.gameDao.addGame(game: testGame1); - - gameExists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(gameExists, true); - }); - - test('Deleting a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); - - final gameDeleted = await database.gameDao.deleteGame( - gameId: testGame1.id, + test('Match existence check works correctly', () async { + var matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, ); - expect(gameDeleted, true); + expect(matchExists, false); - final gameExists = await database.gameDao.gameExists( - gameId: testGame1.id, + await database.matchDao.addMatch(match: testMatch1); + + matchExists = await database.matchDao.matchExists(matchId: testMatch1.id); + expect(matchExists, true); + }); + + test('Deleting a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + final matchDeleted = await database.matchDao.deleteMatch( + matchId: testMatch1.id, ); - expect(gameExists, false); + expect(matchDeleted, true); + + final matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, + ); + expect(matchExists, false); }); - test('Getting the game count works correctly', () async { - var gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 0); + test('Getting the match count works correctly', () async { + var matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); - await database.gameDao.addGame(game: testGame1); + await database.matchDao.addMatch(match: testMatch1); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); - await database.gameDao.addGame(game: testGame2); + await database.matchDao.addMatch(match: testMatch2); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 2); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 2); - await database.gameDao.deleteGame(gameId: testGame1.id); + await database.matchDao.deleteMatch(matchId: testMatch1.id); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); - await database.gameDao.deleteGame(gameId: testGame2.id); + await database.matchDao.deleteMatch(matchId: testMatch2.id); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 0); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); }); - test('Checking if game has winner works correclty', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGameOnlyGroup); + test('Checking if match has winner works correclty', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: testMatchOnlyGroup); - var hasWinner = await database.gameDao.hasWinner(gameId: testGame1.id); + var hasWinner = await database.matchDao.hasWinner(matchId: testMatch1.id); expect(hasWinner, true); - hasWinner = await database.gameDao.hasWinner( - gameId: testGameOnlyGroup.id, + hasWinner = await database.matchDao.hasWinner( + matchId: testMatchOnlyGroup.id, ); expect(hasWinner, false); }); - test('Fetching the winner of a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Fetching the winner of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final winner = await database.gameDao.getWinner(gameId: testGame1.id); + final winner = await database.matchDao.getWinner(matchId: testMatch1.id); if (winner == null) { fail('Winner is null'); } else { - expect(winner.id, testGame1.winner!.id); - expect(winner.name, testGame1.winner!.name); - expect(winner.createdAt, testGame1.winner!.createdAt); + expect(winner.id, testMatch1.winner!.id); + expect(winner.name, testMatch1.winner!.name); + expect(winner.createdAt, testMatch1.winner!.createdAt); } }); - test('Updating the winner of a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Updating the winner of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final winner = await database.gameDao.getWinner(gameId: testGame1.id); + final winner = await database.matchDao.getWinner(matchId: testMatch1.id); if (winner == null) { fail('Winner is null'); } else { - expect(winner.id, testGame1.winner!.id); - expect(winner.name, testGame1.winner!.name); - expect(winner.createdAt, testGame1.winner!.createdAt); + expect(winner.id, testMatch1.winner!.id); + expect(winner.name, testMatch1.winner!.name); + expect(winner.createdAt, testMatch1.winner!.createdAt); expect(winner.id, testPlayer4.id); expect(winner.id != testPlayer5.id, true); } - await database.gameDao.setWinner( - gameId: testGame1.id, + await database.matchDao.setWinner( + matchId: testMatch1.id, winnerId: testPlayer5.id, ); - final newWinner = await database.gameDao.getWinner(gameId: testGame1.id); + final newWinner = await database.matchDao.getWinner( + matchId: testMatch1.id, + ); if (newWinner == null) { fail('New winner is null'); @@ -292,39 +312,41 @@ void main() { }); test('Removing a winner works correctly', () async { - await database.gameDao.addGame(game: testGame2); + await database.matchDao.addMatch(match: testMatch2); - var hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + var hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); expect(hasWinner, true); - await database.gameDao.removeWinner(gameId: testGame2.id); + await database.matchDao.removeWinner(matchId: testMatch2.id); - hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); expect(hasWinner, false); - final removedWinner = await database.gameDao.getWinner( - gameId: testGame2.id, + final removedWinner = await database.matchDao.getWinner( + matchId: testMatch2.id, ); expect(removedWinner, null); }); - test('Renaming a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Renaming a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - var fetchedGame = await database.gameDao.getGameById( - gameId: testGame1.id, + var fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, ); - expect(fetchedGame.name, testGame1.name); + expect(fetchedMatch.name, testMatch1.name); - const newName = 'Updated Game Name'; - await database.gameDao.updateGameName( - gameId: testGame1.id, + const newName = 'Updated Match Name'; + await database.matchDao.updateMatchName( + matchId: testMatch1.id, newName: newName, ); - fetchedGame = await database.gameDao.getGameById(gameId: testGame1.id); - expect(fetchedGame.name, newName); + fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.name, newName); }); }); } diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 1e9b8fc..a4fa146 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,8 +16,8 @@ void main() { late Player testPlayer5; late Group testGroup1; late Group testGroup2; - late Game testgameWithGroup; - late Game testgameWithPlayers; + late Match testMatchWithGroup; + late Match testMatchWithPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -44,81 +44,84 @@ void main() { name: 'Test Group', members: [testPlayer3, testPlayer2], ); - testgameWithPlayers = Game( - name: 'Test Game with Players', + testMatchWithPlayers = Match( + name: 'Test Match with Players', players: [testPlayer4, testPlayer5], ); - testgameWithGroup = Game(name: 'Test Game with Group', group: testGroup1); + testMatchWithGroup = Match( + name: 'Test Match with Group', + group: testGroup1, + ); }); }); tearDown(() async { await database.close(); }); - group('Group-Game Tests', () { - test('Game has group works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + group('Group-Match Tests', () { + test('matchHasGroup() has group works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithPlayers); await database.groupDao.addGroup(group: testGroup1); - var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgameWithPlayers.id, + var matchHasGroup = await database.groupMatchDao.matchHasGroup( + matchId: testMatchWithPlayers.id, ); - expect(gameHasGroup, false); + expect(matchHasGroup, false); - await database.groupGameDao.addGroupToGame( - gameId: testgameWithPlayers.id, + await database.groupMatchDao.addGroupToMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); - gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgameWithPlayers.id, + matchHasGroup = await database.groupMatchDao.matchHasGroup( + matchId: testMatchWithPlayers.id, ); - expect(gameHasGroup, true); + expect(matchHasGroup, true); }); - test('Adding a group to a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + test('Adding a group to a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithPlayers); await database.groupDao.addGroup(group: testGroup1); - await database.groupGameDao.addGroupToGame( - gameId: testgameWithPlayers.id, + await database.groupMatchDao.addGroupToMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); - var groupAdded = await database.groupGameDao.isGroupInGame( - gameId: testgameWithPlayers.id, + var groupAdded = await database.groupMatchDao.isGroupInMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); expect(groupAdded, true); - groupAdded = await database.groupGameDao.isGroupInGame( - gameId: testgameWithPlayers.id, + groupAdded = await database.groupMatchDao.isGroupInMatch( + matchId: testMatchWithPlayers.id, groupId: '', ); expect(groupAdded, false); }); - test('Removing group from game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); + test('Removing group from match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); - final groupToRemove = testgameWithGroup.group!; + final groupToRemove = testMatchWithGroup.group!; - final removed = await database.groupGameDao.removeGroupFromGame( + final removed = await database.groupMatchDao.removeGroupFromMatch( groupId: groupToRemove.id, - gameId: testgameWithGroup.id, + matchId: testMatchWithGroup.id, ); expect(removed, true); - final result = await database.gameDao.getGameById( - gameId: testgameWithGroup.id, + final result = await database.matchDao.getMatchById( + matchId: testMatchWithGroup.id, ); expect(result.group, null); }); - test('Retrieving group of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); - final group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + test('Retrieving group of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); + final group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { @@ -136,11 +139,11 @@ void main() { } }); - test('Updating the group of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); + test('Updating the group of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); - var group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + var group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { @@ -153,13 +156,13 @@ void main() { } await database.groupDao.addGroup(group: testGroup2); - await database.groupGameDao.updateGroupOfGame( - gameId: testgameWithGroup.id, + await database.groupMatchDao.updateGroupOfMatch( + matchId: testMatchWithGroup.id, newGroupId: testGroup2.id, ); - group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 4e2616e..e2501eb 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,8 +16,8 @@ void main() { late Player testPlayer5; late Player testPlayer6; late Group testgroup; - late Game testGameOnlyGroup; - late Game testGameOnlyPlayers; + late Match testMatchOnlyGroup; + late Match testMatchOnlyPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -41,9 +41,12 @@ void main() { name: 'Test Group', members: [testPlayer1, testPlayer2, testPlayer3], ); - testGameOnlyGroup = Game(name: 'Test Game with Group', group: testgroup); - testGameOnlyPlayers = Game( - name: 'Test Game with Players', + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + group: testgroup, + ); + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', players: [testPlayer4, testPlayer5, testPlayer6], ); }); @@ -52,67 +55,67 @@ void main() { await database.close(); }); - group('Player-Game Tests', () { - test('Game has player works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyGroup); + group('Player-Match Tests', () { + test('Match has player works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer1); - var gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testGameOnlyGroup.id, + var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, ); - expect(gameHasPlayers, false); + expect(matchHasPlayers, false); - await database.playerGameDao.addPlayerToGame( - gameId: testGameOnlyGroup.id, + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer1.id, ); - gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testGameOnlyGroup.id, + matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, ); - expect(gameHasPlayers, true); + expect(matchHasPlayers, true); }); - test('Adding a player to a game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyGroup); + test('Adding a player to a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer5); - await database.playerGameDao.addPlayerToGame( - gameId: testGameOnlyGroup.id, + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer5.id, ); - var playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testGameOnlyGroup.id, + var playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer5.id, ); expect(playerAdded, true); - playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testGameOnlyGroup.id, + playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, playerId: '', ); expect(playerAdded, false); }); - test('Removing player from game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); + test('Removing player from match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final playerToRemove = testGameOnlyPlayers.players![0]; + final playerToRemove = testMatchOnlyPlayers.players![0]; - final removed = await database.playerGameDao.removePlayerFromGame( + final removed = await database.playerMatchDao.removePlayerFromMatch( playerId: playerToRemove.id, - gameId: testGameOnlyPlayers.id, + matchId: testMatchOnlyPlayers.id, ); expect(removed, true); - final result = await database.gameDao.getGameById( - gameId: testGameOnlyPlayers.id, + final result = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, ); - expect(result.players!.length, testGameOnlyPlayers.players!.length - 1); + expect(result.players!.length, testMatchOnlyPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, @@ -120,10 +123,10 @@ void main() { expect(playerExists, false); }); - test('Retrieving players of a game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); - final players = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + test('Retrieving players of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (players == null) { @@ -131,34 +134,37 @@ void main() { } for (int i = 0; i < players.length; i++) { - expect(players[i].id, testGameOnlyPlayers.players![i].id); - expect(players[i].name, testGameOnlyPlayers.players![i].name); - expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); + expect(players[i].id, testMatchOnlyPlayers.players![i].id); + expect(players[i].name, testMatchOnlyPlayers.players![i].name); + expect( + players[i].createdAt, + testMatchOnlyPlayers.players![i].createdAt, + ); } }); - test('Updating the games players works coreclty', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); + test('Updating the match players works coreclty', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; await database.playerDao.addPlayersAsList(players: newPlayers); // First, remove all existing players - final existingPlayers = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (existingPlayers == null || existingPlayers.isEmpty) { fail('Existing players should not be null or empty'); } - await database.playerGameDao.updatePlayersFromGame( - gameId: testGameOnlyPlayers.id, + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, newPlayer: newPlayers, ); - final updatedPlayers = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (updatedPlayers == null) { -- 2.49.1 From c4b249b19961cdd9a3dca86676d5b4e0caf1ddb5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:11:02 +0100 Subject: [PATCH 438/563] Added missing game instants --- lib/data/dao/player_match_dao.dart | 2 +- .../views/main_menu/home_view.dart | 6 ++--- .../views/main_menu/statistics_view.dart | 24 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index f42b8bb..47c7b2d 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -66,7 +66,7 @@ class PlayerMatchDao extends DatabaseAccessor return (count ?? 0) > 0; } - /// Removes the association of a player with a game by deleting the record + /// Removes the association of a player with a match by deleting the record /// from the [PlayerMatchTable]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future removePlayerFromMatch({ diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index c6322d8..d312516 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -82,7 +82,7 @@ class _HomeViewState extends State { return QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Games', + title: 'Matches', icon: Icons.groups_rounded, value: count, ); @@ -112,7 +112,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: 'Recent Games', + title: 'Recent Matches', icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -127,7 +127,7 @@ class _HomeViewState extends State { return const Center( heightFactor: 4, child: Text( - 'Error while loading recent games.', + 'Error while loading recent matches.', ), ); } diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 17376b2..6104e39 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -33,7 +33,7 @@ class _StatisticsViewState extends State { final matches = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(matches, players); - matchCounts = _calculateGameAmountsForAllPlayers(matches, players); + matchCounts = _calculateMatchAmountsForAllPlayers(matches, players); winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); if (mounted) { setState(() { @@ -97,14 +97,14 @@ class _StatisticsViewState extends State { /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) List<(String, int)> _calculateWinsForAllPlayers( - List games, + List matches, List players, ) { List<(String, int)> winCounts = []; // Getting the winners - for (var game in games) { - final winner = game.winner; + for (var match in matches) { + final winner = match.winner; if (winner != null) { final index = winCounts.indexWhere((entry) => entry.$1 == winner.id); // -1 means winner not found in winCounts @@ -143,7 +143,7 @@ class _StatisticsViewState extends State { /// Calculates the number of matches played for each player /// and returns a sorted list of tuples (playerName, matchCount) - List<(String, int)> _calculateGameAmountsForAllPlayers( + List<(String, int)> _calculateMatchAmountsForAllPlayers( List matches, List players, ) { @@ -155,7 +155,7 @@ class _StatisticsViewState extends State { final members = match.group!.members.map((p) => p.id).toList(); for (var playerId in members) { final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index != -1) { final current = matchCounts[index].$2; matchCounts[index] = (playerId, current + 1); @@ -168,7 +168,7 @@ class _StatisticsViewState extends State { final members = match.players!.map((p) => p.id).toList(); for (var playerId in members) { final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index != -1) { final current = matchCounts[index].$2; matchCounts[index] = (playerId, current + 1); @@ -179,10 +179,10 @@ class _StatisticsViewState extends State { } } - // Adding all players with zero games + // Adding all players with zero matches for (var player in players) { final index = matchCounts.indexWhere((entry) => entry.$1 == player.id); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index == -1) { matchCounts.add((player.id, 0)); } @@ -209,15 +209,15 @@ class _StatisticsViewState extends State { required List<(String, int)> matches, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; - final Map gamesMap = {for (var e in matches) e.$1: e.$2}; + final Map matchesMap = {for (var e in matches) e.$1: e.$2}; // Get all unique player names - final names = {...winsMap.keys, ...gamesMap.keys}; + final names = {...winsMap.keys, ...matchesMap.keys}; // Calculate win rates final result = names.map((name) { final int w = winsMap[name] ?? 0; - final int g = gamesMap[name] ?? 0; + final int g = matchesMap[name] ?? 0; // Calculate percentage and round to 2 decimal places // Avoid division by zero final double percent = (g > 0) -- 2.49.1 From 0e5ad1c3d9953464d273080383f894aeaa8e2f1b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:12:21 +0100 Subject: [PATCH 439/563] Another missing --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index d312516..49bfa8f 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -153,7 +153,7 @@ class _HomeViewState extends State { ruleset: 'Ruleset', players: _getPlayerText(matches[0]), winner: matches[0].winner == null - ? 'Game in progress...' + ? 'Match in progress...' : matches[0].winner!.name, ), const Padding( -- 2.49.1 From d578a7f9bd982091d0dc1262afa10eadd1758d76 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:12:56 +0100 Subject: [PATCH 440/563] Missing comment --- lib/data/dao/player_match_dao.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 47c7b2d..9e242b7 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -80,9 +80,9 @@ class PlayerMatchDao extends DatabaseAccessor return rowsAffected > 0; } - /// Updates the players associated with a game based on the provided + /// Updates the players associated with a match based on the provided /// [newPlayer] list. It adds new players and removes players that are no - /// longer associated with the game. + /// longer associated with the match. Future updatePlayersFromMatch({ required String matchId, required List newPlayer, -- 2.49.1 From 0eaf3d251bb7bb256121cdf115771f1a5f1a2bee Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:49:58 +0100 Subject: [PATCH 441/563] added constant minimumSkeletonDuration --- lib/core/constants.dart | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lib/core/constants.dart diff --git a/lib/core/constants.dart b/lib/core/constants.dart new file mode 100644 index 0000000..51b4c70 --- /dev/null +++ b/lib/core/constants.dart @@ -0,0 +1,2 @@ +/// Minimum duration of all app skeletons +Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file -- 2.49.1 From d96494f608e483d61b553e239150ce1988e26cd8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:24 +0100 Subject: [PATCH 442/563] Changed futurebuilder logic in groups_view --- .../views/main_menu/group_view/groups_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5fd5e4b..67109e6 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -34,10 +35,10 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.groupDao.getAllGroups(), - ); + _allGroupsFuture = Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override -- 2.49.1 From f05114a99e3ccf686e84b500bd5a89d3b6aef65f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:39 +0100 Subject: [PATCH 443/563] removed futurebuilder logic in groups_view --- .../views/main_menu/home_view.dart | 191 +++++++----------- 1 file changed, 72 insertions(+), 119 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 49bfa8f..31bedf5 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; @@ -18,12 +19,10 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { - late Future _matchCountFuture; - late Future _groupCountFuture; - late Future> _recentMatchesFuture; bool isLoading = true; - - late final List skeletonData = List.filled( + int matchCount = 0; + int groupCount = 0; + List recentMatches = List.filled( 2, Match( name: 'Skeleton Match', @@ -39,19 +38,24 @@ class _HomeViewState extends State { ); @override - initState() { + void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchCountFuture = db.matchDao.getMatchCount(); - _groupCountFuture = db.groupDao.getGroupCount(); - _recentMatchesFuture = db.matchDao.getAllMatches(); Future.wait([ - _matchCountFuture, - _groupCountFuture, - _recentMatchesFuture, - ]).then((_) async { - await Future.delayed(const Duration(milliseconds: 250)); + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + recentMatches = results[2] as List; + + recentMatches = + (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); if (mounted) { setState(() { isLoading = false; @@ -73,38 +77,20 @@ class _HomeViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - FutureBuilder( - future: _matchCountFuture, - builder: (context, snapshot) { - final int count = (snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Matches', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Matches', + icon: Icons.groups_rounded, + value: matchCount, ), SizedBox(width: constraints.maxWidth * 0.05), - FutureBuilder( - future: _groupCountFuture, - builder: (context, snapshot) { - final int count = - (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Groups', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: groupCount, ), ], ), @@ -116,80 +102,48 @@ class _HomeViewState extends State { icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), - child: FutureBuilder( - future: _recentMatchesFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - heightFactor: 4, - child: Text( - 'Error while loading recent matches.', - ), - ); - } - final List matches = - (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo( - a.createdAt, - ), - )) - .take(2) - .toList(); - if (matches.isNotEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MatchTile( - matchTitle: matches[0].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[0]), - winner: matches[0].winner == null - ? 'Match in progress...' - : matches[0].winner!.name, - ), - const Padding( - padding: EdgeInsets.symmetric( - vertical: 8.0, - ), - child: Divider(), - ), - if (matches.length > 1) ...[ - MatchTile( - matchTitle: matches[1].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[1]), - winner: matches[1].winner == null - ? 'Game in progress...' - : matches[1].winner!.name, - ), - const SizedBox(height: 8), - ] else ...[ - const Center( - heightFactor: 4, - child: Text( - 'No second game available.', - ), - ), - ], - ], - ); - } else { - return const Center( - heightFactor: 12, - child: Text('No recent games available.'), - ); - } - }, + child: Visibility( + visible: !isLoading, + replacement: const Center( + heightFactor: 12, + child: Text('No recent games available.'), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MatchTile( + matchTitle: recentMatches[0].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[0]), + winner: recentMatches[0].winner == null + ? 'Match in progress...' + : recentMatches[0].winner!.name, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + if (recentMatches.length > 1) ...[ + MatchTile( + matchTitle: recentMatches[1].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[1]), + winner: recentMatches[1].winner == null + ? 'Game in progress...' + : recentMatches[1].winner!.name, + ), + const SizedBox(height: 8), + ] else ...[ + const Center( + heightFactor: 4, + child: Text('No second game available.'), + ), + ], + ], + ), ), ), ), @@ -199,7 +153,6 @@ class _HomeViewState extends State { title: 'Quick Create', icon: Icons.add_box_rounded, content: Column( - spacing: 8, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, -- 2.49.1 From 76186787e73d976ce13e7ffec44f098461cc92d7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:51:24 +0100 Subject: [PATCH 444/563] changed futurebuilder logic in player selection --- lib/presentation/widgets/player_selection.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e2114b2..96d9d9a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -46,10 +47,10 @@ class _PlayerSelectionState extends State { } void loadPlayerList() { - _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.playerDao.getAllPlayers(), - ); + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { -- 2.49.1 From c8532adfde02960bca203825f1678cb92b70fbdf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:52:02 +0100 Subject: [PATCH 445/563] changed skeleton duration logic in statistics view and refactored --- .../views/main_menu/statistics_view.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6104e39..7df77a7 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -14,8 +15,6 @@ class StatisticsView extends StatefulWidget { } class _StatisticsViewState extends State { - late Future> _matchesFuture; - late Future> _playersFuture; List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); @@ -25,11 +24,12 @@ class _StatisticsViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchesFuture = db.matchDao.getAllMatches(); - _playersFuture = db.playerDao.getAllPlayers(); - Future.wait([_matchesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 250)); + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) async { final matches = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(matches, players); -- 2.49.1 From 24b60bb18b937791f359b37544f30cb15887203b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:46:59 +0100 Subject: [PATCH 446/563] added minimumSkeletonDuration constant and changed future logic --- .../views/main_menu/match_view/match_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 92fd268..7622134 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -2,6 +2,7 @@ import 'dart:core' hide Match; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -43,10 +44,10 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.matchDao.getAllMatches(), - ); + _gameListFuture = Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override -- 2.49.1 From 4f0a1eec6d12c28a178e8eeb8bce8a9d1004e31e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:50:34 +0100 Subject: [PATCH 447/563] refactor --- .../match_view/create_match/create_match_view.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 03c081c..27a1ce9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -28,12 +28,6 @@ class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; - /// Futures to load all groups and players from the database - late Future> _allGroupsFuture; - - /// Future to load all players from the database - late Future> _allPlayersFuture; - /// Controller for the game name input field final TextEditingController _gameNameController = TextEditingController(); @@ -107,10 +101,10 @@ class _CreateMatchViewState extends State { db = Provider.of(context, listen: false); - _allGroupsFuture = db.groupDao.getAllGroups(); - _allPlayersFuture = db.playerDao.getAllPlayers(); - - Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { + Future.wait([ + db.groupDao.getAllGroups(), + db.playerDao.getAllPlayers(), + ]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; }); -- 2.49.1 From df0a5207c2b9b5d8c99612d4c62e7374c1ae249b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 20:01:48 +0100 Subject: [PATCH 448/563] add const --- lib/core/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 51b4c70..075b1ab 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,2 @@ /// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file +Duration minimumSkeletonDuration = const Duration(milliseconds: 250); -- 2.49.1 From 9b3d61e5b0bd06497007d70102767dba6136241d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:36:11 +0100 Subject: [PATCH 449/563] remove futurebuilder from playerselection and refactor --- .../widgets/player_selection.dart | 109 +++++++----------- 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 96d9d9a..916ed37 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -30,6 +30,7 @@ class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; + bool isLoading = true; late final TextEditingController _searchBarController = TextEditingController(); late final AppDatabase db; @@ -43,6 +44,7 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); + suggestedPlayers = skeletonData; loadPlayerList(); } @@ -51,8 +53,8 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { + isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -163,75 +165,44 @@ class _PlayerSelectionState extends State { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - FutureBuilder( - future: _allPlayersFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), + /* + + */ + Expanded( + child: AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: suggestedPlayers.isNotEmpty, + replacement: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: allPlayers.isEmpty + ? 'No players created yet.' + : (selectedPlayers.length == allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + child: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add(suggestedPlayers[index]); + widget.onChanged([...selectedPlayers]); + suggestedPlayers.remove(suggestedPlayers[index]); + } + }); + }, ); - } - bool doneLoading = - snapshot.connectionState == ConnectionState.done; - bool snapshotDataEmpty = - !snapshot.hasData || snapshot.data!.isEmpty; - if (doneLoading && - (snapshotDataEmpty && allPlayers.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - return Expanded( - child: AppSkeleton( - enabled: isLoading, - child: Visibility( - visible: - (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), - replacement: ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index].name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - widget.onChanged([...selectedPlayers]); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); - }, - ), - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ), - ), - ), - ); - }, + }, + ), + ), + ), ), ], ), -- 2.49.1 From 9ad5c4ad6fc7bd97ba91523c7a71be8ec02f4976 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:58:49 +0100 Subject: [PATCH 450/563] remove futurebuilder from groups view and refactor --- .../main_menu/group_view/groups_view.dart | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 67109e6..c2cb6aa 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -19,15 +19,15 @@ class GroupsView extends StatefulWidget { } class _GroupsViewState extends State { - late Future> _allGroupsFuture; late final AppDatabase db; + late List loadedGroups; + bool isLoading = true; - final player = Player(name: 'Skeleton Player'); - late final List skeletonData = List.filled( + List groups = List.filled( 7, Group( - name: 'Skeleton Match', - members: [player, player, player, player, player, player], + name: 'Skeleton Group', + members: List.filled(6, Player(name: 'Skeleton Player')), ), ); @@ -35,10 +35,17 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.wait([ + Future.wait([ db.groupDao.getAllGroups(), Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); } @override @@ -48,50 +55,30 @@ class _GroupsViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _allGroupsFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Group data couldn\'t\nbe loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: groups.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No groups created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: groups.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == groups.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No groups created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List groups = - isLoading ? skeletonData : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: groups.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == groups.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 20, - ); - } - return GroupTile(group: groups[index]); - }, - ), - ); + return GroupTile(group: groups[index]); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -108,7 +95,7 @@ class _GroupsViewState extends State { ), ); setState(() { - _allGroupsFuture = db.groupDao.getAllGroups(); + //_allGroupsFuture = db.groupDao.getAllGroups(); }); }, ), -- 2.49.1 From 06a9c0cd84e8089161d4ca6ed43bd0a2281d7d2c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:59:01 +0100 Subject: [PATCH 451/563] remove futurebuilder from player selection and refactor --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 916ed37..fda5ea7 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -54,7 +54,6 @@ class _PlayerSelectionState extends State { Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); _allPlayersFuture.then((loadedPlayers) { - isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -79,6 +78,7 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); + isLoading = false; }); } -- 2.49.1 From a747d91c5da6d696aa6524e371d36e64ee1c4574 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:16:57 +0100 Subject: [PATCH 452/563] refactor group loading into reusable method `loadGroups` and call it after adding a group --- .../main_menu/group_view/groups_view.dart | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c2cb6aa..5d303d5 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,17 +35,7 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - Future.wait([ - db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) { - loadedGroups = results[0] as List; - setState(() { - groups = loadedGroups - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - }); - isLoading = false; - }); + loadGroups(); } @override @@ -95,7 +85,7 @@ class _GroupsViewState extends State { ), ); setState(() { - //_allGroupsFuture = db.groupDao.getAllGroups(); + loadGroups(); }); }, ), @@ -104,4 +94,18 @@ class _GroupsViewState extends State { ), ); } + + void loadGroups() { + Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); + } } -- 2.49.1 From 7732c6ceb9a67b011b44cc279f16f975c4c6ce6b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:17:27 +0100 Subject: [PATCH 453/563] remove futurebuilder from match view and refactor --- .../main_menu/match_view/match_view.dart | 118 ++++++++---------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 7622134..eb0e83d 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -24,16 +24,16 @@ class MatchView extends StatefulWidget { } class _MatchViewState extends State { - late Future> _gameListFuture; late final AppDatabase db; + bool isLoading = true; - late final List skeletonData = List.filled( + List matches = List.filled( 4, Match( name: 'Skeleton Gamename', group: Group( name: 'Groupname', - members: List.generate(5, (index) => Player(name: 'Player')), + members: List.filled(5, Player(name: 'Player')), ), winner: Player(name: 'Player'), players: [Player(name: 'Player')], @@ -44,10 +44,7 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.wait([ - db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + loadGames(); } @override @@ -57,67 +54,44 @@ class _MatchViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _gameListFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: matches.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Info', + message: 'No games created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: matches.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == matches.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Info', - message: 'No games created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List matches = - (isLoading ? skeletonData : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo(a.createdAt), - )) - .toList(); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: matches.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == matches.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, - ); - } - return GameHistoryTile( - onTap: () async { - Navigator.push( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => GameResultView( - match: matches[index], - onWinnerChanged: refreshGameList, - ), - ), - ); - }, - match: matches[index], - ); - }, - ), + return GameHistoryTile( + onTap: () async { + Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => GameResultView( + match: matches[index], + onWinnerChanged: loadGames, + ), + ), + ); + }, + match: matches[index], ); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -129,7 +103,7 @@ class _MatchViewState extends State { context, MaterialPageRoute( builder: (context) => - CreateMatchView(onWinnerChanged: refreshGameList), + CreateMatchView(onWinnerChanged: loadGames), ), ); }, @@ -140,9 +114,17 @@ class _MatchViewState extends State { ); } - void refreshGameList() { - setState(() { - _gameListFuture = db.matchDao.getAllMatches(); + void loadGames() { + Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + setState(() { + isLoading = false; + }); }); } } -- 2.49.1 From 1d92084da6a5499e8dfc55b69ff8401e8bdf53d9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:53:37 +0100 Subject: [PATCH 454/563] fix rangeerror when only 1 or less matches exist --- .../views/main_menu/home_view.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 31bedf5..bb2acf6 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -22,6 +22,7 @@ class _HomeViewState extends State { bool isLoading = true; int matchCount = 0; int groupCount = 0; + List loadedRecentMatches = []; List recentMatches = List.filled( 2, Match( @@ -41,7 +42,6 @@ class _HomeViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - Future.wait([ db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), @@ -50,12 +50,17 @@ class _HomeViewState extends State { ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; - recentMatches = results[2] as List; - + loadedRecentMatches = results[2] as List; recentMatches = - (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .take(2) .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: "Dummy Match", winner: null, group: null, players: null), + ); + } if (mounted) { setState(() { isLoading = false; @@ -69,6 +74,7 @@ class _HomeViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( + fixLayoutBuilder: true, enabled: isLoading, child: SingleChildScrollView( child: Column( @@ -103,7 +109,7 @@ class _HomeViewState extends State { content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( - visible: !isLoading, + visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, child: Text('No recent games available.'), @@ -125,7 +131,7 @@ class _HomeViewState extends State { padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - if (recentMatches.length > 1) ...[ + if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, game: 'Winner', -- 2.49.1 From b29cd6dff47e90611d9bfedd410c8895ae41e222 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:54:05 +0100 Subject: [PATCH 455/563] remove double quotes --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index bb2acf6..06b647c 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -58,7 +58,7 @@ class _HomeViewState extends State { .toList(); if (loadedRecentMatches.length < 2) { recentMatches.add( - Match(name: "Dummy Match", winner: null, group: null, players: null), + Match(name: 'Dummy Match', winner: null, group: null, players: null), ); } if (mounted) { -- 2.49.1 From f2917a6813b80a64ee935e8e6e6bd8e81f7651cd Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:00:38 +0100 Subject: [PATCH 456/563] removed empty line --- .../main_menu/match_view/create_match/create_match_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 27a1ce9..787d200 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -108,7 +108,6 @@ class _CreateMatchViewState extends State { groupsList = result[0] as List; playerList = result[1] as List; }); - filteredPlayerList = List.from(playerList); } -- 2.49.1 From f9722bc7628910c06ac5c7ded67100aa26d03541 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:05 +0100 Subject: [PATCH 457/563] wrap `isLoading = false` in a `mounted` check and `setState` call --- .../views/main_menu/group_view/groups_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5d303d5..b2243bc 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -105,7 +105,11 @@ class _GroupsViewState extends State { groups = loadedGroups ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } -- 2.49.1 From 7eb25221d75bd989fe52b344be6dfcd63d06f359 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:16 +0100 Subject: [PATCH 458/563] add mounted check before calling setState in match_view --- .../views/main_menu/match_view/match_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index eb0e83d..97a765e 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -122,9 +122,11 @@ class _MatchViewState extends State { final loadedMatches = results[0] as List; matches = loadedMatches ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - setState(() { - isLoading = false; - }); + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } -- 2.49.1 From 1e730cebe61e0c40faa6051a09bf82b2571309b3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:40 +0100 Subject: [PATCH 459/563] wrap isLoading into mounted and setState --- lib/presentation/widgets/player_selection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda5ea7..1e283fb 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -78,7 +78,11 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } -- 2.49.1 From c73f37507f23b2705eab820994814e52de75d9bf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:05:49 +0100 Subject: [PATCH 460/563] put isLoading in existing setState and move mounted check up --- .../widgets/player_selection.dart | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 1e283fb..fda1da1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -53,37 +53,35 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided, use that list. - if (widget.availablePlayers.isNotEmpty) { - widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - }); - if (mounted) { + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { setState(() { + // If a list of available players is provided, use that list. + if (widget.availablePlayers.isNotEmpty) { + widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } isLoading = false; }); - } - }); + }); + } } @override -- 2.49.1 From 7a80c1a792e2c208f0ef773522db94027242fdb5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:08:25 +0100 Subject: [PATCH 461/563] adjust heightFactor for empty game state in home view --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 06b647c..ade8f6e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -144,7 +144,7 @@ class _HomeViewState extends State { const SizedBox(height: 8), ] else ...[ const Center( - heightFactor: 4, + heightFactor: 5.35, child: Text('No second game available.'), ), ], -- 2.49.1 From b0a5145490ef353b134b3aa2d2e16824c70db044 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:14:31 +0100 Subject: [PATCH 462/563] move setState to include match loading and sorting in MatchView --- lib/presentation/views/main_menu/match_view/match_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 97a765e..462e1b5 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -119,11 +119,11 @@ class _MatchViewState extends State { db.matchDao.getAllMatches(), Future.delayed(minimumSkeletonDuration), ]).then((results) { - final loadedMatches = results[0] as List; - matches = loadedMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); if (mounted) { setState(() { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); isLoading = false; }); } -- 2.49.1 From 12856342a812f11f1d3b68efd950b5000082001c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 22:53:17 +0100 Subject: [PATCH 463/563] remove dots after sentences --- lib/presentation/views/main_menu/home_view.dart | 4 ++-- lib/presentation/widgets/player_selection.dart | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index ade8f6e..3e221e7 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -112,7 +112,7 @@ class _HomeViewState extends State { visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, - child: Text('No recent games available.'), + child: Text('No recent games available'), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -145,7 +145,7 @@ class _HomeViewState extends State { ] else ...[ const Center( heightFactor: 5.35, - child: Text('No second game available.'), + child: Text('No second game available'), ), ], ], diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda1da1..01c0338 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -179,10 +179,10 @@ class _PlayerSelectionState extends State { icon: Icons.info, title: 'Info', message: allPlayers.isEmpty - ? 'No players created yet.' + ? 'No players created yet' : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', + ? 'No more players to add' + : 'No players found with that name', ), child: ListView.builder( itemCount: suggestedPlayers.length, -- 2.49.1 From d7a006dd87f2449f1ee74dc94e758b840470aa4c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 23:51:34 +0100 Subject: [PATCH 464/563] fixed last gametile not visible --- lib/presentation/views/main_menu/match_view/match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 92fd268..7a7cb01 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -95,7 +95,7 @@ class _MatchViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == matches.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + height: MediaQuery.paddingOf(context).bottom - 20, ); } return GameHistoryTile( -- 2.49.1 From 966f7caa54ad02b0665858338108428ec6899a90 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:19:29 +0100 Subject: [PATCH 465/563] Fixed info message --- .../create_match/choose_group_view.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index c30d6ef..93686bd 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -74,10 +74,18 @@ class _ChooseGroupViewState extends State { Expanded( child: Visibility( visible: filteredGroups.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', + replacement: Visibility( + visible: controller.text.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'You have no groups created yet', + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'There is no group matching your search', + ), ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), -- 2.49.1 From 856ce43c49fac4627871ff5327b0c1d9096fbbfd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:28:06 +0100 Subject: [PATCH 466/563] Updated statistics title --- lib/presentation/views/main_menu/statistics_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 7df77a7..e94f2b6 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -60,7 +60,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.01), StatisticsTile( icon: Icons.sports_score, - title: 'Wins per Player', + title: 'Wins', width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate per Player', + title: 'Winrate', width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Match per Player', + title: 'Amount of Matches', width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, -- 2.49.1 From dac5cc8a892e7d2d1c4ff4b175e935ca1c15027e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:37:44 +0100 Subject: [PATCH 467/563] Hab leider nur von Wand bis Tapete gedacht --- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 93686bd..37096b9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -75,7 +75,7 @@ class _ChooseGroupViewState extends State { child: Visibility( visible: filteredGroups.isNotEmpty, replacement: Visibility( - visible: controller.text.isNotEmpty, + visible: widget.groups.isNotEmpty, replacement: const TopCenteredMessage( icon: Icons.info, title: 'Info', -- 2.49.1 From 5ae569f2e7ce3117d0ff68abcb81dec1743f2d40 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 19:16:25 +0100 Subject: [PATCH 468/563] Fixed updating error --- .../main_menu/match_view/create_match/create_match_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 787d200..0eea2b1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,6 +234,8 @@ class _CreateMatchViewState extends State { players: selectedPlayers, ); await db.matchDao.addMatch(match: match); + widget.onWinnerChanged?.call(); + if (context.mounted) { Navigator.pushReplacement( context, -- 2.49.1 From 31c85982226df68cdc24132e9c0b42b2e6877e91 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 19:28:47 +0100 Subject: [PATCH 469/563] Made cleaner version --- .../match_view/create_match/create_match_view.dart | 2 -- .../views/main_menu/match_view/match_result_view.dart | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 0eea2b1..787d200 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,8 +234,6 @@ class _CreateMatchViewState extends State { players: selectedPlayers, ); await db.matchDao.addMatch(match: match); - widget.onWinnerChanged?.call(); - if (context.mounted) { Navigator.pushReplacement( context, diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 58ff9ce..c6c3dae 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -38,6 +38,13 @@ class _GameResultViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + widget.onWinnerChanged?.call(); + Navigator.of(context).pop(); + }, + ), backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( -- 2.49.1 From 356cb1fb4316c2c6e64ea696c48c19482a89dd71 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:21:39 +0100 Subject: [PATCH 470/563] Updated insert mode to fix problem with replacing existing player match connections --- lib/data/dao/group_match_dao.dart | 2 +- lib/data/dao/player_match_dao.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/dao/group_match_dao.dart b/lib/data/dao/group_match_dao.dart index 3c16c83..d428fb5 100644 --- a/lib/data/dao/group_match_dao.dart +++ b/lib/data/dao/group_match_dao.dart @@ -21,7 +21,7 @@ class GroupMatchDao extends DatabaseAccessor } await into(groupMatchTable).insert( GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 9e242b7..7ebaee6 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -18,7 +18,7 @@ class PlayerMatchDao extends DatabaseAccessor }) async { await into(playerMatchTable).insert( PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } @@ -121,7 +121,7 @@ class PlayerMatchDao extends DatabaseAccessor inserts.map( (c) => into( playerMatchTable, - ).insert(c, mode: InsertMode.insertOrReplace), + ).insert(c, mode: InsertMode.insertOrIgnore), ), ); } -- 2.49.1 From b8b65c4ca18cd4bcfccc3b040c51a06910287909 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:23:15 +0100 Subject: [PATCH 471/563] Removed lines that added the group and players to the db again in addMatch() --- lib/data/dao/match_dao.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 32bd323..76c3709 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -80,7 +80,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); if (match.players != null) { - await db.playerDao.addPlayersAsList(players: match.players!); for (final p in match.players ?? []) { await db.playerMatchDao.addPlayerToMatch( matchId: match.id, @@ -90,7 +89,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } if (match.group != null) { - await db.groupDao.addGroup(group: match.group!); await db.groupMatchDao.addGroupToMatch( matchId: match.id, groupId: match.group!.id, @@ -186,7 +184,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { matchId: match.id, playerId: p.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } @@ -204,7 +202,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { playerId: m.id, groupId: match.group!.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } @@ -221,7 +219,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { matchId: match.id, groupId: match.group!.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } -- 2.49.1 From 9cb35dc4a1fd5676073641f80364d5728f896238 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:45:28 +0100 Subject: [PATCH 472/563] Updated comments --- lib/data/dao/match_dao.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 76c3709..b56a38b 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -65,8 +65,9 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } - /// Adds a new [Match] to the database. - /// Also adds associated players and group if they exist. + /// Adds a new [Match] to the database. Also adds players and group + /// associations. This method assumes that the players and groups added to + /// this match are already present in the database. Future addMatch({required Match match}) async { await db.transaction(() async { await into(matchTable).insert( @@ -100,6 +101,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// Adds multiple [Match]s to the database in a batch operation. /// Also adds associated players and groups if they exist. /// If the [matches] list is empty, the method returns immediately. + /// This Method should only be used to import matches from a different device. Future addMatchAsList({required List matches}) async { if (matches.isEmpty) return; await db.transaction(() async { -- 2.49.1 From e19f6967141a3ee586a696f681f6c7b7271a686d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:51:57 +0100 Subject: [PATCH 473/563] Fixed tests with adding players and groups to database on setup --- test/db_tests/game_test.dart | 12 +++++++++++- test/db_tests/group_game_test.dart | 12 +++++++++++- test/db_tests/player_game_test.dart | 13 ++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index ca86a60..d750156 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -23,7 +23,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -68,6 +68,16 @@ void main() { group: testGroup2, ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + ], + ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); }); tearDown(() async { await database.close(); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index a4fa146..06d201b 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -21,7 +21,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -53,6 +53,16 @@ void main() { group: testGroup1, ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + ], + ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); }); tearDown(() async { await database.close(); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index e2501eb..71e4088 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -21,7 +21,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -50,6 +50,17 @@ void main() { players: [testPlayer4, testPlayer5, testPlayer6], ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + testPlayer6, + ], + ); + await database.groupDao.addGroup(group: testgroup); }); tearDown(() async { await database.close(); -- 2.49.1 From c1789458e0a7193d093380b058375ddd7e62d5de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 20:56:39 +0100 Subject: [PATCH 474/563] Added tests for bug causing behaviour --- test/db_tests/game_test.dart | 2 +- test/db_tests/group_game_test.dart | 30 +++++++++++++++++++++- test/db_tests/player_game_test.dart | 39 ++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d750156..0ec2cfc 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -263,7 +263,7 @@ void main() { expect(matchCount, 0); }); - test('Checking if match has winner works correclty', () async { + test('Checking if match has winner works correctly', () async { await database.matchDao.addMatch(match: testMatch1); await database.matchDao.addMatch(match: testMatchOnlyGroup); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 06d201b..7d812bd 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; +import 'package:drift/drift.dart' hide isNotNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -189,5 +189,33 @@ void main() { } } }); + + test('Adding the same group to seperate matches works correctly', () async { + final match1 = Match(name: 'Match 1', group: testGroup1); + final match2 = Match(name: 'Match 2', group: testGroup1); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + final group1 = await database.groupMatchDao.getGroupOfMatch( + matchId: match1.id, + ); + final group2 = await database.groupMatchDao.getGroupOfMatch( + matchId: match2.id, + ); + + expect(group1, isNotNull); + expect(group2, isNotNull); + + final groups = [group1!, group2!]; + for (final group in groups) { + expect(group.members.length, testGroup1.members.length); + expect(group.id, testGroup1.id); + expect(group.name, testGroup1.name); + expect(group.createdAt, testGroup1.createdAt); + } + }); }); } diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 71e4088..8a4f569 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; +import 'package:drift/drift.dart' hide isNotNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -196,5 +196,42 @@ void main() { expect(player.createdAt, testPlayer.createdAt); } }); + + test( + 'Adding the same player to seperate matches works correctly', + () async { + final playersList = [testPlayer1, testPlayer2, testPlayer3]; + final match1 = Match(name: 'Match 1', players: playersList); + final match2 = Match(name: 'Match 2', players: playersList); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + final players1 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match1.id, + ); + final players2 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match2.id, + ); + + expect(players1, isNotNull); + expect(players2, isNotNull); + + expect( + players1!.map((p) => p.id).toList(), + equals(players2!.map((p) => p.id).toList()), + ); + expect( + players1.map((p) => p.name).toList(), + equals(players2.map((p) => p.name).toList()), + ); + expect( + players1.map((p) => p.createdAt).toList(), + equals(players2.map((p) => p.createdAt).toList()), + ); + }, + ); }); } -- 2.49.1 From 9821af39fed15f8b7fc6c0fe9995f7709e19290c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:00:56 +0100 Subject: [PATCH 475/563] Renamed test files --- test/db_tests/{group_game_test.dart => group_match_test.dart} | 0 test/db_tests/{player_game_test.dart => player_match_test.dart} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/db_tests/{group_game_test.dart => group_match_test.dart} (100%) rename test/db_tests/{player_game_test.dart => player_match_test.dart} (100%) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_match_test.dart similarity index 100% rename from test/db_tests/group_game_test.dart rename to test/db_tests/group_match_test.dart diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_match_test.dart similarity index 100% rename from test/db_tests/player_game_test.dart rename to test/db_tests/player_match_test.dart -- 2.49.1 From 05c6625bf32b4318f112f7bb614f522edff68b04 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:01:07 +0100 Subject: [PATCH 476/563] Renamed MatchResultView and match variable --- .../create_match/create_match_view.dart | 2 +- .../match_view/match_result_view.dart | 20 +++++++++---------- .../main_menu/match_view/match_view.dart | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 787d200..ea48392 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -239,7 +239,7 @@ class _CreateMatchViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView( + builder: (context) => MatchResultView( match: match, onWinnerChanged: widget.onWinnerChanged, ), diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index c6c3dae..e8075f6 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -6,17 +6,17 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; -class GameResultView extends StatefulWidget { +class MatchResultView extends StatefulWidget { final Match match; final VoidCallback? onWinnerChanged; - const GameResultView({super.key, required this.match, this.onWinnerChanged}); + const MatchResultView({super.key, required this.match, this.onWinnerChanged}); @override - State createState() => _GameResultViewState(); + State createState() => _MatchResultViewState(); } -class _GameResultViewState extends State { +class _MatchResultViewState extends State { late final List allPlayers; late final AppDatabase db; Player? _selectedPlayer; @@ -142,12 +142,12 @@ class _GameResultViewState extends State { widget.onWinnerChanged?.call(); } - List getAllPlayers(Match game) { - if (game.group == null && game.players != null) { - return [...game.players!]; - } else if (game.group != null && game.players != null) { - return [...game.players!, ...game.group!.members]; + List getAllPlayers(Match match) { + if (match.group == null && match.players != null) { + return [...match.players!]; + } else if (match.group != null && match.players != null) { + return [...match.players!, ...match.group!.members]; } - return [...game.group!.members]; + return [...match.group!.members]; } } diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 462e1b5..e7d29c0 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -80,7 +80,7 @@ class _MatchViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView( + builder: (context) => MatchResultView( match: matches[index], onWinnerChanged: loadGames, ), -- 2.49.1 From ecd03dfd9d5efea2c0ac099b9e7372ccbc2e0c3d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:01:40 +0100 Subject: [PATCH 477/563] Updated hint text --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index ea48392..6f966af 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -132,7 +132,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _gameNameController, - hintText: 'Game name', + hintText: 'Match name', ), ), ChooseTile( -- 2.49.1 From 58fe38fd72b6a8efd24063e822e7681507e4b35e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:02:03 +0100 Subject: [PATCH 478/563] Updated comment --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 6f966af..2d67aa4 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -28,7 +28,7 @@ class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; - /// Controller for the game name input field + /// Controller for the match name input field final TextEditingController _gameNameController = TextEditingController(); /// List of all groups from the database -- 2.49.1 From 5428064c53aa37e8b795788b2b10960d6b60efb7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:22:07 +0100 Subject: [PATCH 479/563] Some last changes --- .../match_view/create_match/create_match_view.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 2d67aa4..f3b4d79 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -29,7 +29,7 @@ class _CreateMatchViewState extends State { late final AppDatabase db; /// Controller for the match name input field - final TextEditingController _gameNameController = TextEditingController(); + final TextEditingController _matchNameController = TextEditingController(); /// List of all groups from the database List groupsList = []; @@ -95,7 +95,7 @@ class _CreateMatchViewState extends State { @override void initState() { super.initState(); - _gameNameController.addListener(() { + _matchNameController.addListener(() { setState(() {}); }); @@ -119,7 +119,7 @@ class _CreateMatchViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: const Text( - 'Create new game', + 'Create new match', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -131,7 +131,7 @@ class _CreateMatchViewState extends State { Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( - controller: _gameNameController, + controller: _matchNameController, hintText: 'Match name', ), ), @@ -222,13 +222,13 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: 'Create game', + text: 'Create match', sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() ? () async { Match match = Match( - name: _gameNameController.text.trim(), + name: _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, @@ -258,7 +258,7 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _gameNameController.text.isNotEmpty && + return _matchNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; -- 2.49.1 From bfca41bd36af578a80e34f9b84c3cc7856972c63 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 16:38:09 +0100 Subject: [PATCH 480/563] Fixed info message placement --- .../widgets/player_selection.dart | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e2114b2..88841e6 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -128,33 +128,47 @@ class _PlayerSelectionState extends State { style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 8.0, - runSpacing: 8.0, - children: [ - // Generates a TextIconTile for each selected player. - for (var player in selectedPlayers) - TextIconTile( - text: player.name, - onIconTap: () { - setState(() { - // Removes the player from the selection and notifies the parent. - final currentSearch = _searchBarController.text - .toLowerCase(); - selectedPlayers.remove(player); - widget.onChanged([...selectedPlayers]); - // If the player matches the current search query (or search is empty), - // they are added back to the suggestions and the list is re-sorted. - if (currentSearch.isEmpty || - player.name.toLowerCase().contains(currentSearch)) { - suggestedPlayers.add(player); - } - }); - }, - ), - ], + SizedBox( + height: 50, + child: selectedPlayers.isEmpty + ? const Center( + child: Text( + 'No players selected', + style: TextStyle(color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (var player in selectedPlayers) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: TextIconTile( + text: player.name, + onIconTap: () { + setState(() { + // Removes the player from the selection and notifies the parent. + final currentSearch = _searchBarController + .text + .toLowerCase(); + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), + // they are added back to the suggestions and the list is re-sorted. + if (currentSearch.isEmpty || + player.name.toLowerCase().contains( + currentSearch, + )) { + suggestedPlayers.add(player); + } + }); + }, + ), + ), + ], + ), + ), ), const SizedBox(height: 10), const Text( -- 2.49.1 From 8c3282b954642f823a8e7f063d5a7ef9b5ceb977 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 17:10:47 +0100 Subject: [PATCH 481/563] Changed color to white --- lib/presentation/widgets/player_selection.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 007a95f..eb70ae0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -136,12 +136,7 @@ class _PlayerSelectionState extends State { SizedBox( height: 50, child: selectedPlayers.isEmpty - ? const Center( - child: Text( - 'No players selected', - style: TextStyle(color: Colors.grey), - ), - ) + ? const Center(child: Text('No players selected')) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( -- 2.49.1 From 16cdf9db3ea5bd3277086e6dec4871c050095b79 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 31 Dec 2025 17:39:20 +0100 Subject: [PATCH 482/563] Restricted app orientation to portrait mode on iOS and Android --- android/app/src/main/AndroidManifest.xml | 1 + ios/Runner/Info.plist | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4cded1b..7e89f4b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad -- 2.49.1 From 18f0626e95befce350f434b53566b5615712c1ba Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 18:33:09 +0100 Subject: [PATCH 483/563] Added empty statistics message --- .../views/main_menu/statistics_view.dart | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e94f2b6..0e95721 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class StatisticsView extends StatefulWidget { @@ -58,31 +59,46 @@ class _StatisticsViewState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: constraints.maxHeight * 0.01), - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, + Visibility( + visible: + winCounts.isEmpty && + matchCounts.isEmpty && + winRates.isEmpty, + replacement: Column( + children: [ + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), + ], + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No statistics available', + ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], -- 2.49.1 From 175a9cb3490a3554fd10d24b9962a3a5426287c2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 18:36:33 +0100 Subject: [PATCH 484/563] Added hiding statistics tiles if their corresponding data is not available --- .../views/main_menu/statistics_view.dart | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 0e95721..46b01b7 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -66,32 +66,38 @@ class _StatisticsViewState extends State { winRates.isEmpty, replacement: Column( children: [ - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), + if (winCounts.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + ], + if (winRates.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + ], + if (matchCounts.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), + ], ], ), child: const TopCenteredMessage( -- 2.49.1 From 2b78617924fc29e30d138071b38cdd16ae4e3f64 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 19:11:34 +0100 Subject: [PATCH 485/563] Redo seperate visibility --- .../views/main_menu/statistics_view.dart | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 46b01b7..0e95721 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -66,38 +66,32 @@ class _StatisticsViewState extends State { winRates.isEmpty, replacement: Column( children: [ - if (winCounts.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - ], - if (winRates.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - ], - if (matchCounts.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), - ], + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), ], ), child: const TopCenteredMessage( -- 2.49.1 From 179ac2fe213ae22c2129275a75a4d7d2caa81f2f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:16 +0100 Subject: [PATCH 486/563] Update localization dependencies and enable code generation --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 07e4df2..866f662 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,10 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 - intl: ^0.18.0 + intl: any + flutter_localizations: + sdk: flutter + auto_localize: ^0.0.5 dev_dependencies: flutter_test: @@ -35,5 +38,6 @@ dev_dependencies: flutter: uses-material-design: true + generate: true assets: - assets/schema.json -- 2.49.1 From 8e05e9d61b71e1591b7ab7a7821ce408e821fafe Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:22 +0100 Subject: [PATCH 487/563] Added l10n.yaml for localization configuration --- l10n.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 l10n.yaml diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..f5730dc --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n/arb +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +output-dir: lib/l10n/generated \ No newline at end of file -- 2.49.1 From d9a26a8cf7df889298cc3e80a3e2435be4edd313 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:44 +0100 Subject: [PATCH 488/563] Added English and German localization files containing all strings used in app --- lib/l10n/arb/app_de.arb | 84 ++++++++++ lib/l10n/arb/app_en.arb | 357 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 lib/l10n/arb/app_de.arb create mode 100644 lib/l10n/arb/app_en.arb diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb new file mode 100644 index 0000000..3e090e3 --- /dev/null +++ b/lib/l10n/arb/app_de.arb @@ -0,0 +1,84 @@ +{ + "@@locale": "de", + "choose_group": "Gruppe wählen", + "create_new_match": "Neues Match erstellen", + "choose_ruleset": "Regelwerk wählen", + "choose_game": "Spiel wählen", + "select_winner": "Gewinner wählen:", + "no_recent_matches_available": "Keine letzten Matches verfügbar", + "no_second_match_available": "Kein zweites Match verfügbar", + "delete_all_data": "Alle Daten löschen?", + "cancel": "Abbrechen", + "delete": "Löschen", + "create_new_group": "Neue Gruppe erstellen", + "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler: {count}", + "no_players_selected": "Keine Spieler ausgewählt", + "all_players": "Alle Spieler:", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "winner": "Gewinner: {winnerName}", + "players": "Spieler", + "player_name": "Spielername", + "no_data_available": "Keine Daten verfügbar.", + "matches": "Matches", + "groups": "Gruppen", + "recent_matches": "Letzte Matches", + "quick_create": "Schnellzugriff", + "winner_label": "Gewinner", + "ruleset_label": "Regelwerk", + "match_in_progress": "Match läuft...", + "menu": "Menü", + "settings": "Einstellungen", + "export_data": "Daten exportieren", + "import_data": "Daten importieren", + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_imported": "Daten erfolgreich importiert", + "invalid_schema": "Ungültiges Schema", + "error_reading_file": "Fehler beim Lesen der Datei", + "import_canceled": "Import abgebrochen", + "format_exception": "Formatfehler (siehe Konsole)", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "data_successfully_exported": "Daten erfolgreich exportiert", + "export_canceled": "Export abgebrochen", + "undo": "Rückgängig", + "wins": "Siege", + "winrate": "Siegquote", + "amount_of_matches": "Anzahl der Matches", + "info": "Info", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "create_group": "Gruppe erstellen", + "group_name": "Gruppenname", + "no_matches_created_yet": "Noch keine Matches erstellt", + "create_game": "Match erstellen", + "match_name": "Matchname", + "game": "Spiel", + "ruleset": "Regelwerk", + "group": "Gruppe", + "none": "Keine", + "create_match": "Match erstellen", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen", + "no_players_created_yet": "Noch keine Spieler erstellt", + "all_players_selected": "Alle Spieler ausgewählt", + "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "today_at": "Heute um {time}", + "yesterday_at": "Gestern um {time}", + "days_ago": "vor {count} Tagen", + "home": "Startseite", + "statistics": "Statistiken", + "stats": "Statistiken", + "players_count": "{count} Spieler", + "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", + "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", + "game_name": "Spielname", + "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", + "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein Gewinner", + "single_loser": "Ein Verlierer", + "most_points": "Höchste Punkte", + "least_points": "Niedrigste Punkte" +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb new file mode 100644 index 0000000..44a8f67 --- /dev/null +++ b/lib/l10n/arb/app_en.arb @@ -0,0 +1,357 @@ +{ + "@@locale": "en", + "choose_group": "Choose Group", + "@choose_group": { + "description": "Label for choosing a group" + }, + "create_new_match": "Create new match", + "@create_new_match": { + "description": "Button text to create a new match" + }, + "choose_ruleset": "Choose Ruleset", + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "choose_game": "Choose Game", + "@choose_game": { + "description": "Label for choosing a game" + }, + "select_winner": "Select Winner:", + "@select_winner": { + "description": "Label to select the winner" + }, + "no_recent_matches_available": "No recent matches available", + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "no_second_match_available": "No second match available", + "@no_second_matcb_available": { + "description": "Message when no second match exists" + }, + "delete_all_data": "Delete all data?", + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button text" + }, + "delete": "Delete", + "@delete": { + "description": "Delete button text" + }, + "create_new_group": "Create new group", + "@create_new_group": { + "description": "Button text to create a new group" + }, + "error_while_creating_group_please_try_again": "Error while creating group, please try again", + "@error_while_creating_group_please_try_again": { + "description": "Error message when group creation fails" + }, + "selected_players": "Selected players: {count}", + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "no_players_selected": "No players selected", + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "all_players": "All players:", + "@all_players": { + "description": "Label for all players list" + }, + "successfully_added_player": "Successfully added player {playerName}.", + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "could_not_add_player": "Could not add player {playerName}.", + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "winner": "Winner: {winnerName}", + "@winner": { + "description": "Shows the winner's name", + "placeholders": { + "winnerName": { + "type": "String", + "example": "John" + } + } + }, + "players": "Players", + "@players": { + "description": "Players label" + }, + "no_data_available": "No data available.", + "@no_data_available": { + "description": "Message when no data is available" + }, + "matches": "Matches", + "@matches": { + "description": "Label for matches" + }, + "groups": "Groups", + "@groups": { + "description": "Label for groups" + }, + "recent_matches": "Recent Matches", + "@recent_matches": { + "description": "Title for recent matches section" + }, + "quick_create": "Quick Create", + "@quick_create": { + "description": "Title for quick create section" + }, + "winner_label": "Winner", + "@winner_label": { + "description": "Label for winner field" + }, + "ruleset_label": "Ruleset", + "@ruleset_label": { + "description": "Label for ruleset field" + }, + "match_in_progress": "Match in progress...", + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "menu": "Menu", + "@menu": { + "description": "Menu label" + }, + "settings": "Settings", + "@settings": { + "description": "Settings label" + }, + "export_data": "Export data", + "@export_data": { + "description": "Export data menu item" + }, + "import_data": "Import data", + "@import_data": { + "description": "Import data menu item" + }, + "this_cannot_be_undone": "This can't be undone", + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "data_successfully_deleted": "Data successfully deleted", + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "data_successfully_imported": "Data successfully imported", + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "invalid_schema": "Invalid Schema", + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "error_reading_file": "Error reading file", + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "import_canceled": "Import canceled", + "@import_canceled": { + "description": "Message when import is canceled" + }, + "format_exception": "Format Exception (see console)", + "@format_exception": { + "description": "Error message for format exceptions" + }, + "unknown_exception": "Unknown Exception (see console)", + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "data_successfully_exported": "Data successfully exported", + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "export_canceled": "Export canceled", + "@export_canceled": { + "description": "Message when export is canceled" + }, + "undo": "Undo", + "@undo": { + "description": "Undo button text" + }, + "wins": "Wins", + "@wins": { + "description": "Label for wins statistic" + }, + "winrate": "Winrate", + "@winrate": { + "description": "Label for winrate statistic" + }, + "amount_of_matches": "Amount of Matches", + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "info": "Info", + "@info": { + "description": "Info label" + }, + "no_groups_created_yet": "No groups created yet", + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "no_players_created_yet": "No players created yet", + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "create_group": "Create Group", + "@create_group": { + "description": "Button text to create a group" + }, + "group_name": "Group name", + "@group_name": { + "description": "Placeholder for group name input" + }, + "player_name": "Player name", + "@player_name": { + "description": "Placeholder for player name input" + }, + "no_matches_created_yet": "No matches created yet", + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "match_name": "Match name", + "@match_name": { + "description": "Placeholder for match name input" + }, + "game": "Game", + "@game": { + "description": "Game label" + }, + "ruleset": "Ruleset", + "@ruleset": { + "description": "Ruleset label" + }, + "group": "Group", + "@group": { + "description": "Group label" + }, + "none": "None", + "@none": { + "description": "None option label" + }, + "create_match": "Create match", + "@create_match": { + "description": "Button text to create a match" + }, + "no_players_found_with_that_name": "No players found with that name", + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "all_players_selected": "All players selected", + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "today_at": "Today at {time}", + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "yesterday_at": "Yesterday at {time}", + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "days_ago": "{count} days ago", + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "home": "Home", + "@home": { + "description": "Home tab label" + }, + "statistics": "Statistics", + "@statistics": { + "description": "Statistics tab label" + }, + "stats": "Stats", + "@stats": { + "description": "Stats tab label (short)" + }, + "players_count": "{count} Players", + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "there_is_no_group_matching_your_search": "There is no group matching your search", + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "game_name": "Game Name", + "@game_name": { + "description": "Placeholder for game name search" + }, + "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner_desc": { + "description": "Description for single winner ruleset" + }, + "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser_desc": { + "description": "Description for single loser ruleset" + }, + "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points_desc": { + "description": "Description for most points ruleset" + }, + "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points_desc": { + "description": "Description for least points ruleset" + }, + "single_winner": "Single Winner", + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "single_loser": "Single Loser", + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "most_points": "Most Points", + "@most_points": { + "description": "Title for most points ruleset" + }, + "least_points": "Least Points", + "@least_points": { + "description": "Title for least points ruleset" + } +} -- 2.49.1 From 534d19efc3dfd6fd961ddb9e68466ae0a8cd1042 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:21:09 +0100 Subject: [PATCH 489/563] Implemented localization across the application. --- lib/core/enums.dart | 15 +-- lib/main.dart | 3 + .../main_menu/custom_navigation_bar.dart | 17 ++-- .../group_view/create_group_view.dart | 17 ++-- .../main_menu/group_view/groups_view.dart | 9 +- .../views/main_menu/home_view.dart | 49 +++++++--- .../create_match/choose_game_view.dart | 13 ++- .../create_match/choose_group_view.dart | 22 +++-- .../create_match/choose_ruleset_view.dart | 10 +- .../create_match/create_match_view.dart | 92 +++++++++---------- .../match_view/match_result_view.dart | 5 +- .../main_menu/match_view/match_view.dart | 9 +- .../views/main_menu/settings_view.dart | 83 +++++++++++------ .../views/main_menu/statistics_view.dart | 7 +- .../widgets/player_selection.dart | 31 +++++-- .../widgets/tiles/game_history_tile.dart | 17 ++-- .../widgets/tiles/statistics_tile.dart | 5 +- 17 files changed, 247 insertions(+), 157 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 737882e..fc1ac91 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; + /// Button types used for styling the [CustomWidthButton] enum ButtonType { primary, secondary, tertiary } @@ -30,16 +33,16 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.leastPoints]: The player with the fewest points wins. enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } -/// Translates a [Ruleset] enum value to its corresponding string representation. -String translateRulesetToString(Ruleset ruleset) { +/// Translates a [Ruleset] enum value to its corresponding localized string. +String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return 'Single Winner'; + return AppLocalizations.of(context)!.single_winner; case Ruleset.singleLoser: - return 'Single Loser'; + return AppLocalizations.of(context)!.single_loser; case Ruleset.mostPoints: - return 'Most Points'; + return AppLocalizations.of(context)!.most_points; case Ruleset.leastPoints: - return 'Least Points'; + return AppLocalizations.of(context)!.least_points; } } diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..0f3b6b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -20,6 +21,8 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2ec28fa..980bf1a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; @@ -89,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: 'Home', + label: AppLocalizations.of(context)!.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: 'Matches', + label: AppLocalizations.of(context)!.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: 'Groups', + label: AppLocalizations.of(context)!.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: 'Stats', + label: AppLocalizations.of(context)!.statistics, onTabTapped: onTabTapped, ), ], @@ -131,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return 'Home'; + return AppLocalizations.of(context)!.home; case 1: - return 'Matches'; + return AppLocalizations.of(context)!.matches; case 2: - return 'Groups'; + return AppLocalizations.of(context)!.groups; case 3: - return 'Statistics'; + return AppLocalizations.of(context)!.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index f20fb4e..bceb3bd 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; @@ -43,8 +44,8 @@ class _CreateGroupViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new group', + title: Text( + AppLocalizations.of(context)!.create_new_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -57,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: 'Group name', + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() {}); }, @@ -73,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: 'Create group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -94,10 +95,12 @@ class _CreateGroupViewState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, - content: const Center( + content: Center( child: Text( - 'Error while creating group, please try again', - style: TextStyle(color: Colors.white), + AppLocalizations.of( + context, + )!.error_while_creating_group_please_try_again, + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index b2243bc..0e9ddbb 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -49,11 +50,11 @@ class _GroupsViewState extends State { enabled: isLoading, child: Visibility( visible: groups.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), ), child: ListView.builder( @@ -73,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3e221e7..1f0f233 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; @@ -86,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Matches', + title: AppLocalizations.of(context)!.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -94,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Groups', + title: AppLocalizations.of(context)!.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -104,15 +105,19 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: 'Recent Matches', + title: AppLocalizations.of(context)!.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( visible: !isLoading && loadedRecentMatches.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 12, - child: Text('No recent games available'), + child: Text( + AppLocalizations.of( + context, + )!.no_recent_matches_available, + ), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -120,11 +125,15 @@ class _HomeViewState extends State { children: [ MatchTile( matchTitle: recentMatches[0].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of(context)!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null - ? 'Match in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -134,18 +143,28 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of( + context, + )!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null - ? 'Game in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ - const Center( + Center( heightFactor: 5.35, - child: Text('No second game available'), + child: Text( + AppLocalizations.of( + context, + )!.no_second_match_available, + ), ), ], ], @@ -156,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: 'Quick Create', + title: AppLocalizations.of(context)!.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -213,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return '$playerCount Players'; + return AppLocalizations.of(context)!.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 53a4fcb..9ffe5b2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; @@ -41,8 +42,8 @@ class _ChooseGameViewState extends State { Navigator.of(context).pop(selectedGameIndex); }, ), - title: const Text( - 'Choose Game', + title: Text( + AppLocalizations.of(context)!.choose_game, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -53,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: 'Game Name', + hintText: AppLocalizations.of(context)!.game_name, ), ), const SizedBox(height: 5), @@ -64,8 +65,10 @@ class _ChooseGameViewState extends State { return TitleDescriptionListTile( title: widget.games[index].$1, description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, + badgeText: translateRulesetToString( + widget.games[index].$3, + context, + ), onPressed: () async { setState(() { if (selectedGameIndex == index) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 37096b9..2f5dc75 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -22,7 +23,6 @@ class ChooseGroupView extends StatefulWidget { class _ChooseGroupViewState extends State { late String selectedGroupId; final TextEditingController controller = TextEditingController(); - final String hintText = 'Group Name'; late final List filteredGroups; @override @@ -51,8 +51,8 @@ class _ChooseGroupViewState extends State { ); }, ), - title: const Text( - 'Choose Group', + title: Text( + AppLocalizations.of(context)!.choose_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: hintText, + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() { filterGroups(value); @@ -76,15 +76,17 @@ class _ChooseGroupViewState extends State { visible: filteredGroups.isNotEmpty, replacement: Visibility( visible: widget.groups.isNotEmpty, - replacement: const TopCenteredMessage( + replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'You have no groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of( + context, + )!.there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 537f749..ff6ab83 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { @@ -46,8 +47,8 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: const Text( - 'Choose Ruleset', + title: Text( + AppLocalizations.of(context)!.choose_ruleset, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -66,7 +67,10 @@ class _ChooseRulesetViewState extends State { } }); }, - title: translateRulesetToString(widget.rulesets[index].$1), + title: translateRulesetToString( + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f3b4d79..62da943 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; @@ -64,34 +65,6 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their descriptions - /// as tuples of (Ruleset, String) - /// TODO: Replace when rulesets are implemented - List<(Ruleset, String)> rulesets = [ - ( - Ruleset.singleWinner, - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', - ), - ( - Ruleset.singleLoser, - 'Exactly one loser is determined; last place receives the penalty or consequence.', - ), - ( - Ruleset.mostPoints, - 'Traditional ruleset: the player with the most points wins.', - ), - ( - Ruleset.leastPoints, - 'Inverse scoring: the player with the fewest points wins.', - ), - ]; - - // TODO: Replace when games are implemented - List<(String, String, Ruleset)> games = [ - ('Example Game 1', 'This is a discription', Ruleset.leastPoints), - ('Example Game 2', '', Ruleset.singleWinner), - ]; - @override void initState() { super.initState(); @@ -111,6 +84,33 @@ class _CreateMatchViewState extends State { filteredPlayerList = List.from(playerList); } + List<(Ruleset, String)> _getRulesets(BuildContext context) { + return [ + ( + Ruleset.singleWinner, + AppLocalizations.of(context)!.ruleset_single_winner_desc, + ), + ( + Ruleset.singleLoser, + AppLocalizations.of(context)!.ruleset_single_loser_desc, + ), + ( + Ruleset.mostPoints, + AppLocalizations.of(context)!.ruleset_most_points_desc, + ), + ( + Ruleset.leastPoints, + AppLocalizations.of(context)!.ruleset_least_points_desc, + ), + ]; + } + + // TODO: Replace when games are implemented + List<(String, String, Ruleset)> games = [ + ('Example Game 1', 'This is a description', Ruleset.leastPoints), + ('Example Game 2', '', Ruleset.singleWinner), + ]; + @override Widget build(BuildContext context) { return Scaffold( @@ -118,8 +118,8 @@ class _CreateMatchViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new match', + title: Text( + AppLocalizations.of(context)!.create_new_match, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -132,13 +132,13 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: 'Match name', + hintText: AppLocalizations.of(context)!.match_name, ), ), ChooseTile( - title: 'Game', + title: AppLocalizations.of(context)!.game, trailingText: selectedGameIndex == -1 - ? 'None' + ? AppLocalizations.of(context)!.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -152,9 +152,9 @@ class _CreateMatchViewState extends State { setState(() { if (selectedGameIndex != -1) { selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); } else { selectedRuleset = null; } @@ -162,30 +162,30 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: 'Ruleset', + title: AppLocalizations.of(context)!.ruleset, trailingText: selectedRuleset == null - ? 'None' - : translateRulesetToString(selectedRuleset!), + ? AppLocalizations.of(context)!.none + : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _getRulesets(context), initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); selectedGameIndex = -1; setState(() {}); }, ), ChooseTile( - title: 'Group', + title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? 'None' + ? AppLocalizations.of(context)!.none : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -222,7 +222,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: 'Create match', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e8075f6..1639dce 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; @@ -79,8 +80,8 @@ class _MatchResultViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Select Winner:', + Text( + AppLocalizations.of(context)!.select_winner, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..af7f7ed 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -8,6 +8,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; @@ -58,11 +59,11 @@ class _MatchViewState extends State { enabled: isLoading, child: Visibility( visible: matches.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Info', - message: 'No games created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_matches_created_yet, ), ), child: ListView.builder( @@ -96,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Game', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 6ebb7fb..8899e40 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; @@ -24,30 +25,33 @@ class _SettingsViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Padding( - padding: EdgeInsets.fromLTRB(24, 0, 24, 10), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - 'Menu', - style: TextStyle( + AppLocalizations.of(context)!.menu, + style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), child: Text( textAlign: TextAlign.start, - 'Settings', - style: TextStyle( + AppLocalizations.of(context)!.settings, + style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), ), SettingsListTile( - title: 'Export data', + title: AppLocalizations.of(context)!.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -62,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Import data', + title: AppLocalizations.of(context)!.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -74,23 +78,27 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Delete all data', + title: AppLocalizations.of(context)!.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete all data?'), - content: const Text('This can\'t be undone'), + title: Text( + AppLocalizations.of(context)!.delete_all_data, + ), + content: Text( + AppLocalizations.of(context)!.this_cannot_be_undone, + ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Abbrechen'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: const Text('Löschen'), + child: Text(AppLocalizations.of(context)!.delete), ), ], ), @@ -99,7 +107,9 @@ class _SettingsViewState extends State { DataTransferService.deleteAllData(context); showSnackbar( context: context, - message: 'Daten erfolgreich gelöscht', + message: AppLocalizations.of( + context, + )!.data_successfully_deleted, ); } }); @@ -122,22 +132,34 @@ class _SettingsViewState extends State { }) { switch (result) { case ImportResult.success: - showSnackbar(context: context, message: 'Data successfully imported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_imported, + ); case ImportResult.invalidSchema: - showSnackbar(context: context, message: 'Invalid Schema'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.invalid_schema, + ); case ImportResult.fileReadError: - showSnackbar(context: context, message: 'Error reading file'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.error_reading_file, + ); case ImportResult.canceled: - showSnackbar(context: context, message: 'Import canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.import_canceled, + ); case ImportResult.formatException: showSnackbar( context: context, - message: 'Format Exception (see console)', + message: AppLocalizations.of(context)!.format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -152,13 +174,19 @@ class _SettingsViewState extends State { }) { switch (result) { case ExportResult.success: - showSnackbar(context: context, message: 'Data successfully exported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_exported, + ); case ExportResult.canceled: - showSnackbar(context: context, message: 'Export canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.export_canceled, + ); case ExportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -183,7 +211,10 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction(label: 'Rückgängig', onPressed: action) + ? SnackBarAction( + label: AppLocalizations.of(context)!.undo, + onPressed: action, + ) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e94f2b6..07c27aa 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; @@ -60,7 +61,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.01), StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -69,7 +70,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -78,7 +79,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of(context)!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eb70ae0..a827cc0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; @@ -129,14 +130,20 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - 'Selected players: (${selectedPlayers.length})', + AppLocalizations.of( + context, + )!.selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), SizedBox( height: 50, child: selectedPlayers.isEmpty - ? const Center(child: Text('No players selected')) + ? Center( + child: Text( + AppLocalizations.of(context)!.no_players_selected, + ), + ) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -171,8 +178,8 @@ class _PlayerSelectionState extends State { ), ), const SizedBox(height: 10), - const Text( - 'All players:', + Text( + AppLocalizations.of(context)!.all_players, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -186,12 +193,14 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', + title: AppLocalizations.of(context)!.info, message: allPlayers.isEmpty - ? 'No players created yet' + ? AppLocalizations.of(context)!.no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add' - : 'No players found with that name', + ? AppLocalizations.of(context)!.all_players_selected + : AppLocalizations.of( + context, + )!.no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -243,7 +252,9 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Successfully added player $playerName.', + AppLocalizations.of( + context, + )!.successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -255,7 +266,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Could not add player $playerName.', + AppLocalizations.of(context)!.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index f79edc3..4f40fc6 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; @@ -97,7 +98,7 @@ class _GameHistoryTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - 'Winner: ${winner.name}', + AppLocalizations.of(context)!.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -113,8 +114,8 @@ class _GameHistoryTileState extends State { ], if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', + Text( + AppLocalizations.of(context)!.players, style: TextStyle( fontSize: 13, color: Colors.grey, @@ -141,11 +142,15 @@ class _GameHistoryTileState extends State { final difference = now.difference(dateTime); if (difference.inDays == 0) { - return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { - return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return '${difference.inDays} days ago'; + return AppLocalizations.of(context)!.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 582cf66..e783ab4 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; class StatisticsTile extends StatelessWidget { @@ -33,9 +34,9 @@ class StatisticsTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Visibility( visible: values.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 4, - child: Text('No data available.'), + child: Text(AppLocalizations.of(context)!.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From ed9e3af76869efa9027d75be1bc874dbcea9d3c2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:34:55 +0100 Subject: [PATCH 490/563] merged dev into branch and fixed missing comma --- lib/presentation/views/main_menu/statistics_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e66a2ce..dabfff5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,6 +100,7 @@ class _StatisticsViewState extends State { title: 'Info', message: 'No statistics available', ), + ), StatisticsTile( icon: Icons.sports_score, title: AppLocalizations.of(context)!.wins, -- 2.49.1 From 73aea0d0f56853f1da87522191b1e6c5cb63c5cc Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:39:15 +0100 Subject: [PATCH 491/563] added missings consts --- .../views/main_menu/group_view/create_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_game_view.dart | 2 +- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_ruleset_view.dart | 2 +- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- .../views/main_menu/match_view/match_result_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- lib/presentation/widgets/tiles/game_history_tile.dart | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index bceb3bd..e9e5fe1 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,7 +46,7 @@ class _CreateGroupViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 9ffe5b2..6a75ae1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -44,7 +44,7 @@ class _ChooseGameViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_game, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 2f5dc75..7308dce 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -53,7 +53,7 @@ class _ChooseGroupViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index ff6ab83..a6129cc 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -49,7 +49,7 @@ class _ChooseRulesetViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_ruleset, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 62da943..8eeaca6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -120,7 +120,7 @@ class _CreateMatchViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_match, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 1639dce..f13ef87 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -82,7 +82,7 @@ class _MatchResultViewState extends State { children: [ Text( AppLocalizations.of(context)!.select_winner, - style: TextStyle( + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a827cc0..9a1ffd1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -180,7 +180,7 @@ class _PlayerSelectionState extends State { const SizedBox(height: 10), Text( AppLocalizations.of(context)!.all_players, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), /* diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 4f40fc6..091935e 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -116,7 +116,7 @@ class _GameHistoryTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( AppLocalizations.of(context)!.players, - style: TextStyle( + style: const TextStyle( fontSize: 13, color: Colors.grey, fontWeight: FontWeight.w500, -- 2.49.1 From 86dbc9afb0cc04584448c83948e092f16025428c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:42:21 +0100 Subject: [PATCH 492/563] Optimized ruleset selection in CreateMatchView --- .../match_view/create_match/create_match_view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 8eeaca6..5e35f41 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -167,17 +167,19 @@ class _CreateMatchViewState extends State { ? AppLocalizations.of(context)!.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { + final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: _getRulesets(context), + rulesets: rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + if (!mounted) return; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); selectedGameIndex = -1; setState(() {}); }, -- 2.49.1 From bbb7914fc5360bd46a6fd0df4710bc53456e9af6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 16:39:57 +0100 Subject: [PATCH 493/563] Updated sorting logic & added comments --- .../widgets/player_selection.dart | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eb70ae0..5f77e1f 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -149,18 +149,24 @@ class _PlayerSelectionState extends State { onIconTap: () { setState(() { // Removes the player from the selection and notifies the parent. + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); + + // Get the current search query final currentSearch = _searchBarController .text .toLowerCase(); - selectedPlayers.remove(player); - widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), - // they are added back to the suggestions and the list is re-sorted. + // they are added back to the `suggestedPlayers` and the list is re-sorted. if (currentSearch.isEmpty || player.name.toLowerCase().contains( currentSearch, )) { suggestedPlayers.add(player); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); } }); }, @@ -200,11 +206,15 @@ class _PlayerSelectionState extends State { text: suggestedPlayers[index].name, onPressed: () { setState(() { + // If the player is not already selected if (!selectedPlayers.contains( suggestedPlayers[index], )) { - selectedPlayers.add(suggestedPlayers[index]); + // Add to player to the front of the selectedPlayers + selectedPlayers.insert(0, suggestedPlayers[index]); + // Notify the parent widget of the change widget.onChanged([...selectedPlayers]); + // Remove the player from the suggestedPlayers suggestedPlayers.remove(suggestedPlayers[index]); } }); @@ -221,7 +231,7 @@ class _PlayerSelectionState extends State { } /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success or failure. + /// Shows a snackbar indicating success xfor failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { String playerName = _searchBarController.text.trim(); -- 2.49.1 From 0bfaba42259470d758fb7e81f7932f6fe8c84c8b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:40:36 +0100 Subject: [PATCH 494/563] Refactored German localization strings by removing redundant entries --- lib/l10n/arb/app_de.arb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3e090e3..a922996 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -51,15 +51,12 @@ "create_group": "Gruppe erstellen", "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", - "create_game": "Match erstellen", "match_name": "Matchname", "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Keine", "create_match": "Match erstellen", - "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", @@ -70,7 +67,6 @@ "statistics": "Statistiken", "stats": "Statistiken", "players_count": "{count} Spieler", - "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "game_name": "Spielname", "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", -- 2.49.1 From 7bf03ec3889c372bba27225e8948766c124159f0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:42:38 +0100 Subject: [PATCH 495/563] added auto-generated files to git --- lib/l10n/generated/app_localizations.dart | 596 +++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 270 +++++++++ lib/l10n/generated/app_localizations_en.dart | 269 +++++++++ 3 files changed, 1135 insertions(+) create mode 100644 lib/l10n/generated/app_localizations.dart create mode 100644 lib/l10n/generated/app_localizations_de.dart create mode 100644 lib/l10n/generated/app_localizations_en.dart diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart new file mode 100644 index 0000000..71091ab --- /dev/null +++ b/lib/l10n/generated/app_localizations.dart @@ -0,0 +1,596 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_de.dart'; +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'generated/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('de'), + Locale('en'), + ]; + + /// Label for choosing a group + /// + /// In en, this message translates to: + /// **'Choose Group'** + String get choose_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Label for choosing a game + /// + /// In en, this message translates to: + /// **'Choose Game'** + String get choose_game; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Message when no recent matches exist + /// + /// In en, this message translates to: + /// **'No recent matches available'** + String get no_recent_matches_available; + + /// No description provided for @no_second_match_available. + /// + /// In en, this message translates to: + /// **'No second match available'** + String get no_second_match_available; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_while_creating_group_please_try_again; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + + /// Label for all players list + /// + /// In en, this message translates to: + /// **'All players:'** + String get all_players; + + /// Success message when adding a player + /// + /// In en, this message translates to: + /// **'Successfully added player {playerName}.'** + String successfully_added_player(String playerName); + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}.'** + String could_not_add_player(String playerName); + + /// Shows the winner's name + /// + /// In en, this message translates to: + /// **'Winner: {winnerName}'** + String winner(String winnerName); + + /// Players label + /// + /// In en, this message translates to: + /// **'Players'** + String get players; + + /// Message when no data is available + /// + /// In en, this message translates to: + /// **'No data available.'** + String get no_data_available; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Title for recent matches section + /// + /// In en, this message translates to: + /// **'Recent Matches'** + String get recent_matches; + + /// Title for quick create section + /// + /// In en, this message translates to: + /// **'Quick Create'** + String get quick_create; + + /// Label for winner field + /// + /// In en, this message translates to: + /// **'Winner'** + String get winner_label; + + /// Label for ruleset field + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset_label; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Warning message for irreversible actions + /// + /// In en, this message translates to: + /// **'This can\'t be undone'** + String get this_cannot_be_undone; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Error message for unknown exceptions + /// + /// In en, this message translates to: + /// **'Unknown Exception (see console)'** + String get unknown_exception; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Undo button text + /// + /// In en, this message translates to: + /// **'Undo'** + String get undo; + + /// Label for wins statistic + /// + /// In en, this message translates to: + /// **'Wins'** + String get wins; + + /// Label for winrate statistic + /// + /// In en, this message translates to: + /// **'Winrate'** + String get winrate; + + /// Label for amount of matches statistic + /// + /// In en, this message translates to: + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Placeholder for player name input + /// + /// In en, this message translates to: + /// **'Player name'** + String get player_name; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Ruleset label + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset; + + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// None option label + /// + /// In en, this message translates to: + /// **'None'** + String get none; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when all players are added to selection + /// + /// In en, this message translates to: + /// **'All players selected'** + String get all_players_selected; + + /// Date format for today + /// + /// In en, this message translates to: + /// **'Today at {time}'** + String today_at(String time); + + /// Date format for yesterday + /// + /// In en, this message translates to: + /// **'Yesterday at {time}'** + String yesterday_at(String time); + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Statistics tab label + /// + /// In en, this message translates to: + /// **'Statistics'** + String get statistics; + + /// Stats tab label (short) + /// + /// In en, this message translates to: + /// **'Stats'** + String get stats; + + /// Shows the number of players + /// + /// In en, this message translates to: + /// **'{count} Players'** + String players_count(int count); + + /// Message when search returns no groups + /// + /// In en, this message translates to: + /// **'There is no group matching your search'** + String get there_is_no_group_matching_your_search; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner_desc; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser_desc; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points_desc; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points_desc; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['de', 'en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart new file mode 100644 index 0000000..77476f2 --- /dev/null +++ b/lib/l10n/generated/app_localizations_de.dart @@ -0,0 +1,270 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class AppLocalizationsDe extends AppLocalizations { + AppLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get choose_group => 'Gruppe wählen'; + + @override + String get create_new_match => 'Neues Match erstellen'; + + @override + String get choose_ruleset => 'Regelwerk wählen'; + + @override + String get choose_game => 'Spiel wählen'; + + @override + String get select_winner => 'Gewinner wählen:'; + + @override + String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Match verfügbar'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get cancel => 'Abbrechen'; + + @override + String get delete => 'Löschen'; + + @override + String get create_new_group => 'Neue Gruppe erstellen'; + + @override + String get error_while_creating_group_please_try_again => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler: $countString'; + } + + @override + String get no_players_selected => 'Keine Spieler ausgewählt'; + + @override + String get all_players => 'Alle Spieler:'; + + @override + String successfully_added_player(String playerName) { + return 'Spieler $playerName erfolgreich hinzugefügt.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + } + + @override + String winner(String winnerName) { + return 'Gewinner: $winnerName'; + } + + @override + String get players => 'Spieler'; + + @override + String get no_data_available => 'Keine Daten verfügbar.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Gruppen'; + + @override + String get recent_matches => 'Letzte Matches'; + + @override + String get quick_create => 'Schnellzugriff'; + + @override + String get winner_label => 'Gewinner'; + + @override + String get ruleset_label => 'Regelwerk'; + + @override + String get match_in_progress => 'Match läuft...'; + + @override + String get menu => 'Menü'; + + @override + String get settings => 'Einstellungen'; + + @override + String get export_data => 'Daten exportieren'; + + @override + String get import_data => 'Daten importieren'; + + @override + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; + + @override + String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + + @override + String get data_successfully_imported => 'Daten erfolgreich importiert'; + + @override + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get error_reading_file => 'Fehler beim Lesen der Datei'; + + @override + String get import_canceled => 'Import abgebrochen'; + + @override + String get format_exception => 'Formatfehler (siehe Konsole)'; + + @override + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + + @override + String get export_canceled => 'Export abgebrochen'; + + @override + String get undo => 'Rückgängig'; + + @override + String get wins => 'Siege'; + + @override + String get winrate => 'Siegquote'; + + @override + String get amount_of_matches => 'Anzahl der Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler erstellt'; + + @override + String get create_group => 'Gruppe erstellen'; + + @override + String get group_name => 'Gruppenname'; + + @override + String get player_name => 'Spielername'; + + @override + String get no_matches_created_yet => 'Noch keine Matches erstellt'; + + @override + String get match_name => 'Matchname'; + + @override + String get game => 'Spiel'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get group => 'Gruppe'; + + @override + String get none => 'Keine'; + + @override + String get create_match => 'Match erstellen'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler mit diesem Namen gefunden'; + + @override + String get all_players_selected => 'Alle Spieler ausgewählt'; + + @override + String today_at(String time) { + return 'Heute um $time'; + } + + @override + String yesterday_at(String time) { + return 'Gestern um $time'; + } + + @override + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get home => 'Startseite'; + + @override + String get statistics => 'Statistiken'; + + @override + String get stats => 'Statistiken'; + + @override + String players_count(int count) { + return '$count Spieler'; + } + + @override + String get there_is_no_group_matching_your_search => + 'Es gibt keine Gruppe, die deiner Suche entspricht'; + + @override + String get game_name => 'Spielname'; + + @override + String get ruleset_single_winner_desc => + 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get ruleset_single_loser_desc => + 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_most_points_desc => + 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_least_points_desc => + 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + + @override + String get single_winner => 'Ein Gewinner'; + + @override + String get single_loser => 'Ein Verlierer'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get least_points => 'Niedrigste Punkte'; +} diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart new file mode 100644 index 0000000..f6a5b5f --- /dev/null +++ b/lib/l10n/generated/app_localizations_en.dart @@ -0,0 +1,269 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get choose_group => 'Choose Group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String get choose_game => 'Choose Game'; + + @override + String get select_winner => 'Select Winner:'; + + @override + String get no_recent_matches_available => 'No recent matches available'; + + @override + String get no_second_match_available => 'No second match available'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get cancel => 'Cancel'; + + @override + String get delete => 'Delete'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get error_while_creating_group_please_try_again => + 'Error while creating group, please try again'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Selected players: $countString'; + } + + @override + String get no_players_selected => 'No players selected'; + + @override + String get all_players => 'All players:'; + + @override + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName.'; + } + + @override + String winner(String winnerName) { + return 'Winner: $winnerName'; + } + + @override + String get players => 'Players'; + + @override + String get no_data_available => 'No data available.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Groups'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get quick_create => 'Quick Create'; + + @override + String get winner_label => 'Winner'; + + @override + String get ruleset_label => 'Ruleset'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get menu => 'Menu'; + + @override + String get settings => 'Settings'; + + @override + String get export_data => 'Export data'; + + @override + String get import_data => 'Import data'; + + @override + String get this_cannot_be_undone => 'This can\'t be undone'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get unknown_exception => 'Unknown Exception (see console)'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get undo => 'Undo'; + + @override + String get wins => 'Wins'; + + @override + String get winrate => 'Winrate'; + + @override + String get amount_of_matches => 'Amount of Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get create_group => 'Create Group'; + + @override + String get group_name => 'Group name'; + + @override + String get player_name => 'Player name'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get match_name => 'Match name'; + + @override + String get game => 'Game'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get group => 'Group'; + + @override + String get none => 'None'; + + @override + String get create_match => 'Create match'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get all_players_selected => 'All players selected'; + + @override + String today_at(String time) { + return 'Today at $time'; + } + + @override + String yesterday_at(String time) { + return 'Yesterday at $time'; + } + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get home => 'Home'; + + @override + String get statistics => 'Statistics'; + + @override + String get stats => 'Stats'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get there_is_no_group_matching_your_search => + 'There is no group matching your search'; + + @override + String get game_name => 'Game Name'; + + @override + String get ruleset_single_winner_desc => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get ruleset_single_loser_desc => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_most_points_desc => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_least_points_desc => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get single_winner => 'Single Winner'; + + @override + String get single_loser => 'Single Loser'; + + @override + String get most_points => 'Most Points'; + + @override + String get least_points => 'Least Points'; +} -- 2.49.1 From f97c341b81c050574c739216c09759de70f7bc26 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:43:24 +0100 Subject: [PATCH 496/563] added devtools_options.yaml to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e000548..72eb56e 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,4 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/devtools_options.yaml -- 2.49.1 From 15d09f381aaa60798626871fbccb66925e14ff05 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 17:47:15 +0100 Subject: [PATCH 497/563] New created players will be added now at the front too --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 5f77e1f..d497fcd 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -239,7 +239,7 @@ class _PlayerSelectionState extends State { bool success = await db.playerDao.addPlayer(player: createdPlayer); if (!context.mounted) return; if (success) { - selectedPlayers.add(createdPlayer); + selectedPlayers.insert(0, createdPlayer); widget.onChanged([...selectedPlayers]); allPlayers.add(createdPlayer); setState(() { -- 2.49.1 From 81f63c1c070145cfa5b3252518a2bdc152ef0a44 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:09:50 +0100 Subject: [PATCH 498/563] made statistics_view.dart use localization and fix merge error --- .../views/main_menu/statistics_view.dart | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index dabfff5..8fad901 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +87,9 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of( + context, + )!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -95,38 +97,12 @@ class _StatisticsViewState extends State { ), ], ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No statistics available', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_data_available, ), ), - StatisticsTile( - icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: AppLocalizations.of(context)!.amount_of_matches, - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), -- 2.49.1 From 633a21d829d2556ef3a07befc31c12735c6ae2f5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:08 +0100 Subject: [PATCH 499/563] Updated search bar hint texts to use localization for group and player searches --- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 7308dce..bfc0fed 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context)!.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9a1ffd1..939b211 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: 'Search for players', + hintText: AppLocalizations.of(context)!.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, -- 2.49.1 From ad87dca67429366c9da1d2c2950aeb7d458ebd86 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:24 +0100 Subject: [PATCH 500/563] add generated files --- lib/l10n/generated/app_localizations.dart | 14 +++++++++++++- lib/l10n/generated/app_localizations_de.dart | 6 ++++++ lib/l10n/generated/app_localizations_en.dart | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 71091ab..05ce4b6 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -134,7 +134,7 @@ abstract class AppLocalizations { /// **'No recent matches available'** String get no_recent_matches_available; - /// No description provided for @no_second_match_available. + /// Message when no second match exists /// /// In en, this message translates to: /// **'No second match available'** @@ -559,6 +559,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Least Points'** String get least_points; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 77476f2..070cb9a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -267,4 +267,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get least_points => 'Niedrigste Punkte'; + + @override + String get search_for_players => 'Nach Spielern suchen'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index f6a5b5f..8c78e86 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -266,4 +266,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get least_points => 'Least Points'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get search_for_groups => 'Search for groups'; } -- 2.49.1 From bc01a6de9a2ab9a3dd5c757de7b7aa423a0c4145 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:44 +0100 Subject: [PATCH 501/563] Add localization for search input fields in English and German --- lib/l10n/arb/app_de.arb | 4 +++- lib/l10n/arb/app_en.arb | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a922996..f5e4855 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,5 +76,7 @@ "single_winner": "Ein Gewinner", "single_loser": "Ein Verlierer", "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte" + "least_points": "Niedrigste Punkte", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 44a8f67..ae3fa16 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -25,7 +25,7 @@ "description": "Message when no recent matches exist" }, "no_second_match_available": "No second match available", - "@no_second_matcb_available": { + "@no_second_match_available": { "description": "Message when no second match exists" }, "delete_all_data": "Delete all data?", @@ -353,5 +353,13 @@ "least_points": "Least Points", "@least_points": { "description": "Title for least points ruleset" + }, + "search_for_players": "Search for players", + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "search_for_groups": "Search for groups", + "@search_for_groups": { + "description": "Hint text for group search input field" } } -- 2.49.1 From 7103765054aa2f60605d02efdaef48c505e46632 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:17:53 +0100 Subject: [PATCH 502/563] Update statistics_view.dart to use localized message no statistics available instead of no data available --- lib/presentation/views/main_menu/statistics_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8fad901..fc7825a 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,7 +100,9 @@ class _StatisticsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_data_available, + message: AppLocalizations.of( + context, + )!.no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), -- 2.49.1 From 8afba5680b830dfc762b4b8730fbf7f9ffad0a6d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:18:14 +0100 Subject: [PATCH 503/563] Add localization for no statistics available message and removed dots from all messages --- lib/l10n/arb/app_de.arb | 9 +++++---- lib/l10n/arb/app_en.arb | 12 ++++++++---- lib/l10n/generated/app_localizations.dart | 14 ++++++++++---- lib/l10n/generated/app_localizations_de.dart | 9 ++++++--- lib/l10n/generated/app_localizations_en.dart | 9 ++++++--- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f5e4855..4228671 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -15,12 +15,12 @@ "selected_players": "Ausgewählte Spieler: {count}", "no_players_selected": "Keine Spieler ausgewählt", "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", "winner": "Gewinner: {winnerName}", "players": "Spieler", "player_name": "Spielername", - "no_data_available": "Keine Daten verfügbar.", + "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", @@ -78,5 +78,6 @@ "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen" + "search_for_groups": "Nach Gruppen suchen", + "no_data_available": "Keine Daten verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ae3fa16..72e307c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -66,7 +66,7 @@ "@all_players": { "description": "Label for all players list" }, - "successfully_added_player": "Successfully added player {playerName}.", + "successfully_added_player": "Successfully added player {playerName}", "@successfully_added_player": { "description": "Success message when adding a player", "placeholders": { @@ -76,7 +76,7 @@ } } }, - "could_not_add_player": "Could not add player {playerName}.", + "could_not_add_player": "Could not add player {playerName}", "@could_not_add_player": { "description": "Error message when adding a player fails", "placeholders": { @@ -100,9 +100,13 @@ "@players": { "description": "Players label" }, - "no_data_available": "No data available.", + "no_statistics_available": "No statistics available", + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "no_data_available": "No data available", "@no_data_available": { - "description": "Message when no data is available" + "description": "Message when no data in the statistic tiles is given" }, "matches": "Matches", "@matches": { diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 05ce4b6..edd14df 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -191,13 +191,13 @@ abstract class AppLocalizations { /// Success message when adding a player /// /// In en, this message translates to: - /// **'Successfully added player {playerName}.'** + /// **'Successfully added player {playerName}'** String successfully_added_player(String playerName); /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}.'** + /// **'Could not add player {playerName}'** String could_not_add_player(String playerName); /// Shows the winner's name @@ -212,10 +212,16 @@ abstract class AppLocalizations { /// **'Players'** String get players; - /// Message when no data is available + /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: - /// **'No data available.'** + /// **'No statistics available'** + String get no_statistics_available; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** String get no_data_available; /// Label for matches diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 070cb9a..df46454 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -63,12 +63,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt.'; + return 'Spieler $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + return 'Spieler $playerName konnte nicht hinzugefügt werden'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsDe extends AppLocalizations { String get players => 'Spieler'; @override - String get no_data_available => 'Keine Daten verfügbar.'; + String get no_statistics_available => 'Keine Statistiken verfügbar'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; @override String get matches => 'Matches'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 8c78e86..7f000e5 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -63,12 +63,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Successfully added player $playerName.'; + return 'Successfully added player $playerName'; } @override String could_not_add_player(String playerName) { - return 'Could not add player $playerName.'; + return 'Could not add player $playerName'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsEn extends AppLocalizations { String get players => 'Players'; @override - String get no_data_available => 'No data available.'; + String get no_statistics_available => 'No statistics available'; + + @override + String get no_data_available => 'No data available'; @override String get matches => 'Matches'; -- 2.49.1 From 3c3bf506cbe58c9d74c29969490c53b0da0d248c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:37 +0100 Subject: [PATCH 504/563] Add TODO for implementing quick create functionality in home_view.dart --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f0f233..1f8240d 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -173,6 +173,7 @@ class _HomeViewState extends State { ), ), ), + // TODO: Implement quick create functionality InfoTile( width: constraints.maxWidth * 0.95, title: AppLocalizations.of(context)!.quick_create, @@ -221,7 +222,6 @@ class _HomeViewState extends State { ], ), ), - ], ), ), ); -- 2.49.1 From d77b5f20f99aa2e077bbed9b073d3ae8777d1687 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:53 +0100 Subject: [PATCH 505/563] Update statistics_view.dart to use localized 'not available' message for players --- lib/presentation/views/main_menu/statistics_view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index fc7825a..ed90a84 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -152,7 +152,10 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -214,7 +217,10 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); matchCounts[i] = (player.name, matchCounts[i].$2); } -- 2.49.1 From cc23c03f6b44d896e53967bd20224f77ee242a2d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:32:01 +0100 Subject: [PATCH 506/563] Add localization for 'not available' message in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 3 +++ lib/l10n/generated/app_localizations_en.dart | 3 +++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4228671..c3605da 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -79,5 +79,6 @@ "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar" + "no_data_available": "Keine Daten verfügbar", + "not_available": "Nicht verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 72e307c..a16d327 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -365,5 +365,9 @@ "search_for_groups": "Search for groups", "@search_for_groups": { "description": "Hint text for group search input field" + }, + "not_available": "Not available", + "@not_available": { + "description": "Abbreviation for not available" } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index edd14df..c7036d8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -577,6 +577,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Search for groups'** String get search_for_groups; + + /// Abbreviation for not available + /// + /// In en, this message translates to: + /// **'Not available'** + String get not_available; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index df46454..247e3e3 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -276,4 +276,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get not_available => 'Nicht verfügbar'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7f000e5..a4f62e0 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -275,4 +275,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get search_for_groups => 'Search for groups'; + + @override + String get not_available => 'Not available'; } -- 2.49.1 From 00519901685e782f58def671444b3d966b66d6b4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:43:06 +0100 Subject: [PATCH 507/563] Remove auto_localize dependency from pubspec.yaml --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 866f662..e79ca17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: intl: any flutter_localizations: sdk: flutter - auto_localize: ^0.0.5 dev_dependencies: flutter_test: -- 2.49.1 From 25fe10eb9a3896434d6051a6b389d4baf858724c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:28 +0100 Subject: [PATCH 508/563] added missing square bracket --- lib/presentation/views/main_menu/home_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f8240d..680afde 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -222,6 +222,7 @@ class _HomeViewState extends State { ], ), ), + ], ), ), ); -- 2.49.1 From f22595e6782e3ebff54e121befe5073bb1e92860 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:43 +0100 Subject: [PATCH 509/563] Add localization for 'none_group' option in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 5 ++++- lib/l10n/generated/app_localizations_en.dart | 3 +++ .../match_view/create_match/create_match_view.dart | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c3605da..26ed145 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -55,7 +55,8 @@ "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", - "none": "Keine", + "none": "Kein", + "none_group": "Keine", "create_match": "Match erstellen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a16d327..0a68994 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -256,6 +256,10 @@ "@none": { "description": "None option label" }, + "none_group": "None", + "@none_group": { + "description": "None group option label" + }, "create_match": "Create match", "@create_match": { "description": "Button text to create a match" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index c7036d8..5152962 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -446,6 +446,12 @@ abstract class AppLocalizations { /// **'None'** String get none; + /// None group option label + /// + /// In en, this message translates to: + /// **'None'** + String get none_group; + /// Button text to create a match /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 247e3e3..a270c82 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -195,7 +195,10 @@ class AppLocalizationsDe extends AppLocalizations { String get group => 'Gruppe'; @override - String get none => 'Keine'; + String get none => 'Kein'; + + @override + String get none_group => 'Keine'; @override String get create_match => 'Match erstellen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index a4f62e0..63a7d0e 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -196,6 +196,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get none => 'None'; + @override + String get none_group => 'None'; + @override String get create_match => 'Create match'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 5e35f41..b72a4fa 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -187,7 +187,7 @@ class _CreateMatchViewState extends State { ChooseTile( title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context)!.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( -- 2.49.1 From 5ee0d59377a70b31bfe69eddd25277fed3f9678b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 19:13:50 +0100 Subject: [PATCH 510/563] implement optional match name with game name as default --- .../create_match/create_match_view.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f3b4d79..f2f86be 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,6 +31,8 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); + String hintText = "Match Name"; + /// List of all groups from the database List groupsList = []; @@ -132,7 +134,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: 'Match name', + hintText: hintText, ), ), ChooseTile( @@ -151,11 +153,13 @@ class _CreateMatchViewState extends State { ); setState(() { if (selectedGameIndex != -1) { + hintText = games[selectedGameIndex].$1; selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); } else { + hintText = "Match Name"; selectedRuleset = null; } }); @@ -228,7 +232,9 @@ class _CreateMatchViewState extends State { onPressed: _enableCreateGameButton() ? () async { Match match = Match( - name: _matchNameController.text.trim(), + name: _matchNameController.text.isEmpty + ? hintText.trim() + : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, @@ -258,9 +264,8 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _matchNameController.text.isNotEmpty && - (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1)) && - selectedRuleset != null; + return selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.length > 1) && + selectedRuleset != null; } } -- 2.49.1 From db30b0fd5ab004fbacb1a7e72dfecdbb6eda66eb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 19:15:57 +0100 Subject: [PATCH 511/563] removed double quotes --- .../main_menu/match_view/create_match/create_match_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f2f86be..05da60d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,7 +31,7 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); - String hintText = "Match Name"; + String hintText = 'Match Name'; /// List of all groups from the database List groupsList = []; @@ -159,7 +159,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = "Match Name"; + hintText = 'Match Name'; selectedRuleset = null; } }); -- 2.49.1 From 55a22b292f3cda7ddb1183dff4c5a758835e3513 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 21:40:18 +0100 Subject: [PATCH 512/563] changed bottom padding --- lib/presentation/views/main_menu/match_view/match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..96d40fb 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -71,7 +71,7 @@ class _MatchViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == matches.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + height: MediaQuery.paddingOf(context).bottom - 20, ); } return GameHistoryTile( -- 2.49.1 From 6899bb0c3c48f61d8f4f78d541674e8744de8d95 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 21:47:13 +0100 Subject: [PATCH 513/563] add comment --- .../main_menu/match_view/create_match/create_match_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 05da60d..6ef53ba 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,6 +31,7 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); + /// Hint text for the match name input field String hintText = 'Match Name'; /// List of all groups from the database -- 2.49.1 From 1d4fdae84b0a3410ee5df6470727878835a34337 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 22:40:26 +0100 Subject: [PATCH 514/563] Hotfix: Missing game renames --- lib/presentation/views/main_menu/home_view.dart | 12 ++++++------ .../views/main_menu/match_view/match_view.dart | 10 +++++----- .../{game_tile.dart => match_summary_tile.dart} | 8 ++++---- .../{game_history_tile.dart => match_tile.dart} | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) rename lib/presentation/widgets/tiles/{game_tile.dart => match_summary_tile.dart} (92%) rename lib/presentation/widgets/tiles/{game_history_tile.dart => match_tile.dart} (95%) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3e221e7..8f911cd 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -6,8 +6,8 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; @@ -112,13 +112,13 @@ class _HomeViewState extends State { visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, - child: Text('No recent games available'), + child: Text('No recent matches available'), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - MatchTile( + MatchSummaryTile( matchTitle: recentMatches[0].name, game: 'Winner', ruleset: 'Ruleset', @@ -132,20 +132,20 @@ class _HomeViewState extends State { child: Divider(), ), if (loadedRecentMatches.length > 1) ...[ - MatchTile( + MatchSummaryTile( matchTitle: recentMatches[1].name, game: 'Winner', ruleset: 'Ruleset', players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null - ? 'Game in progress...' + ? 'Match in progress...' : recentMatches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ const Center( heightFactor: 5.35, - child: Text('No second game available'), + child: Text('No second match available'), ), ], ], diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..a5f6200 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -12,7 +12,7 @@ import 'package:game_tracker/presentation/views/main_menu/match_view/create_matc import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; @@ -30,9 +30,9 @@ class _MatchViewState extends State { List matches = List.filled( 4, Match( - name: 'Skeleton Gamename', + name: 'Skeleton match name', group: Group( - name: 'Groupname', + name: 'Group name', members: List.filled(5, Player(name: 'Player')), ), winner: Player(name: 'Player'), @@ -74,7 +74,7 @@ class _MatchViewState extends State { height: MediaQuery.paddingOf(context).bottom - 80, ); } - return GameHistoryTile( + return MatchTile( onTap: () async { Navigator.push( context, @@ -96,7 +96,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Game', + text: 'Create Match', sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/match_summary_tile.dart similarity index 92% rename from lib/presentation/widgets/tiles/game_tile.dart rename to lib/presentation/widgets/tiles/match_summary_tile.dart index f98f425..719037b 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/match_summary_tile.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class MatchTile extends StatefulWidget { +class MatchSummaryTile extends StatefulWidget { final String matchTitle; final String game; final String ruleset; final String players; final String winner; - const MatchTile({ + const MatchSummaryTile({ super.key, required this.matchTitle, required this.game, @@ -19,10 +19,10 @@ class MatchTile extends StatefulWidget { }); @override - State createState() => _MatchTileState(); + State createState() => _MatchSummaryTileState(); } -class _MatchTileState extends State { +class _MatchSummaryTileState extends State { @override Widget build(BuildContext context) { return Column( diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart similarity index 95% rename from lib/presentation/widgets/tiles/game_history_tile.dart rename to lib/presentation/widgets/tiles/match_tile.dart index f79edc3..543a542 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -4,17 +4,17 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; -class GameHistoryTile extends StatefulWidget { +class MatchTile extends StatefulWidget { final Match match; final VoidCallback onTap; - const GameHistoryTile({super.key, required this.match, required this.onTap}); + const MatchTile({super.key, required this.match, required this.onTap}); @override - State createState() => _GameHistoryTileState(); + State createState() => _MatchTileState(); } -class _GameHistoryTileState extends State { +class _MatchTileState extends State { @override Widget build(BuildContext context) { final group = widget.match.group; -- 2.49.1 From b0073addf84062cf7c3fc99d4bfbc003aa95fce8 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Thu, 1 Jan 2026 22:45:55 +0100 Subject: [PATCH 515/563] remove unnecessary trim --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 6ef53ba..a91a45b 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,7 +234,7 @@ class _CreateMatchViewState extends State { ? () async { Match match = Match( name: _matchNameController.text.isEmpty - ? hintText.trim() + ? hintText : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, -- 2.49.1 From 304fa82a93612401260c8159e3f117f114e3ab9c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 23:14:52 +0100 Subject: [PATCH 516/563] Fixed bug in player selection --- .../widgets/player_selection.dart | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d497fcd..c7191a4 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -12,13 +12,13 @@ import 'package:provider/provider.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; - final List availablePlayers; + final List? availablePlayers; final List? initialSelectedPlayers; const PlayerSelection({ super.key, required this.onChanged, - this.availablePlayers = const [], + this.availablePlayers, this.initialSelectedPlayers, }); @@ -56,17 +56,17 @@ class _PlayerSelectionState extends State { if (mounted) { _allPlayersFuture.then((loadedPlayers) { setState(() { - // If a list of available players is provided, use that list. - if (widget.availablePlayers.isNotEmpty) { - widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers]; + // If a list of available players is provided (even if empty), use that list. + if (widget.availablePlayers != null) { + widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers!]; suggestedPlayers = [...allPlayers]; if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. selectedPlayers = widget.initialSelectedPlayers! .where( - (p) => widget.availablePlayers.any( + (p) => widget.availablePlayers!.any( (available) => available.id == p.id, ), ) @@ -193,11 +193,7 @@ class _PlayerSelectionState extends State { replacement: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: allPlayers.isEmpty - ? 'No players created yet' - : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add' - : 'No players found with that name', + message: _getInfoText(), ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -273,4 +269,22 @@ class _PlayerSelectionState extends State { ); } } + + /// Determines the appropriate info text to display when no players + /// are available in the suggested players list. + String _getInfoText() { + if (widget.availablePlayers != null && widget.availablePlayers!.isEmpty) { + // Available players list is provided but empty + return 'All players added to the match'; + } else if (allPlayers.isEmpty) { + // No players exist in the database + return 'No players created yet'; + } else if (selectedPlayers.length == allPlayers.length) { + // All players have been selected + return 'No more players to add'; + } else { + // No players match the search query + return 'No players found with that name'; + } + } } -- 2.49.1 From cb7804cf55a66b2e5bc6a9fd367d1256e9f9de29 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 2 Jan 2026 18:38:13 +0100 Subject: [PATCH 517/563] Fixed Problem with widget state --- .../main_menu/match_view/create_match/create_match_view.dart | 4 +++- lib/presentation/widgets/player_selection.dart | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index a91a45b..9923d7a 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -110,8 +110,10 @@ class _CreateMatchViewState extends State { ]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; + setState(() { + filteredPlayerList = List.from(playerList); + }); }); - filteredPlayerList = List.from(playerList); } @override diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index c7191a4..112df65 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -182,9 +182,6 @@ class _PlayerSelectionState extends State { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - /* - - */ Expanded( child: AppSkeleton( enabled: isLoading, -- 2.49.1 From 87856e6548d4a8ed9595d3f752b709ecf081f690 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 2 Jan 2026 18:41:39 +0100 Subject: [PATCH 518/563] Simplified info texts --- lib/presentation/widgets/player_selection.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 112df65..e9aacd3 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -270,14 +270,13 @@ class _PlayerSelectionState extends State { /// Determines the appropriate info text to display when no players /// are available in the suggested players list. String _getInfoText() { - if (widget.availablePlayers != null && widget.availablePlayers!.isEmpty) { - // Available players list is provided but empty - return 'All players added to the match'; - } else if (allPlayers.isEmpty) { + if (allPlayers.isEmpty) { // No players exist in the database return 'No players created yet'; - } else if (selectedPlayers.length == allPlayers.length) { - // All players have been selected + } else if (selectedPlayers.length == allPlayers.length || + widget.availablePlayers?.isEmpty == true) { + // All players have been selected or + // available players list is provided but empty return 'No more players to add'; } else { // No players match the search query -- 2.49.1 From 3c22b084d63ece1fce8e512a1c72dc3882f65a45 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 19:01:44 +0100 Subject: [PATCH 519/563] made getters non nullable and removed all null assertion operators --- l10n.yaml | 3 +- lib/core/enums.dart | 8 ++-- lib/l10n/generated/app_localizations.dart | 4 +- .../main_menu/custom_navigation_bar.dart | 16 ++++---- .../group_view/create_group_view.dart | 6 +-- .../main_menu/group_view/groups_view.dart | 6 +-- .../views/main_menu/home_view.dart | 12 +++--- .../create_match/choose_game_view.dart | 4 +- .../create_match/choose_group_view.dart | 10 ++--- .../create_match/choose_ruleset_view.dart | 2 +- .../create_match/create_match_view.dart | 24 ++++++------ .../match_view/match_result_view.dart | 2 +- .../main_menu/match_view/match_view.dart | 6 +-- .../views/main_menu/settings_view.dart | 38 +++++++++---------- .../views/main_menu/statistics_view.dart | 10 ++--- .../widgets/player_selection.dart | 14 +++---- .../widgets/tiles/match_tile.dart | 6 +-- .../widgets/tiles/statistics_tile.dart | 2 +- 18 files changed, 87 insertions(+), 86 deletions(-) diff --git a/l10n.yaml b/l10n.yaml index f5730dc..f7805c8 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,5 @@ arb-dir: lib/l10n/arb template-arb-file: app_en.arb output-localization-file: app_localizations.dart -output-dir: lib/l10n/generated \ No newline at end of file +output-dir: lib/l10n/generated +nullable-getter: false \ No newline at end of file diff --git a/lib/core/enums.dart b/lib/core/enums.dart index fc1ac91..74ae023 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -37,12 +37,12 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context)!.single_winner; + return AppLocalizations.of(context).single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context)!.single_loser; + return AppLocalizations.of(context).single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context)!.most_points; + return AppLocalizations.of(context).most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context)!.least_points; + return AppLocalizations.of(context).least_points; } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5152962..e3acecb 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -67,8 +67,8 @@ abstract class AppLocalizations { final String localeName; - static AppLocalizations? of(BuildContext context) { - return Localizations.of(context, AppLocalizations); + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; } static const LocalizationsDelegate delegate = diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 980bf1a..2faef8b 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -90,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context)!.home, + label: AppLocalizations.of(context).home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context)!.matches, + label: AppLocalizations.of(context).matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context)!.groups, + label: AppLocalizations.of(context).groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context)!.statistics, + label: AppLocalizations.of(context).statistics, onTabTapped: onTabTapped, ), ], @@ -132,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return AppLocalizations.of(context)!.home; + return AppLocalizations.of(context).home; case 1: - return AppLocalizations.of(context)!.matches; + return AppLocalizations.of(context).matches; case 2: - return AppLocalizations.of(context)!.groups; + return AppLocalizations.of(context).groups; case 3: - return AppLocalizations.of(context)!.statistics; + return AppLocalizations.of(context).statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index e9e5fe1..eddcf6d 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -45,7 +45,7 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_group, + AppLocalizations.of(context).create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context).group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 0e9ddbb..4d42417 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -53,8 +53,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3fe85fd..e1dd9f8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -87,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.matches, + title: AppLocalizations.of(context).matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.groups, + title: AppLocalizations.of(context).groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +105,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.recent_matches, + title: AppLocalizations.of(context).recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,7 +125,7 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context)!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, )!.ruleset_label, @@ -175,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.quick_create, + title: AppLocalizations.of(context).quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -232,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context)!.players_count(playerCount); + return AppLocalizations.of(context).players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 6a75ae1..8e40ab8 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -43,7 +43,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_game, + AppLocalizations.of(context).choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -54,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context)!.game_name, + hintText: AppLocalizations.of(context).game_name, ), ), const SizedBox(height: 5), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index bfc0fed..83997e7 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -52,7 +52,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_group, + AppLocalizations.of(context).choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.search_for_groups, + hintText: AppLocalizations.of(context).search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -78,12 +78,12 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.there_is_no_group_matching_your_search, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index a6129cc..9383be2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -48,7 +48,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_ruleset, + AppLocalizations.of(context).choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 63da16d..03aa6c0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -91,19 +91,19 @@ class _CreateMatchViewState extends State { return [ ( Ruleset.singleWinner, - AppLocalizations.of(context)!.ruleset_single_winner_desc, + AppLocalizations.of(context).ruleset_single_winner_desc, ), ( Ruleset.singleLoser, - AppLocalizations.of(context)!.ruleset_single_loser_desc, + AppLocalizations.of(context).ruleset_single_loser_desc, ), ( Ruleset.mostPoints, - AppLocalizations.of(context)!.ruleset_most_points_desc, + AppLocalizations.of(context).ruleset_most_points_desc, ), ( Ruleset.leastPoints, - AppLocalizations.of(context)!.ruleset_least_points_desc, + AppLocalizations.of(context).ruleset_least_points_desc, ), ]; } @@ -122,7 +122,7 @@ class _CreateMatchViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_match, + AppLocalizations.of(context).create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -139,9 +139,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context)!.game, + title: AppLocalizations.of(context).game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -167,9 +167,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.ruleset, + title: AppLocalizations.of(context).ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -190,9 +190,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.group, + title: AppLocalizations.of(context).group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none_group + ? AppLocalizations.of(context).none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -229,7 +229,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index f13ef87..e99ce85 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -81,7 +81,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context)!.select_winner, + AppLocalizations.of(context).select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 30804de..2b1b110 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -62,8 +62,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_matches_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 8899e40..3ab401f 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -29,7 +29,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.menu, + AppLocalizations.of(context).menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +43,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.settings, + AppLocalizations.of(context).settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +51,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context)!.export_data, + title: AppLocalizations.of(context).export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.import_data, + title: AppLocalizations.of(context).import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,7 +78,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.delete_all_data, + title: AppLocalizations.of(context).delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { @@ -86,19 +86,19 @@ class _SettingsViewState extends State { context: context, builder: (context) => AlertDialog( title: Text( - AppLocalizations.of(context)!.delete_all_data, + AppLocalizations.of(context).delete_all_data, ), content: Text( - AppLocalizations.of(context)!.this_cannot_be_undone, + AppLocalizations.of(context).this_cannot_be_undone, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context)!.cancel), + child: Text(AppLocalizations.of(context).cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context)!.delete), + child: Text(AppLocalizations.of(context).delete), ), ], ), @@ -134,32 +134,32 @@ class _SettingsViewState extends State { case ImportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_imported, + message: AppLocalizations.of(context).data_successfully_imported, ); case ImportResult.invalidSchema: showSnackbar( context: context, - message: AppLocalizations.of(context)!.invalid_schema, + message: AppLocalizations.of(context).invalid_schema, ); case ImportResult.fileReadError: showSnackbar( context: context, - message: AppLocalizations.of(context)!.error_reading_file, + message: AppLocalizations.of(context).error_reading_file, ); case ImportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.import_canceled, + message: AppLocalizations.of(context).import_canceled, ); case ImportResult.formatException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.format_exception, + message: AppLocalizations.of(context).format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -176,17 +176,17 @@ class _SettingsViewState extends State { case ExportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_exported, + message: AppLocalizations.of(context).data_successfully_exported, ); case ExportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.export_canceled, + message: AppLocalizations.of(context).export_canceled, ); case ExportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -212,7 +212,7 @@ class _SettingsViewState extends State { duration: duration, action: action != null ? SnackBarAction( - label: AppLocalizations.of(context)!.undo, + label: AppLocalizations.of(context).undo, onPressed: action, ) : null, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index ed90a84..d752119 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, + title: AppLocalizations.of(context).wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, + title: AppLocalizations.of(context).winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -99,7 +99,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.no_statistics_available, @@ -154,7 +154,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); winCounts[i] = (player.name, winCounts[i].$2); @@ -219,7 +219,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); matchCounts[i] = (player.name, matchCounts[i].$2); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index dc8731c..d33d83e 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context)!.search_for_players, + hintText: AppLocalizations.of(context).search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -141,7 +141,7 @@ class _PlayerSelectionState extends State { child: selectedPlayers.isEmpty ? Center( child: Text( - AppLocalizations.of(context)!.no_players_selected, + AppLocalizations.of(context).no_players_selected, ), ) : SingleChildScrollView( @@ -185,7 +185,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context)!.all_players, + AppLocalizations.of(context).all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -199,11 +199,11 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: allPlayers.isEmpty - ? AppLocalizations.of(context)!.no_players_created_yet + ? AppLocalizations.of(context).no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? AppLocalizations.of(context)!.all_players_selected + ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, )!.no_players_found_with_that_name, @@ -276,7 +276,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context)!.could_not_add_player(playerName), + AppLocalizations.of(context).could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 9eedec6..93e6f32 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -98,7 +98,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context)!.winner(winner.name), + AppLocalizations.of(context).winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +115,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context)!.players, + AppLocalizations.of(context).players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -150,7 +150,7 @@ class _MatchTileState extends State { context, )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context)!.days_ago(difference.inDays); + return AppLocalizations.of(context).days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index e783ab4..8d81270 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -36,7 +36,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context)!.no_data_available), + child: Text(AppLocalizations.of(context).no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From 4a67dae45617178f7103394434b7bc83d9415dee Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 2 Jan 2026 20:37:10 +0100 Subject: [PATCH 520/563] change import/export json logic to remove redundant data --- lib/services/data_transfer_service.dart | 128 +++++++++++++++++------- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 957d8fd..2fc5c42 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -31,9 +31,25 @@ class DataTransferService { // Construct a JSON representation of the data final Map jsonMap = { - 'matches': matches.map((match) => match.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), + 'players': players.map((p) => p.toJson()).toList(), + + 'groups': groups + .map((g) => { + 'id': g.id, + 'name': g.name, + 'createdAt': g.createdAt.toIso8601String(), + 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + }).toList(), + + 'matches': matches + .map((m) => { + 'id': m.id, + 'name': m.name, + 'createdAt': m.createdAt.toIso8601String(), + 'groupId': m.group?.id, + 'playerIds': (m.players ?? []).map((p) => p.id).toList(), + 'winnerId': m.winner?.id, + }).toList(), }; return json.encode(jsonMap); @@ -46,7 +62,7 @@ class DataTransferService { /// [fileName] The desired name for the exported file (without extension). static Future exportData( String jsonString, - String fileName, + String fileName ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); @@ -54,11 +70,13 @@ class DataTransferService { fileName: '$fileName.json', bytes: bytes, ); + if (path == null) { return ExportResult.canceled; } else { return ExportResult.success; } + } catch (e, stack) { print('[exportData] $e'); print(stack); @@ -81,42 +99,78 @@ class DataTransferService { try { final jsonString = await _readFileContent(path.files.single); - if (jsonString == null) { - return ImportResult.fileReadError; - } + if (jsonString == null) return ImportResult.fileReadError; - if (await _validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; + final isValid = await _validateJsonSchema(jsonString); + if (!isValid) return ImportResult.invalidSchema; - final List? matchesJson = - jsonData['matches'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; + final dynamic decoded = json.decode(jsonString); + if (decoded is! Map) return ImportResult.invalidSchema; - final List importedMatches = - matchesJson - ?.map((g) => Match.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; + final List playersJson = (decoded['players'] as List?) ?? []; + final List groupsJson = (decoded['groups'] as List?) ?? []; + final List matchesJson = (decoded['matches'] as List?) ?? []; + + // Players + final List importedPlayers = playersJson + .map((p) => Player.fromJson(p as Map)) + .toList(); + + final Map playerById = { + for (final p in importedPlayers) p.id: p, + }; + + // Groups + final List importedGroups = groupsJson.map((g) { + final map = g as Map; + final memberIds = (map['memberIds'] as List? ?? []).cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Group( + id: map['id'] as String, + name: map['name'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + + final Map groupById = { + for (final g in importedGroups) g.id: g, + }; + + // Matches + final List importedMatches = matchesJson.map((m) { + final map = m as Map; + + final String? groupId = map['groupId'] as String?; + final List playerIds = (map['playerIds'] as List? ?? []).cast(); + final String? winnerId = map['winnerId'] as String?; + + final group = (groupId == null) ? null : groupById[groupId]; + final players = playerIds + .map((id) => playerById[id]) + .whereType() + .toList(); + final winner = (winnerId == null) ? null : playerById[winnerId]; + + return Match( + id: map['id'] as String, + name: map['name'] as String, + group: group, + players: players, + createdAt: DateTime.parse(map['createdAt'] as String), + winner: winner, + ); + }).toList(); + + await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.matchDao.addMatchAsList(matches: importedMatches); - await db.playerDao.addPlayersAsList(players: importedPlayers); - await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.matchDao.addMatchAsList(matches: importedMatches); - } else { - return ImportResult.invalidSchema; - } return ImportResult.success; } on FormatException catch (e, stack) { print('[importData] FormatException'); @@ -159,4 +213,4 @@ class DataTransferService { return false; } } -} +} \ No newline at end of file -- 2.49.1 From 22fcff73bd3aa0ec2b3e4c1239c1fe6716860a8e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:11:37 +0100 Subject: [PATCH 521/563] configure en locale as fallback --- lib/main.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 0f3b6b0..a232256 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,9 +20,21 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: (locale, supportedLocales) { + for (final supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return supportedLocales.firstWhere( + (locale) => locale.languageCode == 'en', + orElse: () => supportedLocales.first, + ); + }, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), -- 2.49.1 From a038c22ba69d78ad50d4bd6460e55730c9c838ff Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:13:00 +0100 Subject: [PATCH 522/563] removed else in fallback --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index a232256..8ef243a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,7 +32,6 @@ class GameTracker extends StatelessWidget { } return supportedLocales.firstWhere( (locale) => locale.languageCode == 'en', - orElse: () => supportedLocales.first, ); }, debugShowCheckedModeBanner: false, -- 2.49.1 From 678ab90af37e73f7c901684f2a7b427aa49eba57 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:16:13 +0100 Subject: [PATCH 523/563] removed unneccessary null assertion operators --- .../main_menu/group_view/create_group_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 16 +++++++--------- .../create_match/choose_group_view.dart | 2 +- .../views/main_menu/settings_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 6 ++---- lib/presentation/widgets/player_selection.dart | 6 +++--- lib/presentation/widgets/tiles/match_tile.dart | 4 ++-- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index eddcf6d..0d8c28f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -99,7 +99,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - )!.error_while_creating_group_please_try_again, + ).error_while_creating_group_please_try_again, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index e1dd9f8..6446ea8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -116,7 +116,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_recent_matches_available, + ).no_recent_matches_available, ), ), child: Column( @@ -128,12 +128,12 @@ class _HomeViewState extends State { game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -143,17 +143,15 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of( - context, - )!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), @@ -163,7 +161,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_second_match_available, + ).no_second_match_available, ), ), ], diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 83997e7..d05fff9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -86,7 +86,7 @@ class _ChooseGroupViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.there_is_no_group_matching_your_search, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 3ab401f..c554950 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -109,7 +109,7 @@ class _SettingsViewState extends State { context: context, message: AppLocalizations.of( context, - )!.data_successfully_deleted, + ).data_successfully_deleted, ); } }); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index d752119..43de037 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -87,9 +87,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of( - context, - )!.amount_of_matches, + title: AppLocalizations.of(context).amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -102,7 +100,7 @@ class _StatisticsViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.no_statistics_available, + ).no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d33d83e..af95081 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -132,7 +132,7 @@ class _PlayerSelectionState extends State { Text( AppLocalizations.of( context, - )!.selected_players(selectedPlayers.length), + ).selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -206,7 +206,7 @@ class _PlayerSelectionState extends State { ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, - )!.no_players_found_with_that_name, + ).no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -264,7 +264,7 @@ class _PlayerSelectionState extends State { child: Text( AppLocalizations.of( context, - )!.successfully_added_player(playerName), + ).successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 93e6f32..7727827 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -144,11 +144,11 @@ class _MatchTileState extends State { if (difference.inDays == 0) { return AppLocalizations.of( context, - )!.today_at(DateFormat('HH:mm').format(dateTime)); + ).today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { return AppLocalizations.of( context, - )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); + ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { return AppLocalizations.of(context).days_ago(difference.inDays); } else { -- 2.49.1 From bdc3453d7fc9072bf8951314e2b8c204d3e8f357 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:20:30 +0100 Subject: [PATCH 524/563] correct spelling mistake --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e9aacd3..6ca6373 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -224,7 +224,7 @@ class _PlayerSelectionState extends State { } /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success xfor failure. + /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { String playerName = _searchBarController.text.trim(); -- 2.49.1 From 77095725de452c62f1cf994d8c133c491c18b569 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:26:54 +0100 Subject: [PATCH 525/563] fix game choose view highlighting not working --- .../main_menu/match_view/create_match/choose_game_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 8e40ab8..8e6eceb 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -69,6 +69,7 @@ class _ChooseGameViewState extends State { widget.games[index].$3, context, ), + isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { if (selectedGameIndex == index) { -- 2.49.1 From 132966f3d26145cbf4337818087b6bd13db4f96a Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:28:46 +0100 Subject: [PATCH 526/563] increase title_description_list_tile width to make german translation fully visible --- lib/presentation/widgets/tiles/title_description_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 7a138a0..465c94d 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -54,7 +54,7 @@ class TitleDescriptionListTile extends StatelessWidget { if (badgeText != null) ...[ const Spacer(), Container( - constraints: const BoxConstraints(maxWidth: 100), + constraints: const BoxConstraints(maxWidth: 115), margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, -- 2.49.1 From e4ae526d93e78c0b7bdbb887462f5bf025f978ff Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 10:16:28 +0100 Subject: [PATCH 527/563] fix choose_game_view not saving --- .../create_match/choose_game_view.dart | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 53a4fcb..20e1d14 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -47,39 +47,50 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar( - controller: searchBarController, - hintText: 'Game Name', + body: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + print(result); + print(didPop); + if (didPop) { + return; + } + Navigator.of(context).pop(selectedGameIndex); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', + ), ), - ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - if (selectedGameIndex == index) { - selectedGameIndex = -1; - } else { - selectedGameIndex = index; - } - }); - }, - ); - }, + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + if (selectedGameIndex == index) { + selectedGameIndex = -1; + } else { + selectedGameIndex = index; + } + }); + }, + ); + }, + ), ), - ), - ], + ], + ), ), ); } -- 2.49.1 From 16e1f542f57774055be6b7fc7b05de250ecd0983 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 11:02:38 +0100 Subject: [PATCH 528/563] fix choose rulset & choose group view not saving --- .../create_match/choose_group_view.dart | 121 ++++++++++-------- .../create_match/choose_ruleset_view.dart | 49 ++++--- 2 files changed, 101 insertions(+), 69 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 37096b9..92b66ad 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -57,61 +57,78 @@ class _ChooseGroupViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar( - controller: controller, - hintText: hintText, - onChanged: (value) { - setState(() { - filterGroups(value); - }); - }, - ), - ), - Expanded( - child: Visibility( - visible: filteredGroups.isNotEmpty, - replacement: Visibility( - visible: widget.groups.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'You have no groups created yet', - ), - child: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', - ), - ), - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: filteredGroups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupId != filteredGroups[index].id) { - selectedGroupId = filteredGroups[index].id; - } else { - selectedGroupId = ''; - } - }); - }, - child: GroupTile( - group: filteredGroups[index], - isHighlighted: - selectedGroupId == filteredGroups[index].id, - ), - ); + body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedGroupId and therefore the selected Group wasn't saved + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) { + return; + } + Navigator.of(context).pop( + selectedGroupId == '' + ? null + : widget.groups.firstWhere( + (group) => group.id == selectedGroupId, + ), + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( + controller: controller, + hintText: hintText, + onChanged: (value) { + setState(() { + filterGroups(value); + }); }, ), ), - ), - ], + Expanded( + child: Visibility( + visible: filteredGroups.isNotEmpty, + replacement: Visibility( + visible: widget.groups.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'You have no groups created yet', + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'There is no group matching your search', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: filteredGroups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; + } else { + selectedGroupId = ''; + } + }); + }, + child: GroupTile( + group: filteredGroups[index], + isHighlighted: + selectedGroupId == filteredGroups[index].id, + ), + ); + }, + ), + ), + ), + ], + ), ), ); } diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 537f749..479106c 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -52,25 +52,40 @@ class _ChooseRulesetViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - if (selectedRulesetIndex == index) { - selectedRulesetIndex = -1; - } else { - selectedRulesetIndex = index; - } - }); - }, - title: translateRulesetToString(widget.rulesets[index].$1), - description: widget.rulesets[index].$2, - isHighlighted: selectedRulesetIndex == index, + body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedRulesetIndex and therefore the selected Ruleset wasn't saved + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) { + return; + } + Navigator.of(context).pop( + selectedRulesetIndex == -1 + ? null + : widget.rulesets[selectedRulesetIndex].$1, ); }, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + onPressed: () async { + setState(() { + if (selectedRulesetIndex == index) { + selectedRulesetIndex = -1; + } else { + selectedRulesetIndex = index; + } + }); + }, + title: translateRulesetToString(widget.rulesets[index].$1), + description: widget.rulesets[index].$2, + isHighlighted: selectedRulesetIndex == index, + ); + }, + ), ), ), ); -- 2.49.1 From 528966032706008dc4cfc26a73d1b3a456c172bc Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 11:02:43 +0100 Subject: [PATCH 529/563] add comment --- .../main_menu/match_view/create_match/choose_game_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 20e1d14..e21e868 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -48,10 +48,10 @@ class _ChooseGameViewState extends State { centerTitle: true, ), body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedGameIndex and therefore the selected Game wasn't saved canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) { - print(result); - print(didPop); if (didPop) { return; } -- 2.49.1 From cecfd5da4e817d78f9c1ff7282fb97832af786d5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:19:58 +0100 Subject: [PATCH 530/563] update schema.json to current data import version --- assets/schema.json | 244 +++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 162 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index c80915c..fa748d9 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -2,178 +2,98 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "games": { + "players": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "players": { - "type": [ - "array", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "group": { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } - }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] + "createdAt": { + "type": "string" }, - "winner": { - "type": ["object","null"] - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] + } }, "groups": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] - } - ] + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "createdAt", + "memberIds" + ] + } }, - "players": { + "matches": { "type": "array", - "items": [ - { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "playerIds": { + "type": "array", + "items": { "type": "string" } }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "winnerId": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "createdAt", + "groupId", + "playerIds", + "winnerId" + ] + } } - } -} - + }, + "required": [ + "players", + "groups", + "matches" + ] +} \ No newline at end of file -- 2.49.1 From 4c084cae4aa12f795cf4cd7e787e7df7585f2232 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:20:36 +0100 Subject: [PATCH 531/563] remove ?? as members cant be null --- lib/services/data_transfer_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 2fc5c42..e60240c 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -38,7 +38,7 @@ class DataTransferService { 'id': g.id, 'name': g.name, 'createdAt': g.createdAt.toIso8601String(), - 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + 'memberIds': (g.members).map((m) => m.id).toList(), }).toList(), 'matches': matches -- 2.49.1 From ec94e12ed7793e230b543c3e2f38ee099a0fe68e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 15:38:25 +0100 Subject: [PATCH 532/563] implement changes --- lib/core/enums.dart | 9 ++- lib/l10n/arb/app_de.arb | 48 ++++++----- lib/l10n/arb/app_en.arb | 32 ++++---- lib/l10n/generated/app_localizations.dart | 28 +++---- lib/l10n/generated/app_localizations_de.dart | 63 +++++++-------- lib/l10n/generated/app_localizations_en.dart | 19 ++--- lib/main.dart | 3 +- .../main_menu/custom_navigation_bar.dart | 27 +++---- .../group_view/create_group_view.dart | 11 ++- .../main_menu/group_view/groups_view.dart | 8 +- .../views/main_menu/home_view.dart | 32 ++++---- .../create_match/choose_game_view.dart | 12 +-- .../create_match/choose_group_view.dart | 15 ++-- .../create_match/choose_ruleset_view.dart | 9 ++- .../create_match/create_match_view.dart | 38 ++++----- .../match_view/match_result_view.dart | 3 +- .../main_menu/match_view/match_view.dart | 8 +- .../views/main_menu/settings_view.dart | 81 +++++++------------ .../views/main_menu/statistics_view.dart | 41 +++++----- .../widgets/player_selection.dart | 27 +++---- .../widgets/tiles/match_tile.dart | 12 +-- .../widgets/tiles/statistics_tile.dart | 3 +- 22 files changed, 247 insertions(+), 282 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 74ae023..ce06f85 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -35,14 +35,15 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } /// Translates a [Ruleset] enum value to its corresponding localized string. String translateRulesetToString(Ruleset ruleset, BuildContext context) { + final loc = AppLocalizations.of(context); switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context).single_winner; + return loc.single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context).single_loser; + return loc.single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context).most_points; + return loc.most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context).least_points; + return loc.least_points; } } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 26ed145..d8a9e7e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3,30 +3,28 @@ "choose_group": "Gruppe wählen", "create_new_match": "Neues Match erstellen", "choose_ruleset": "Regelwerk wählen", - "choose_game": "Spiel wählen", - "select_winner": "Gewinner wählen:", + "choose_game": "Spielvorlage wählen", + "select_winner": "Gewinner:in wählen:", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", "delete_all_data": "Alle Daten löschen?", "cancel": "Abbrechen", "delete": "Löschen", "create_new_group": "Neue Gruppe erstellen", - "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler: {count}", - "no_players_selected": "Keine Spieler ausgewählt", - "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner: {winnerName}", - "players": "Spieler", - "player_name": "Spielername", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler:in: {count}", + "no_players_selected": "Keine Spieler:in ausgewählt", + "all_players": "Alle Spieler:innen:", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "winner": "Gewinner:in: {winnerName}", + "players": "Spieler:in", + "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", "quick_create": "Schnellzugriff", - "winner_label": "Gewinner", - "ruleset_label": "Regelwerk", "match_in_progress": "Match läuft...", "menu": "Menü", "settings": "Einstellungen", @@ -52,15 +50,15 @@ "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", "match_name": "Matchname", - "game": "Spiel", + "game": "Spielvorlage", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Kein", "none_group": "Keine", "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler erstellt", - "all_players_selected": "Alle Spieler ausgewählt", - "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "today_at": "Heute um {time}", "yesterday_at": "Gestern um {time}", "days_ago": "vor {count} Tagen", @@ -69,16 +67,16 @@ "stats": "Statistiken", "players_count": "{count} Spieler", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielname", - "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", - "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein Gewinner", - "single_loser": "Ein Verlierer", + "game_name": "Spielvorlagenname", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein:e Gewinner:in", + "single_loser": "Ein:e Verlierer:in", "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spielern suchen", + "search_for_players": "Nach Spieler:innen suchen", "search_for_groups": "Nach Gruppen suchen", "no_data_available": "Keine Daten verfügbar", "not_available": "Nicht verfügbar" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 0a68994..8e6d63a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -20,6 +20,10 @@ "@select_winner": { "description": "Label to select the winner" }, + "game_tracker": "Game Tracker", + "@game_tracker": { + "description": "App Name" + }, "no_recent_matches_available": "No recent matches available", "@no_recent_matches_available": { "description": "Message when no recent matches exist" @@ -44,8 +48,8 @@ "@create_new_group": { "description": "Button text to create a new group" }, - "error_while_creating_group_please_try_again": "Error while creating group, please try again", - "@error_while_creating_group_please_try_again": { + "error_creating_group": "Error while creating group, please try again", + "@error_creating_group": { "description": "Error message when group creation fails" }, "selected_players": "Selected players: {count}", @@ -124,14 +128,6 @@ "@quick_create": { "description": "Title for quick create section" }, - "winner_label": "Winner", - "@winner_label": { - "description": "Label for winner field" - }, - "ruleset_label": "Ruleset", - "@ruleset_label": { - "description": "Label for ruleset field" - }, "match_in_progress": "Match in progress...", "@match_in_progress": { "description": "Message when match is in progress" @@ -330,20 +326,20 @@ "@game_name": { "description": "Placeholder for game name search" }, - "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner_desc": { + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner": { "description": "Description for single winner ruleset" }, - "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser_desc": { + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser": { "description": "Description for single loser ruleset" }, - "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points_desc": { + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points": { "description": "Description for most points ruleset" }, - "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points_desc": { + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points": { "description": "Description for least points ruleset" }, "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index e3acecb..951ff22 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -128,6 +128,12 @@ abstract class AppLocalizations { /// **'Select Winner:'** String get select_winner; + /// App Name + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get game_tracker; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -168,7 +174,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Error while creating group, please try again'** - String get error_while_creating_group_please_try_again; + String get error_creating_group; /// Shows the number of selected players /// @@ -248,18 +254,6 @@ abstract class AppLocalizations { /// **'Quick Create'** String get quick_create; - /// Label for winner field - /// - /// In en, this message translates to: - /// **'Winner'** - String get winner_label; - - /// Label for ruleset field - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset_label; - /// Message when match is in progress /// /// In en, this message translates to: @@ -528,25 +522,25 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner_desc; + String get ruleset_single_winner; /// Description for single loser ruleset /// /// In en, this message translates to: /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser_desc; + String get ruleset_single_loser; /// Description for most points ruleset /// /// In en, this message translates to: /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points_desc; + String get ruleset_most_points; /// Description for least points ruleset /// /// In en, this message translates to: /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points_desc; + String get ruleset_least_points; /// Title for single winner ruleset /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index a270c82..3f3e36e 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -18,10 +18,13 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String get choose_game => 'Spiel wählen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get select_winner => 'Gewinner wählen:'; + String get select_winner => 'Gewinner:in wählen:'; + + @override + String get game_tracker => 'Game Tracker'; @override String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; @@ -42,7 +45,7 @@ class AppLocalizationsDe extends AppLocalizations { String get create_new_group => 'Neue Gruppe erstellen'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override @@ -52,32 +55,32 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler: $countString'; + return 'Ausgewählte Spieler:in: $countString'; } @override - String get no_players_selected => 'Keine Spieler ausgewählt'; + String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get all_players => 'Alle Spieler:'; + String get all_players => 'Alle Spieler:innen:'; @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt'; + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden'; + return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @override String winner(String winnerName) { - return 'Gewinner: $winnerName'; + return 'Gewinner:in: $winnerName'; } @override - String get players => 'Spieler'; + String get players => 'Spieler:in'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -97,12 +100,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get quick_create => 'Schnellzugriff'; - @override - String get winner_label => 'Gewinner'; - - @override - String get ruleset_label => 'Regelwerk'; - @override String get match_in_progress => 'Match läuft...'; @@ -168,7 +165,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_players_created_yet => 'Noch keine Spieler erstellt'; + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @override String get create_group => 'Gruppe erstellen'; @@ -177,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get group_name => 'Gruppenname'; @override - String get player_name => 'Spielername'; + String get player_name => 'Spieler:innenname'; @override String get no_matches_created_yet => 'Noch keine Matches erstellt'; @@ -186,7 +183,7 @@ class AppLocalizationsDe extends AppLocalizations { String get match_name => 'Matchname'; @override - String get game => 'Spiel'; + String get game => 'Spielvorlage'; @override String get ruleset => 'Regelwerk'; @@ -205,10 +202,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_players_found_with_that_name => - 'Keine Spieler mit diesem Namen gefunden'; + 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get all_players_selected => 'Alle Spieler ausgewählt'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override String today_at(String time) { @@ -244,29 +241,29 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielname'; + String get game_name => 'Spielvorlagenname'; @override - String get ruleset_single_winner_desc => - 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; @override - String get ruleset_single_loser_desc => - 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; @override - String get ruleset_most_points_desc => - 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; @override - String get ruleset_least_points_desc => - 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; @override - String get single_winner => 'Ein Gewinner'; + String get single_winner => 'Ein:e Gewinner:in'; @override - String get single_loser => 'Ein Verlierer'; + String get single_loser => 'Ein:e Verlierer:in'; @override String get most_points => 'Höchste Punkte'; @@ -275,7 +272,7 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get search_for_players => 'Nach Spielern suchen'; + String get search_for_players => 'Nach Spieler:innen suchen'; @override String get search_for_groups => 'Nach Gruppen suchen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 63a7d0e..263714c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -23,6 +23,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_winner => 'Select Winner:'; + @override + String get game_tracker => 'Game Tracker'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -42,7 +45,7 @@ class AppLocalizationsEn extends AppLocalizations { String get create_new_group => 'Create new group'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Error while creating group, please try again'; @override @@ -97,12 +100,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get quick_create => 'Quick Create'; - @override - String get winner_label => 'Winner'; - - @override - String get ruleset_label => 'Ruleset'; - @override String get match_in_progress => 'Match in progress...'; @@ -246,19 +243,19 @@ class AppLocalizationsEn extends AppLocalizations { String get game_name => 'Game Name'; @override - String get ruleset_single_winner_desc => + String get ruleset_single_winner => 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; @override - String get ruleset_single_loser_desc => + String get ruleset_single_loser => 'Exactly one loser is determined; last place receives the penalty or consequence.'; @override - String get ruleset_most_points_desc => + String get ruleset_most_points => 'Traditional ruleset: the player with the most points wins.'; @override - String get ruleset_least_points_desc => + String get ruleset_least_points => 'Inverse scoring: the player with the fewest points wins.'; @override diff --git a/lib/main.dart b/lib/main.dart index 8ef243a..c1ed977 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,6 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { - print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, @@ -35,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - title: 'Game Tracker', + onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, darkTheme: ThemeData.dark(), themeMode: ThemeMode.dark, // forces dark mode diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2faef8b..1e38808 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -20,13 +20,9 @@ class _CustomNavigationBarState extends State int currentIndex = 0; int tabKeyCount = 0; - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); // Pretty ugly but works final List tabs = [ KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), @@ -47,7 +43,7 @@ class _CustomNavigationBarState extends State appBar: AppBar( centerTitle: true, title: Text( - _currentTabTitle(), + _currentTabTitle(context), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), backgroundColor: CustomTheme.backgroundColor, @@ -90,28 +86,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context).home, + label: loc.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context).matches, + label: loc.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context).groups, + label: loc.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context).statistics, + label: loc.statistics, onTabTapped: onTabTapped, ), ], @@ -129,16 +125,17 @@ class _CustomNavigationBarState extends State }); } - String _currentTabTitle() { + String _currentTabTitle(context) { + final loc = AppLocalizations.of(context); switch (currentIndex) { case 0: - return AppLocalizations.of(context).home; + return loc.home; case 1: - return AppLocalizations.of(context).matches; + return loc.matches; case 2: - return AppLocalizations.of(context).groups; + return loc.groups; case 3: - return AppLocalizations.of(context).statistics; + return loc.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 0d8c28f..cba22ef 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -20,11 +20,13 @@ class CreateGroupView extends StatefulWidget { class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); late final AppDatabase db; + List selectedPlayers = []; @override void initState() { super.initState(); + db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); @@ -39,13 +41,14 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_group, + loc.create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +61,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context).group_name, + hintText: loc.group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +77,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -99,7 +102,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - ).error_while_creating_group_please_try_again, + ).error_creating_group, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 4d42417..3505a3c 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,12 +35,14 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGroups(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -53,8 +55,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +76,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 6446ea8..96280ce 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -72,6 +72,7 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( @@ -87,7 +88,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).matches, + title: loc.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +96,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).groups, + title: loc.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +106,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).recent_matches, + title: loc.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,11 +126,12 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[0], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[0]), + ), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, @@ -143,11 +145,12 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[1], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[1]), + ), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, @@ -173,7 +176,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).quick_create, + title: loc.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -227,10 +230,11 @@ class _HomeViewState extends State { ); } - String _getPlayerText(Match game) { + String _getPlayerText(Match game, context) { + final loc = AppLocalizations.of(context); if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context).players_count(playerCount); + return loc.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 5ed9d95..18e1e9d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -21,6 +21,7 @@ class ChooseGameView extends StatefulWidget { class _ChooseGameViewState extends State { late int selectedGameIndex; + final TextEditingController searchBarController = TextEditingController(); @override @@ -31,6 +32,7 @@ class _ChooseGameViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -43,7 +45,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_game, + loc.choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -64,7 +66,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context).game_name, + hintText: loc.game_name, ), ), const SizedBox(height: 5), @@ -76,9 +78,9 @@ class _ChooseGameViewState extends State { title: widget.games[index].$1, description: widget.games[index].$2, badgeText: translateRulesetToString( - widget.games[index].$3, - context, - ), + widget.games[index].$3, + context, + ), isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 8a3f0f2..5101db6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -34,6 +34,7 @@ class _ChooseGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -52,7 +53,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_group, + loc.choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -79,7 +80,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context).search_for_groups, + hintText: loc.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -94,15 +95,15 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( - context, - ).there_is_no_group_matching_your_search, + context, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index f47b535..7a41417 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -29,6 +29,7 @@ class _ChooseRulesetViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return DefaultTabController( length: 2, initialIndex: 0, @@ -48,7 +49,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_ruleset, + loc.choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -82,9 +83,9 @@ class _ChooseRulesetViewState extends State { }); }, title: translateRulesetToString( - widget.rulesets[index].$1, - context, - ), + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 77fa4e4..d3a23ae 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -90,23 +90,12 @@ class _CreateMatchViewState extends State { } List<(Ruleset, String)> _getRulesets(BuildContext context) { + final loc = AppLocalizations.of(context); return [ - ( - Ruleset.singleWinner, - AppLocalizations.of(context).ruleset_single_winner_desc, - ), - ( - Ruleset.singleLoser, - AppLocalizations.of(context).ruleset_single_loser_desc, - ), - ( - Ruleset.mostPoints, - AppLocalizations.of(context).ruleset_most_points_desc, - ), - ( - Ruleset.leastPoints, - AppLocalizations.of(context).ruleset_least_points_desc, - ), + (Ruleset.singleWinner, loc.ruleset_single_winner), + (Ruleset.singleLoser, loc.ruleset_single_loser), + (Ruleset.mostPoints, loc.ruleset_most_points), + (Ruleset.leastPoints, loc.ruleset_least_points), ]; } @@ -118,13 +107,14 @@ class _CreateMatchViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_match, + loc.create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -141,9 +131,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context).game, + title: loc.game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context).none + ? loc.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -169,9 +159,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).ruleset, + title: loc.ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context).none + ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -192,9 +182,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).group, + title: loc.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context).none_group + ? loc.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -231,7 +221,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e99ce85..8e178bf 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -36,6 +36,7 @@ class _MatchResultViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -81,7 +82,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context).select_winner, + loc.select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 2b1b110..73f596f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -44,12 +44,14 @@ class _MatchViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGames(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -62,8 +64,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_matches_created_yet, + title: loc.info, + message: loc.no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +99,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c554950..8f1e68a 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -13,8 +13,14 @@ class SettingsView extends StatefulWidget { } class _SettingsViewState extends State { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, @@ -29,7 +35,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).menu, + loc.menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +49,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).settings, + loc.settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +57,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context).export_data, + title: loc.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +72,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).import_data, + title: loc.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,27 +84,23 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).delete_all_data, + title: loc.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: Text( - AppLocalizations.of(context).delete_all_data, - ), - content: Text( - AppLocalizations.of(context).this_cannot_be_undone, - ), + title: Text(loc.delete_all_data), + content: Text(loc.this_cannot_be_undone), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context).cancel), + child: Text(loc.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context).delete), + child: Text(loc.delete), ), ], ), @@ -130,37 +132,20 @@ class _SettingsViewState extends State { required BuildContext context, required ImportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ImportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_imported, - ); + showSnackbar(context: context, message: loc.data_successfully_imported); case ImportResult.invalidSchema: - showSnackbar( - context: context, - message: AppLocalizations.of(context).invalid_schema, - ); + showSnackbar(context: context, message: loc.invalid_schema); case ImportResult.fileReadError: - showSnackbar( - context: context, - message: AppLocalizations.of(context).error_reading_file, - ); + showSnackbar(context: context, message: loc.error_reading_file); case ImportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).import_canceled, - ); + showSnackbar(context: context, message: loc.import_canceled); case ImportResult.formatException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).format_exception, - ); + showSnackbar(context: context, message: loc.format_exception); case ImportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -172,22 +157,14 @@ class _SettingsViewState extends State { required BuildContext context, required ExportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ExportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_exported, - ); + showSnackbar(context: context, message: loc.data_successfully_exported); case ExportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).export_canceled, - ); + showSnackbar(context: context, message: loc.export_canceled); case ExportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -203,6 +180,7 @@ class _SettingsViewState extends State { Duration duration = const Duration(seconds: 3), VoidCallback? action, }) { + final loc = AppLocalizations.of(context); final messenger = ScaffoldMessenger.of(context); messenger.hideCurrentSnackBar(); messenger.showSnackBar( @@ -211,10 +189,7 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction( - label: AppLocalizations.of(context).undo, - onPressed: action, - ) + ? SnackBarAction(label: loc.undo, onPressed: action) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 43de037..6c30483 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,6 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); + final db = Provider.of(context, listen: false); Future.wait([ @@ -32,21 +33,25 @@ class _StatisticsViewState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) async { + if (!mounted) return; final matches = results[0] as List; final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players); - matchCounts = _calculateMatchAmountsForAllPlayers(matches, players); + winCounts = _calculateWinsForAllPlayers(matches, players, context); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches, + players, + context, + ); winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - if (mounted) { - setState(() { - isLoading = false; - }); - } + setState(() { + isLoading = false; + }); }); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( @@ -69,7 +74,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context).wins, + title: loc.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +83,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context).winrate, + title: loc.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +92,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of(context).amount_of_matches, + title: loc.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -97,7 +102,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( context, ).no_statistics_available, @@ -118,8 +123,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateWinsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> winCounts = []; + final loc = AppLocalizations.of(context); // Getting the winners for (var match in matches) { @@ -150,10 +157,7 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -168,8 +172,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateMatchAmountsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> matchCounts = []; + final loc = AppLocalizations.of(context); // Counting matches for each player for (var match in matches) { @@ -215,10 +221,7 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); matchCounts[i] = (player.name, matchCounts[i].$2); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e1761b1..eac4480 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -87,6 +87,7 @@ class _PlayerSelectionState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), @@ -97,7 +98,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context).search_for_players, + hintText: loc.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -139,11 +140,7 @@ class _PlayerSelectionState extends State { SizedBox( height: 50, child: selectedPlayers.isEmpty - ? Center( - child: Text( - AppLocalizations.of(context).no_players_selected, - ), - ) + ? Center(child: Text(loc.no_players_selected)) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -185,7 +182,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context).all_players, + loc.all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -196,8 +193,8 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: _getInfoText(), + title: loc.info, + message: _getInfoText(context), ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -234,6 +231,7 @@ class _PlayerSelectionState extends State { /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { + final loc = AppLocalizations.of(context); String playerName = _searchBarController.text.trim(); Player createdPlayer = Player(name: playerName); bool success = await db.playerDao.addPlayer(player: createdPlayer); @@ -267,7 +265,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context).could_not_add_player(playerName), + loc.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -278,18 +276,19 @@ class _PlayerSelectionState extends State { /// Determines the appropriate info text to display when no players /// are available in the suggested players list. - String _getInfoText() { + String _getInfoText(BuildContext context) { + final loc = AppLocalizations.of(context); if (allPlayers.isEmpty) { // No players exist in the database - return AppLocalizations.of(context).no_players_created_yet; + return loc.no_players_created_yet; } else if (selectedPlayers.length == allPlayers.length || widget.availablePlayers?.isEmpty == true) { // All players have been selected or // available players list is provided but empty - return AppLocalizations.of(context).all_players_selected; + return loc.all_players_selected; } else { // No players match the search query - return AppLocalizations.of(context).no_players_found_with_that_name; + return loc.no_players_found_with_that_name; } } } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 7727827..291a256 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -21,6 +21,7 @@ class _MatchTileState extends State { final group = widget.match.group; final winner = widget.match.winner; final allPlayers = _getAllPlayers(); + final loc = AppLocalizations.of(context); return GestureDetector( onTap: widget.onTap, @@ -49,7 +50,7 @@ class _MatchTileState extends State { ), ), Text( - _formatDate(widget.match.createdAt), + _formatDate(widget.match.createdAt, context), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -98,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context).winner(winner.name), + loc.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +116,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context).players, + loc.players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -137,9 +138,10 @@ class _MatchTileState extends State { ); } - String _formatDate(DateTime dateTime) { + String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); + final loc = AppLocalizations.of(context); if (difference.inDays == 0) { return AppLocalizations.of( @@ -150,7 +152,7 @@ class _MatchTileState extends State { context, ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context).days_ago(difference.inDays); + return loc.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 8d81270..598fad0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -25,6 +25,7 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { final maxBarWidth = MediaQuery.of(context).size.width * 0.65; + final loc = AppLocalizations.of(context); return InfoTile( width: width, @@ -36,7 +37,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context).no_data_available), + child: Text(loc.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From 3c7c4598ff5a3eaff4c634c5c469d29c7e60d4e1 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:42:25 +0100 Subject: [PATCH 533/563] sort players in winner selection --- .../main_menu/match_view/match_result_view.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e8075f6..c2076cc 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -143,11 +143,17 @@ class _MatchResultViewState extends State { } List getAllPlayers(Match match) { + List players = []; + if (match.group == null && match.players != null) { - return [...match.players!]; + players = [...match.players!]; } else if (match.group != null && match.players != null) { - return [...match.players!, ...match.group!.members]; + players = [...match.players!, ...match.group!.members]; + } else { + players = [...match.group!.members]; } - return [...match.group!.members]; + + players.sort((a, b) => a.name.compareTo(b.name)); + return players; } } -- 2.49.1 From 072dba1cde3f11c4a3854925cbcc59091be0aae6 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:45:35 +0100 Subject: [PATCH 534/563] sort players in match tile --- lib/presentation/widgets/tiles/match_tile.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 543a542..e89be2f 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -175,6 +175,8 @@ class _MatchTileState extends State { } } + allPlayers.sort((a, b) => a.name.compareTo(b.name)); return allPlayers; + ; } } -- 2.49.1 From bdcee85eb9e283698fbb1f751616b13c5d0d183b Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:49:15 +0100 Subject: [PATCH 535/563] sort players in group tile --- lib/presentation/widgets/tiles/group_tile.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 248c1c6..5f870de 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -56,7 +56,9 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) + for (var member in [ + ...group.members, + ]..sort((a, b) => a.name.compareTo(b.name))) TextIconTile(text: member.name, iconEnabled: false), ], ), -- 2.49.1 From 7fa434782c04d8d03010436adf4fa31b2530f9da Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:49:37 +0100 Subject: [PATCH 536/563] remove semicolon and empty line --- lib/presentation/widgets/tiles/match_tile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index e89be2f..bfe1b9f 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -177,6 +177,5 @@ class _MatchTileState extends State { allPlayers.sort((a, b) => a.name.compareTo(b.name)); return allPlayers; - ; } } -- 2.49.1 From 7f6c1cb9a65c85d7bd087ef2df3fff9eb0152476 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:40:57 +0100 Subject: [PATCH 537/563] change InsertMode to insertOrIgnore not insertOrReplace --- lib/data/dao/group_dao.dart | 8 ++++++-- lib/data/dao/match_dao.dart | 8 ++++++-- lib/data/dao/player_dao.dart | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 9802203..98c602a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -95,6 +95,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } // Insert unique groups in batch + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when groups already exist await db.batch( (b) => b.insertAll( groupTable, @@ -107,7 +109,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -120,6 +122,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -132,7 +136,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index b56a38b..160686a 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -124,6 +124,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); // Add all groups of the matches in batch + // Using insertOrIgnore to avoid overwriting existing groups (which would + // trigger cascade deletes on player_group associations) await db.batch( (b) => b.insertAll( db.groupTable, @@ -137,7 +139,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -158,6 +160,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group/player_match associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -170,7 +174,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 8a58504..c8db800 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -50,6 +50,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Adds multiple [players] to the database in a batch operation. + /// Uses insertOrIgnore to avoid triggering cascade deletes on + /// player_group associations when players already exist. Future addPlayersAsList({required List players}) async { if (players.isEmpty) return false; @@ -65,7 +67,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); -- 2.49.1 From 7b2314a25e9543fe202e918ae6181d0d4509aecb Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:46:26 +0100 Subject: [PATCH 538/563] fix null error for winnerId and groupId in match import --- assets/schema.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index fa748d9..b3a8a2c 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -68,7 +68,10 @@ "type": "string" }, "groupId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] }, "playerIds": { "type": "array", @@ -77,7 +80,10 @@ } }, "winnerId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] } }, "required": [ @@ -85,8 +91,7 @@ "name", "createdAt", "groupId", - "playerIds", - "winnerId" + "playerIds" ] } } -- 2.49.1 From 69effa2b7d287f47945dfc6f43b04cd9b23c0aa8 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 11:49:10 +0100 Subject: [PATCH 539/563] change winner localization to not include placeholder --- lib/l10n/arb/app_en.arb | 10 ++-------- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8e6d63a..e3666b3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -90,15 +90,9 @@ } } }, - "winner": "Winner: {winnerName}", + "winner": "Winner", "@winner": { - "description": "Shows the winner's name", - "placeholders": { - "winnerName": { - "type": "String", - "example": "John" - } - } + "description": "Winner label", }, "players": "Players", "@players": { diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 2b94cf2..b037af2 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - loc.winner(winner.name), + "${loc.winner}: ${winner.name}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, -- 2.49.1 From 99044c0ea0219deefe0f703d95f86ebf9a785ed5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 540/563] remove unnecessary check --- lib/services/data_transfer_service.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index e60240c..8767c59 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -104,8 +104,7 @@ class DataTransferService { final isValid = await _validateJsonSchema(jsonString); if (!isValid) return ImportResult.invalidSchema; - final dynamic decoded = json.decode(jsonString); - if (decoded is! Map) return ImportResult.invalidSchema; + final Map decoded = json.decode(jsonString) as Map; final List playersJson = (decoded['players'] as List?) ?? []; final List groupsJson = (decoded['groups'] as List?) ?? []; -- 2.49.1 From a487e4071f9500d439501a80e70a7193c03546b0 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 541/563] Sort files --- lib/l10n/arb/app_de.arb | 144 ++++---- lib/l10n/arb/app_en.arb | 732 ++++++++++++++++++++-------------------- 2 files changed, 438 insertions(+), 438 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index d8a9e7e..89354bd 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,83 +1,83 @@ { "@@locale": "de", - "choose_group": "Gruppe wählen", - "create_new_match": "Neues Match erstellen", - "choose_ruleset": "Regelwerk wählen", + "all_players": "Alle Spieler:innen:", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "amount_of_matches": "Anzahl der Matches", + "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", - "select_winner": "Gewinner:in wählen:", + "choose_group": "Gruppe wählen", + "choose_ruleset": "Regelwerk wählen", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "create_group": "Gruppe erstellen", + "create_match": "Match erstellen", + "create_new_group": "Neue Gruppe erstellen", + "create_new_match": "Neues Match erstellen", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_exported": "Daten erfolgreich exportiert", + "data_successfully_imported": "Daten erfolgreich importiert", + "days_ago": "vor {count} Tagen", + "delete": "Löschen", + "delete_all_data": "Alle Daten löschen?", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "error_reading_file": "Fehler beim Lesen der Datei", + "export_canceled": "Export abgebrochen", + "export_data": "Daten exportieren", + "format_exception": "Formatfehler (siehe Konsole)", + "game": "Spielvorlage", + "game_name": "Spielvorlagenname", + "group": "Gruppe", + "group_name": "Gruppenname", + "groups": "Gruppen", + "home": "Startseite", + "import_canceled": "Import abgebrochen", + "import_data": "Daten importieren", + "info": "Info", + "invalid_schema": "Ungültiges Schema", + "least_points": "Niedrigste Punkte", + "match_in_progress": "Match läuft...", + "match_name": "Matchname", + "matches": "Matches", + "menu": "Menü", + "most_points": "Höchste Punkte", + "no_data_available": "Keine Daten verfügbar", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "no_matches_created_yet": "Noch keine Matches erstellt", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", + "no_players_selected": "Keine Spieler:in ausgewählt", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", - "delete_all_data": "Alle Daten löschen?", - "cancel": "Abbrechen", - "delete": "Löschen", - "create_new_group": "Neue Gruppe erstellen", - "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler:in: {count}", - "no_players_selected": "Keine Spieler:in ausgewählt", - "all_players": "Alle Spieler:innen:", - "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner:in: {winnerName}", - "players": "Spieler:in", - "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", - "matches": "Matches", - "groups": "Gruppen", - "recent_matches": "Letzte Matches", - "quick_create": "Schnellzugriff", - "match_in_progress": "Match läuft...", - "menu": "Menü", - "settings": "Einstellungen", - "export_data": "Daten exportieren", - "import_data": "Daten importieren", - "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "data_successfully_deleted": "Daten erfolgreich gelöscht", - "data_successfully_imported": "Daten erfolgreich importiert", - "invalid_schema": "Ungültiges Schema", - "error_reading_file": "Fehler beim Lesen der Datei", - "import_canceled": "Import abgebrochen", - "format_exception": "Formatfehler (siehe Konsole)", - "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "data_successfully_exported": "Daten erfolgreich exportiert", - "export_canceled": "Export abgebrochen", - "undo": "Rückgängig", - "wins": "Siege", - "winrate": "Siegquote", - "amount_of_matches": "Anzahl der Matches", - "info": "Info", - "no_groups_created_yet": "Noch keine Gruppen erstellt", - "create_group": "Gruppe erstellen", - "group_name": "Gruppenname", - "no_matches_created_yet": "Noch keine Matches erstellt", - "match_name": "Matchname", - "game": "Spielvorlage", - "ruleset": "Regelwerk", - "group": "Gruppe", "none": "Kein", "none_group": "Keine", - "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler:in erstellt", - "all_players_selected": "Alle Spieler:innen ausgewählt", - "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "today_at": "Heute um {time}", - "yesterday_at": "Gestern um {time}", - "days_ago": "vor {count} Tagen", - "home": "Startseite", + "not_available": "Nicht verfügbar", + "player_name": "Spieler:innenname", + "players": "Spieler:in", + "players_count": "{count} Spieler", + "quick_create": "Schnellzugriff", + "recent_matches": "Letzte Matches", + "ruleset": "Regelwerk", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "search_for_groups": "Nach Gruppen suchen", + "search_for_players": "Nach Spieler:innen suchen", + "select_winner": "Gewinner:in wählen:", + "selected_players": "Ausgewählte Spieler:in: {count}", + "settings": "Einstellungen", + "single_loser": "Ein:e Verlierer:in", + "single_winner": "Ein:e Gewinner:in", "statistics": "Statistiken", "stats": "Statistiken", - "players_count": "{count} Spieler", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielvorlagenname", - "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", - "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein:e Gewinner:in", - "single_loser": "Ein:e Verlierer:in", - "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spieler:innen suchen", - "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar", - "not_available": "Nicht verfügbar" -} + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "today_at": "Heute um {time}", + "undo": "Rückgängig", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "winner": "Gewinner:in: {winnerName}", + "winrate": "Siegquote", + "wins": "Siege", + "yesterday_at": "Gestern um {time}" +} \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e3666b3..d567f50 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,367 @@ { - "@@locale": "en", - "choose_group": "Choose Group", - "@choose_group": { - "description": "Label for choosing a group" - }, - "create_new_match": "Create new match", - "@create_new_match": { - "description": "Button text to create a new match" - }, - "choose_ruleset": "Choose Ruleset", - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "choose_game": "Choose Game", - "@choose_game": { - "description": "Label for choosing a game" - }, - "select_winner": "Select Winner:", - "@select_winner": { - "description": "Label to select the winner" - }, - "game_tracker": "Game Tracker", - "@game_tracker": { - "description": "App Name" - }, - "no_recent_matches_available": "No recent matches available", - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "no_second_match_available": "No second match available", - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "delete_all_data": "Delete all data?", - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "cancel": "Cancel", - "@cancel": { - "description": "Cancel button text" - }, - "delete": "Delete", - "@delete": { - "description": "Delete button text" - }, - "create_new_group": "Create new group", - "@create_new_group": { - "description": "Button text to create a new group" - }, - "error_creating_group": "Error while creating group, please try again", - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "selected_players": "Selected players: {count}", - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "no_players_selected": "No players selected", - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "all_players": "All players:", - "@all_players": { - "description": "Label for all players list" - }, - "successfully_added_player": "Successfully added player {playerName}", - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "could_not_add_player": "Could not add player {playerName}", - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "winner": "Winner", - "@winner": { - "description": "Winner label", - }, - "players": "Players", - "@players": { - "description": "Players label" - }, - "no_statistics_available": "No statistics available", - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "no_data_available": "No data available", - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "matches": "Matches", - "@matches": { - "description": "Label for matches" - }, - "groups": "Groups", - "@groups": { - "description": "Label for groups" - }, - "recent_matches": "Recent Matches", - "@recent_matches": { - "description": "Title for recent matches section" - }, - "quick_create": "Quick Create", - "@quick_create": { - "description": "Title for quick create section" - }, - "match_in_progress": "Match in progress...", - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "menu": "Menu", - "@menu": { - "description": "Menu label" - }, - "settings": "Settings", - "@settings": { - "description": "Settings label" - }, - "export_data": "Export data", - "@export_data": { - "description": "Export data menu item" - }, - "import_data": "Import data", - "@import_data": { - "description": "Import data menu item" - }, - "this_cannot_be_undone": "This can't be undone", - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "data_successfully_deleted": "Data successfully deleted", - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "data_successfully_imported": "Data successfully imported", - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "invalid_schema": "Invalid Schema", - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "error_reading_file": "Error reading file", - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "import_canceled": "Import canceled", - "@import_canceled": { - "description": "Message when import is canceled" - }, - "format_exception": "Format Exception (see console)", - "@format_exception": { - "description": "Error message for format exceptions" - }, - "unknown_exception": "Unknown Exception (see console)", - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "data_successfully_exported": "Data successfully exported", - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "export_canceled": "Export canceled", - "@export_canceled": { - "description": "Message when export is canceled" - }, - "undo": "Undo", - "@undo": { - "description": "Undo button text" - }, - "wins": "Wins", - "@wins": { - "description": "Label for wins statistic" - }, - "winrate": "Winrate", - "@winrate": { - "description": "Label for winrate statistic" - }, - "amount_of_matches": "Amount of Matches", - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "info": "Info", - "@info": { - "description": "Info label" - }, - "no_groups_created_yet": "No groups created yet", - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "no_players_created_yet": "No players created yet", - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "create_group": "Create Group", - "@create_group": { - "description": "Button text to create a group" - }, - "group_name": "Group name", - "@group_name": { - "description": "Placeholder for group name input" - }, - "player_name": "Player name", - "@player_name": { - "description": "Placeholder for player name input" - }, - "no_matches_created_yet": "No matches created yet", - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "match_name": "Match name", - "@match_name": { - "description": "Placeholder for match name input" - }, - "game": "Game", - "@game": { - "description": "Game label" - }, - "ruleset": "Ruleset", - "@ruleset": { - "description": "Ruleset label" - }, - "group": "Group", - "@group": { - "description": "Group label" - }, - "none": "None", - "@none": { - "description": "None option label" - }, - "none_group": "None", - "@none_group": { - "description": "None group option label" - }, - "create_match": "Create match", - "@create_match": { - "description": "Button text to create a match" - }, - "no_players_found_with_that_name": "No players found with that name", - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "all_players_selected": "All players selected", - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "today_at": "Today at {time}", - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "yesterday_at": "Yesterday at {time}", - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "days_ago": "{count} days ago", - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "home": "Home", - "@home": { - "description": "Home tab label" - }, - "statistics": "Statistics", - "@statistics": { - "description": "Statistics tab label" - }, - "stats": "Stats", - "@stats": { - "description": "Stats tab label (short)" - }, - "players_count": "{count} Players", - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "there_is_no_group_matching_your_search": "There is no group matching your search", - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "game_name": "Game Name", - "@game_name": { - "description": "Placeholder for game name search" - }, - "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "single_winner": "Single Winner", - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "single_loser": "Single Loser", - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "most_points": "Most Points", - "@most_points": { - "description": "Title for most points ruleset" - }, - "least_points": "Least Points", - "@least_points": { - "description": "Title for least points ruleset" - }, - "search_for_players": "Search for players", - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "search_for_groups": "Search for groups", - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "not_available": "Not available", - "@not_available": { - "description": "Abbreviation for not available" - } -} + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@game_tracker": { + "description": "App Name" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player {playerName}", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "game_tracker": "Game Tracker", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "search_for_groups": "Search for groups", + "search_for_players": "Search for players", + "select_winner": "Select Winner:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at {time}", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at {time}" +} \ No newline at end of file -- 2.49.1 From 1dc5286c6b4c21a7552a592a070413caa67d39bd Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:08:21 +0100 Subject: [PATCH 542/563] change double to single quotes --- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index b037af2..c455949 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - "${loc.winner}: ${winner.name}", + '${loc.winner}: ${winner.name}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, -- 2.49.1 From a78614851bdeac36e363957ffd80bf939953a09d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:02:09 +0100 Subject: [PATCH 543/563] Added constants class --- lib/core/constants.dart | 8 ++++++-- .../views/main_menu/group_view/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 2 +- .../views/main_menu/match_view/match_view.dart | 2 +- lib/presentation/views/main_menu/statistics_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 075b1ab..8d3c8cc 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,6 @@ -/// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +class Constants { + Constants._(); // Private constructor to prevent instantiation + + /// Minimum duration of all app skeletons + static Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +} diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 3505a3c..c641dde 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -101,7 +101,7 @@ class _GroupsViewState extends State { void loadGroups() { Future.wait([ db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { loadedGroups = results[0] as List; setState(() { diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 96280ce..072699e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -47,7 +47,7 @@ class _HomeViewState extends State { db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 73f596f..bea2f14 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -120,7 +120,7 @@ class _MatchViewState extends State { void loadGames() { Future.wait([ db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { if (mounted) { setState(() { diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6c30483..1125118 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -31,7 +31,7 @@ class _StatisticsViewState extends State { Future.wait([ db.matchDao.getAllMatches(), db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) async { if (!mounted) return; final matches = results[0] as List; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eac4480..9eb005a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -52,7 +52,7 @@ class _PlayerSelectionState extends State { void loadPlayerList() { _allPlayersFuture = Future.wait([ db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) => results[0] as List); if (mounted) { _allPlayersFuture.then((loadedPlayers) { -- 2.49.1 From 6e45e9435b5ef9c7bc1606922eae2bd68fbf3f70 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:27:39 +0100 Subject: [PATCH 544/563] Refactored views --- .../main_menu/custom_navigation_bar.dart | 5 ++ .../group_view/create_group_view.dart | 6 +- .../main_menu/group_view/groups_view.dart | 4 + .../views/main_menu/home_view.dart | 69 +++++++++++------- .../create_match/choose_game_view.dart | 6 +- .../create_match/choose_group_view.dart | 3 +- .../create_match/choose_ruleset_view.dart | 1 + .../create_match/create_match_view.dart | 1 - .../match_view/match_result_view.dart | 12 ++- .../main_menu/match_view/match_view.dart | 4 +- .../views/main_menu/statistics_view.dart | 73 +++++++++++-------- 11 files changed, 117 insertions(+), 67 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 1e38808..a8b18c8 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -17,7 +17,10 @@ class CustomNavigationBar extends StatefulWidget { class _CustomNavigationBarState extends State with SingleTickerProviderStateMixin { + /// Currently selected tab index int currentIndex = 0; + + /// Key count to force rebuild of tab views int tabKeyCount = 0; @override @@ -119,12 +122,14 @@ class _CustomNavigationBarState extends State ); } + /// Handles tab tap events. Updates the current [index] state. void onTabTapped(int index) { setState(() { currentIndex = index; }); } + /// Returns the title of the current tab based on [currentIndex]. String _currentTabTitle(context) { final loc = AppLocalizations.of(context); switch (currentIndex) { diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index cba22ef..022a4b5 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -18,15 +18,17 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - final _groupNameController = TextEditingController(); late final AppDatabase db; + /// Controller for the group name input field + final _groupNameController = TextEditingController(); + + /// List of currently selected players List selectedPlayers = []; @override void initState() { super.initState(); - db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c641dde..57d05a4 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -21,7 +21,11 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late final AppDatabase db; + + /// Loaded groups from the database late List loadedGroups; + + /// Loading state bool isLoading = true; List groups = List.filled( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 072699e..170adb4 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -21,9 +21,17 @@ class HomeView extends StatefulWidget { class _HomeViewState extends State { bool isLoading = true; + + /// Amount of matches in the database int matchCount = 0; + + /// Amount of groups in the database int groupCount = 0; + + /// Loaded recent matches from the database List loadedRecentMatches = []; + + /// Recent matches to display, initially filled with skeleton matches List recentMatches = List.filled( 2, Match( @@ -42,32 +50,7 @@ class _HomeViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); - Future.wait([ - db.matchDao.getMatchCount(), - db.groupDao.getGroupCount(), - db.matchDao.getAllMatches(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) { - matchCount = results[0] as int; - groupCount = results[1] as int; - loadedRecentMatches = results[2] as List; - recentMatches = - (loadedRecentMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) - .toList(); - if (loadedRecentMatches.length < 2) { - recentMatches.add( - Match(name: 'Dummy Match', winner: null, group: null, players: null), - ); - } - if (mounted) { - setState(() { - isLoading = false; - }); - } - }); + loadHomeViewData(); } @override @@ -230,6 +213,40 @@ class _HomeViewState extends State { ); } + /// Loads the data for the HomeView from the database. + /// This includes the match count, group count, and recent matches. + void loadHomeViewData() { + final db = Provider.of(context, listen: false); + Future.wait([ + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + loadedRecentMatches = results[2] as List; + recentMatches = + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: 'Dummy Match', winner: null, group: null, players: null), + ); + } + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + + /// Generates a text representation of the players in the match. + /// If the match has a group, it returns the group name and the number of additional players. + /// If there is no group, it returns the count of players. String _getPlayerText(Match game, context) { final loc = AppLocalizations.of(context); if (game.group == null) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 18e1e9d..01981e2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -20,10 +20,12 @@ class ChooseGameView extends StatefulWidget { } class _ChooseGameViewState extends State { - late int selectedGameIndex; - + /// Controller for the search bar final TextEditingController searchBarController = TextEditingController(); + /// Currently selected game index + late int selectedGameIndex; + @override void initState() { selectedGameIndex = widget.initialGameIndex; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 5101db6..011b819 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -136,8 +136,7 @@ class _ChooseGroupViewState extends State { ); } - /// Filters the groups based on the search query. - /// TODO: Maybe implement also targetting player names? + /// Filters the groups based on the search [query]. void filterGroups(String query) { setState(() { if (query.isEmpty) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 7a41417..73be471 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -19,6 +19,7 @@ class ChooseRulesetView extends StatefulWidget { } class _ChooseRulesetViewState extends State { + /// Currently selected ruleset index late int selectedRulesetIndex; @override diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index d3a23ae..775f29d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -26,7 +26,6 @@ class CreateMatchView extends StatefulWidget { } class _CreateMatchViewState extends State { - /// Reference to the app database late final AppDatabase db; /// Controller for the match name input field diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 5c455f6..93ebbc6 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -18,8 +18,12 @@ class MatchResultView extends StatefulWidget { } class _MatchResultViewState extends State { - late final List allPlayers; late final AppDatabase db; + + /// List of all players who participated in the match + late final List allPlayers; + + /// Currently selected winner player Player? _selectedPlayer; @override @@ -132,6 +136,8 @@ class _MatchResultViewState extends State { ); } + /// Handles saving or removing the winner in the database + /// based on the current selection. Future _handleWinnerSaving() async { if (_selectedPlayer == null) { await db.matchDao.removeWinner(matchId: widget.match.id); @@ -144,6 +150,10 @@ class _MatchResultViewState extends State { widget.onWinnerChanged?.call(); } + /// Retrieves all players associated with the given [match]. + /// This includes players directly assigned to the match + /// as well as members of the group (if any). + /// The returned list is sorted alphabetically by player name. List getAllPlayers(Match match) { List players = []; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index bea2f14..45b957f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -28,6 +28,8 @@ class _MatchViewState extends State { late final AppDatabase db; bool isLoading = true; + /// Loaded matches from the database, + /// initially filled with skeleton matches List matches = List.filled( 4, Match( @@ -44,7 +46,6 @@ class _MatchViewState extends State { @override void initState() { super.initState(); - db = Provider.of(context, listen: false); loadGames(); } @@ -117,6 +118,7 @@ class _MatchViewState extends State { ); } + /// Loads the games from the database and sorts them by creation date. void loadGames() { Future.wait([ db.matchDao.getAllMatches(), diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 1125118..a60b854 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,28 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); - - final db = Provider.of(context, listen: false); - - Future.wait([ - db.matchDao.getAllMatches(), - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) async { - if (!mounted) return; - final matches = results[0] as List; - final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players, context); - matchCounts = _calculateMatchAmountsForAllPlayers( - matches, - players, - context, - ); - winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - setState(() { - isLoading = false; - }); - }); + loadStatisticData(); } @override @@ -118,13 +97,43 @@ class _StatisticsViewState extends State { ); } + /// Loads matches and players from the database + /// and calculates statistics for each player + void loadStatisticData() { + final db = Provider.of(context, listen: false); + + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) async { + if (!mounted) return; + final matches = results[0] as List; + final players = results[1] as List; + winCounts = _calculateWinsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); + setState(() { + isLoading = false; + }); + }); + } + /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) - List<(String, int)> _calculateWinsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateWinsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> winCounts = []; final loc = AppLocalizations.of(context); @@ -169,11 +178,11 @@ class _StatisticsViewState extends State { /// Calculates the number of matches played for each player /// and returns a sorted list of tuples (playerName, matchCount) - List<(String, int)> _calculateMatchAmountsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateMatchAmountsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> matchCounts = []; final loc = AppLocalizations.of(context); -- 2.49.1 From 21c74b74bc16b8ea54e297ffdaa41a00ca35580f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:05:19 +0100 Subject: [PATCH 545/563] Refactored components --- lib/core/enums.dart | 3 + lib/presentation/widgets/app_skeleton.dart | 17 +- .../widgets/buttons/custom_width_button.dart | 13 ++ .../widgets/buttons/quick_create_button.dart | 11 +- lib/presentation/widgets/navbar_item.dart | 27 ++- .../widgets/player_selection.dart | 198 ++++++++++-------- .../widgets/text_input/custom_search_bar.dart | 42 +++- .../widgets/text_input/text_input_field.dart | 17 +- .../widgets/tiles/choose_tile.dart | 16 +- .../widgets/tiles/custom_radio_list_tile.dart | 17 +- .../widgets/tiles/group_tile.dart | 6 + lib/presentation/widgets/tiles/info_tile.dart | 31 ++- .../widgets/tiles/match_tile.dart | 35 +++- .../widgets/tiles/quick_info_tile.dart | 31 ++- .../widgets/tiles/settings_list_tile.dart | 23 +- .../widgets/tiles/statistics_tile.dart | 18 ++ .../widgets/tiles/text_icon_list_tile.dart | 19 +- .../widgets/tiles/text_icon_tile.dart | 19 +- .../tiles/title_description_list_tile.dart | 32 ++- .../widgets/top_centered_message.dart | 13 +- 20 files changed, 429 insertions(+), 159 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index ce06f85..17a01f6 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; /// Button types used for styling the [CustomWidthButton] +/// - [ButtonType.primary]: Primary button style. +/// - [ButtonType.secondary]: Secondary button style. +/// - [ButtonType.tertiary]: Tertiary button style. enum ButtonType { primary, secondary, tertiary } /// Result types for import operations in the [SettingsView] diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart index 209f1d8..1d74456 100644 --- a/lib/presentation/widgets/app_skeleton.dart +++ b/lib/presentation/widgets/app_skeleton.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; +/// A widget that provides a skeleton loading effect to its child widget tree. +/// - [child]: The widget tree to apply the skeleton effect to. +/// - [enabled]: A boolean to enable or disable the skeleton effect. +/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher. class AppSkeleton extends StatefulWidget { - final Widget child; - final bool enabled; - final bool fixLayoutBuilder; - const AppSkeleton({ super.key, required this.child, @@ -13,6 +13,15 @@ class AppSkeleton extends StatefulWidget { this.fixLayoutBuilder = false, }); + /// The widget tree to apply the skeleton effect to. + final Widget child; + + /// A boolean to enable or disable the skeleton effect. + final bool enabled; + + /// A boolean to fix the layout builder for AnimatedSwitcher. + final bool fixLayoutBuilder; + @override State createState() => _AppSkeletonState(); } diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 17c9dc5..e27f009 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -2,6 +2,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +/// A custom button widget that is designed to have a width relative to the screen size. +/// It supports three types of buttons: primary, secondary, and text buttons. +/// - [text]: The text to display on the button. +/// - [buttonType]: The type of button to display. Defaults to [ButtonType.primary]. +/// - [sizeRelativeToWidth]: The size of the button relative to the width of the screen. +/// - [onPressed]: The callback to be invoked when the button is pressed. class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, @@ -11,9 +17,16 @@ class CustomWidthButton extends StatelessWidget { this.onPressed, }); + /// The text to display on the button. final String text; + + /// The size of the button relative to the width of the screen. final double sizeRelativeToWidth; + + /// The callback to be invoked when the button is pressed. final VoidCallback? onPressed; + + /// The type of button to display. Depends on the enum [ButtonType]. final ButtonType buttonType; @override diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 3860f1c..2f61805 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -1,15 +1,22 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A button widget designed for quick creating matches in the [HomeView] +/// - [text]: The text to display on the button. +/// - [onPressed]: The callback to be invoked when the button is pressed. class QuickCreateButton extends StatefulWidget { - final String text; - final VoidCallback? onPressed; const QuickCreateButton({ super.key, required this.text, required this.onPressed, }); + /// The text to display on the button. + final String text; + + /// The callback to be invoked when the button is pressed. + final VoidCallback? onPressed; + @override State createState() => _QuickCreateButtonState(); } diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index b249571..13a8d4d 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; +/// A navigation bar item widget that represents a single tab in a navigation bar. +/// - [index]: The index of the tab. +/// - [isSelected]: A boolean indicating whether the tab is currently selected. +/// - [icon]: The icon to display for the tab. +/// - [label]: The label to display for the tab. +/// - [onTabTapped]: The callback to be invoked when the tab is tapped. class NavbarItem extends StatefulWidget { - final int index; - final bool isSelected; - final IconData icon; - final String label; - final Function(int) onTabTapped; - const NavbarItem({ super.key, required this.index, @@ -16,6 +16,21 @@ class NavbarItem extends StatefulWidget { required this.onTabTapped, }); + /// The index of the tab. + final int index; + + /// A boolean indicating whether the tab is currently selected. + final bool isSelected; + + /// The icon to display for the tab. + final IconData icon; + + /// The label to display for the tab. + final String label; + + /// The callback to be invoked when the tab is tapped. + final Function(int) onTabTapped; + @override State createState() => _NavbarItemState(); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9eb005a..99b1e1c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,31 +11,55 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +/// A widget that allows users to select players from a list, +/// with search functionality and the ability to add new players. +/// - [availablePlayers]: An optional list of players to choose from. If null, all +/// players from the database are used. +/// - [initialSelectedPlayers]: An optional list of players that should be pre-selected. +/// - [onChanged]: A callback function that is invoked whenever the selection changes, +/// providing the updated list of selected players. class PlayerSelection extends StatefulWidget { - final Function(List value) onChanged; - final List? availablePlayers; - final List? initialSelectedPlayers; - const PlayerSelection({ super.key, - required this.onChanged, this.availablePlayers, this.initialSelectedPlayers, + required this.onChanged, }); + /// An optional list of players to choose from. If null, all players from the database are used. + final List? availablePlayers; + + /// An optional list of players that should be pre-selected. + final List? initialSelectedPlayers; + + /// A callback function that is invoked whenever the selection changes, + final Function(List value) onChanged; + @override State createState() => _PlayerSelectionState(); } class _PlayerSelectionState extends State { - List selectedPlayers = []; - List suggestedPlayers = []; - List allPlayers = []; + late final AppDatabase db; bool isLoading = true; + + /// Future that loads all players from the database. + late Future> _allPlayersFuture; + + /// The complete list of all available players. + List allPlayers = []; + + /// The list of players suggested based on the search input. + List suggestedPlayers = []; + + /// The list of currently selected players. + List selectedPlayers = []; + + /// Controller for the search bar input. late final TextEditingController _searchBarController = TextEditingController(); - late final AppDatabase db; - late Future> _allPlayersFuture; + + /// Skeleton data used while loading players. late final List skeletonData = List.filled( 7, Player(name: 'Player 0'), @@ -49,42 +73,6 @@ class _PlayerSelectionState extends State { loadPlayerList(); } - void loadPlayerList() { - _allPlayersFuture = Future.wait([ - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) => results[0] as List); - if (mounted) { - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided (even if empty), use that list. - if (widget.availablePlayers != null) { - widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers!]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers!.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - isLoading = false; - }); - }); - } - } - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -227,53 +215,97 @@ class _PlayerSelectionState extends State { ); } + /// Loads the list of players from the database or uses the provided available players. + /// Sets the loading state and updates the player lists accordingly. + void loadPlayerList() { + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) => results[0] as List); + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { + setState(() { + // If a list of available players is provided (even if empty), use that list. + if (widget.availablePlayers != null) { + widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers!]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers!.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } + isLoading = false; + }); + }); + } + } + /// Adds a new player to the database from the search bar input. /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. - void addNewPlayerFromSearch({required BuildContext context}) async { + Future addNewPlayerFromSearch({required BuildContext context}) async { final loc = AppLocalizations.of(context); - String playerName = _searchBarController.text.trim(); - Player createdPlayer = Player(name: playerName); - bool success = await db.playerDao.addPlayer(player: createdPlayer); + final playerName = _searchBarController.text.trim(); + + final createdPlayer = Player(name: playerName); + final success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { - selectedPlayers.insert(0, createdPlayer); - widget.onChanged([...selectedPlayers]); - allPlayers.add(createdPlayer); - setState(() { - _searchBarController.clear(); - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - AppLocalizations.of( - context, - ).successfully_added_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + _handleSuccessfulPlayerCreation(createdPlayer); + showSnackBarMessage(loc.successfully_added_player(playerName)); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - loc.could_not_add_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + showSnackBarMessage(loc.could_not_add_player(playerName)); } } + /// Updates the state after successfully adding a new player. + void _handleSuccessfulPlayerCreation(Player player) { + selectedPlayers.insert(0, player); + widget.onChanged([...selectedPlayers]); + allPlayers.add(player); + + setState(() { + _searchBarController.clear(); + _updateSuggestedPlayers(); + }); + } + + /// Updates the suggested players list based on current selection. + void _updateSuggestedPlayers() { + suggestedPlayers = allPlayers + .where((player) => !selectedPlayers.contains(player)) + .toList(); + } + + /// Displays a snackbar message at the bottom of the screen. + /// [message] - The message to display in the snackbar. + void showSnackBarMessage(String message) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text(message, style: const TextStyle(color: Colors.white)), + ), + ), + ); + } + /// Determines the appropriate info text to display when no players /// are available in the suggested players list. String _getInfoText(BuildContext context) { diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index 35c11e1..bf7971a 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom search bar widget that encapsulates a [SearchBar] with additional customization options. +/// - [controller]: The controller for the search bar's text input. +/// - [hintText]: The hint text displayed in the search bar when it is empty. +/// - [trailingButtonShown]: Whether to show the trailing button. +/// - [trailingButtonicon]: The icon for the trailing button. +/// - [trailingButtonEnabled]: Whether the trailing button is in enabled state. +/// - [onTrailingButtonPressed]: The callback invoked when the trailing button is pressed. +/// - [onChanged]: The callback invoked when the text in the search bar changes. +/// - [constraints]: The constraints for the search bar. class CustomSearchBar extends StatelessWidget { - final TextEditingController controller; - final String hintText; - final ValueChanged? onChanged; - final BoxConstraints? constraints; - final bool trailingButtonShown; - final bool trailingButtonEnabled; - final VoidCallback? onTrailingButtonPressed; - final IconData trailingButtonicon; - const CustomSearchBar({ super.key, required this.controller, @@ -23,6 +23,30 @@ class CustomSearchBar extends StatelessWidget { this.constraints, }); + /// The controller for the search bar's text input. + final TextEditingController controller; + + /// The hint text displayed in the search bar when it is empty. + final String hintText; + + /// Whether to show the trailing button. + final bool trailingButtonShown; + + /// The icon for the trailing button. + final IconData trailingButtonicon; + + /// Whether the trailing button is in enabled state. + final bool trailingButtonEnabled; + + /// The callback invoked when the trailing button is pressed. + final VoidCallback? onTrailingButtonPressed; + + /// The callback invoked when the text in the search bar changes. + final ValueChanged? onChanged; + + /// The constraints for the search bar. + final BoxConstraints? constraints; + @override Widget build(BuildContext context) { return SearchBar( diff --git a/lib/presentation/widgets/text_input/text_input_field.dart b/lib/presentation/widgets/text_input/text_input_field.dart index 6cd9d75..a409c68 100644 --- a/lib/presentation/widgets/text_input/text_input_field.dart +++ b/lib/presentation/widgets/text_input/text_input_field.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom text input field widget that encapsulates a [TextField] with specific styling. +/// - [controller]: The controller for the text input field. +/// - [onChanged]: The callback invoked when the text in the field changes. +/// - [hintText]: The hint text displayed in the text input field when it is empty class TextInputField extends StatelessWidget { - final TextEditingController controller; - final ValueChanged? onChanged; - final String hintText; - const TextInputField({ super.key, required this.controller, @@ -13,6 +13,15 @@ class TextInputField extends StatelessWidget { this.onChanged, }); + /// The controller for the text input field. + final TextEditingController controller; + + /// The callback invoked when the text in the field changes. + final ValueChanged? onChanged; + + /// The hint text displayed in the text input field when it is empty. + final String hintText; + @override Widget build(BuildContext context) { return TextField( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index 10a695d..ba12c3d 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that allows users to choose an option by tapping on it. +/// - [title]: The title text displayed on the tile. +/// - [trailingText]: Optional trailing text displayed on the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class ChooseTile extends StatefulWidget { - final String title; - final VoidCallback? onPressed; - final String? trailingText; const ChooseTile({ super.key, required this.title, @@ -12,6 +13,15 @@ class ChooseTile extends StatefulWidget { this.onPressed, }); + /// The title text displayed on the tile. + final String title; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// Optional trailing text displayed on the tile. + final String? trailingText; + @override State createState() => _ChooseTileState(); } diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 11e8b40..d76cf3f 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality. +/// - [text]: The text to display next to the radio button. +/// - [value]: The value associated with the radio button. +/// - [onContainerTap]: The callback invoked when the container is tapped. class CustomRadioListTile extends StatelessWidget { - final String text; - final T value; - final ValueChanged onContainerTap; - const CustomRadioListTile({ super.key, required this.text, @@ -13,6 +13,15 @@ class CustomRadioListTile extends StatelessWidget { required this.onContainerTap, }); + /// The text to display next to the radio button. + final String text; + + /// The value associated with the radio button. + final T value; + + /// The callback invoked when the container is tapped. + final ValueChanged onContainerTap; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 5f870de..8dee1cd 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -3,10 +3,16 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +/// A tile widget that displays information about a group, including its name and members. +/// - [group]: The group data to be displayed. +/// - [isHighlighted]: Whether the tile should be highlighted. class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group, this.isHighlighted = false}); + /// The group data to be displayed. final Group group; + + /// Whether the tile should be highlighted. final bool isHighlighted; @override diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index ff73e59..3e11679 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and some content below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [content]: The content widget displayed below the title. +/// - [padding]: Optional padding for the tile content. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. class InfoTile extends StatefulWidget { - final String title; - final IconData icon; - final Widget content; - final EdgeInsets? padding; - final double? height; - final double? width; const InfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class InfoTile extends StatefulWidget { this.width, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The content widget displayed below the title. + final Widget content; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + @override State createState() => _InfoTileState(); } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index c455949..d764dd9 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -1,26 +1,41 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; +/// A tile widget that displays information about a match, including its name, +/// creation date, associated group, winner, and players. +/// - [match]: The match data to be displayed. +/// - [onTap]: The callback invoked when the tile is tapped. class MatchTile extends StatefulWidget { - final Match match; - final VoidCallback onTap; - const MatchTile({super.key, required this.match, required this.onTap}); + /// The match data to be displayed. + final Match match; + + /// The callback invoked when the tile is tapped. + final VoidCallback onTap; + @override State createState() => _MatchTileState(); } class _MatchTileState extends State { + late final List _allPlayers; + + @override + void initState() { + super.initState(); + _allPlayers = _getCombinedPlayers(); + } + @override Widget build(BuildContext context) { final group = widget.match.group; final winner = widget.match.winner; - final allPlayers = _getAllPlayers(); final loc = AppLocalizations.of(context); return GestureDetector( @@ -114,7 +129,7 @@ class _MatchTileState extends State { const SizedBox(height: 12), ], - if (allPlayers.isNotEmpty) ...[ + if (_allPlayers.isNotEmpty) ...[ Text( loc.players, style: const TextStyle( @@ -127,7 +142,7 @@ class _MatchTileState extends State { Wrap( spacing: 6, runSpacing: 6, - children: allPlayers.map((player) { + children: _allPlayers.map((player) { return TextIconTile(text: player.name, iconEnabled: false); }).toList(), ), @@ -138,6 +153,8 @@ class _MatchTileState extends State { ); } + /// Formats the given [dateTime] into a human-readable string based on its + /// difference from the current date. String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); @@ -158,8 +175,10 @@ class _MatchTileState extends State { } } - List _getAllPlayers() { - final allPlayers = []; + /// Retrieves all unique players associated with the match, + /// combining players from both the match and its group. + List _getCombinedPlayers() { + final allPlayers = []; final playerIds = {}; // Add players from game.players diff --git a/lib/presentation/widgets/tiles/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart index d360aba..839f6c3 100644 --- a/lib/presentation/widgets/tiles/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and a numeric value below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [value]: The numeric value displayed below the title. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. +/// - [padding]: Optional padding for the tile content. class QuickInfoTile extends StatefulWidget { - final String title; - final IconData icon; - final int value; - final double? height; - final double? width; - final EdgeInsets? padding; const QuickInfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class QuickInfoTile extends StatefulWidget { this.padding, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The numeric value displayed below the title. + final int value; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + @override State createState() => _QuickInfoTileState(); } diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 6b43557..7fb0f80 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,19 +1,32 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. +/// - [icon]: The icon displayed on the left side of the tile. +/// - [title]: The title text displayed next to the icon. +/// - [suffixWidget]: An optional widget displayed on the right side of the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class SettingsListTile extends StatelessWidget { - final VoidCallback? onPressed; - final IconData icon; - final String title; - final Widget? suffixWidget; const SettingsListTile({ super.key, - required this.title, required this.icon, + required this.title, this.suffixWidget, this.onPressed, }); + /// The icon displayed on the left side of the tile. + final IconData icon; + + /// The title text displayed next to the icon. + final String title; + + /// An optional widget displayed on the right side of the tile. + final Widget? suffixWidget; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Padding( diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 598fad0..57ceb04 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -4,6 +4,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +/// A tile widget that displays statistical data using horizontal bars. +/// - [icon]: The icon displayed next to the title. +/// - [title]: The title text displayed on the tile. +/// - [width]: The width of the tile. +/// - [values]: A list of tuples containing labels and their corresponding numeric values. +/// - [itemCount]: The maximum number of items to display. +/// - [barColor]: The color of the bars representing the values. class StatisticsTile extends StatelessWidget { const StatisticsTile({ super.key, @@ -15,11 +22,22 @@ class StatisticsTile extends StatelessWidget { required this.barColor, }); + /// The icon displayed next to the title. final IconData icon; + + /// The title text displayed on the tile. final String title; + + /// The width of the tile. final double width; + + /// A list of tuples containing labels and their corresponding numeric values. final List<(String, num)> values; + + /// The maximum number of items to display. final int itemCount; + + /// The color of the bars representing the values. final Color barColor; @override diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index b23ef75..7d3fe1c 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A list tile widget that displays text with an optional icon button. +/// - [text]: The text to display in the tile. +/// - [onPressed]: The callback to be invoked when the icon is pressed. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. class TextIconListTile extends StatelessWidget { - final String text; - final VoidCallback? onPressed; - final bool iconEnabled; - const TextIconListTile({ super.key, required this.text, - this.onPressed, this.iconEnabled = true, + this.onPressed, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is pressed. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 2544837..7142b27 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays text with an optional icon that can be tapped. +/// - [text]: The text to display in the tile. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. +/// - [onIconTap]: The callback to be invoked when the icon is tapped. class TextIconTile extends StatelessWidget { - final String text; - final bool iconEnabled; - final VoidCallback? onIconTap; - const TextIconTile({ super.key, required this.text, - this.onIconTap, this.iconEnabled = true, + this.onIconTap, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is tapped. + final VoidCallback? onIconTap; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 465c94d..781149e 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A list tile widget that displays a title and description, with optional highlighting and badge. +/// - [title]: The title text displayed on the tile. +/// - [description]: The description text displayed below the title. +/// - [onPressed]: The callback invoked when the tile is tapped. +/// - [isHighlighted]: A boolean to determine if the tile should be highlighted. +/// - [badgeText]: Optional text to display in a badge on the right side of the title. +/// - [badgeColor]: Optional color for the badge background. class TitleDescriptionListTile extends StatelessWidget { - final String title; - final String description; - final VoidCallback? onPressed; - final bool isHighlighted; - final String? badgeText; - final Color? badgeColor; - const TitleDescriptionListTile({ super.key, required this.title, @@ -19,6 +19,24 @@ class TitleDescriptionListTile extends StatelessWidget { this.badgeColor, }); + /// The title text displayed on the tile. + final String title; + + /// The description text displayed below the title. + final String description; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// A boolean to determine if the tile should be highlighted. + final bool isHighlighted; + + /// Optional text to display in a badge on the right side of the title. + final String? badgeText; + + /// Optional color for the badge background. + final Color? badgeColor; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index a5deea2..c15c93d 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +/// A widget that displays a message centered at the top of the screen with an icon, title, and message. +/// - [icon]: The icon to display above the title. +/// - [title]: The title text to display. +/// - [message]: The message text to display below the title. class TopCenteredMessage extends StatelessWidget { const TopCenteredMessage({ super.key, @@ -8,10 +12,15 @@ class TopCenteredMessage extends StatelessWidget { required this.message, }); - final String title; - final String message; + /// The icon to display above the title. final IconData icon; + /// The title text to display. + final String title; + + /// The message text to display below the title. + final String message; + @override Widget build(BuildContext context) { return Container( -- 2.49.1 From 0f824bb30a3feb48804f5f00342f7e465b9810c4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:10:09 +0100 Subject: [PATCH 546/563] Optimized statistics tile --- .../widgets/tiles/statistics_tile.dart | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 57ceb04..2c0ced0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -42,7 +42,6 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { - final maxBarWidth = MediaQuery.of(context).size.width * 0.65; final loc = AppLocalizations.of(context); return InfoTile( @@ -57,38 +56,56 @@ class StatisticsTile extends StatelessWidget { heightFactor: 4, child: Text(loc.no_data_available), ), - child: Column( - children: List.generate(min(values.length, itemCount), (index) { - /// The maximum wins among all players - final maxMatches = values.isNotEmpty ? values[0].$2 : 0; + child: LayoutBuilder( + builder: (context, constraints) { + final maxBarWidth = constraints.maxWidth * 0.65; + return Column( + children: List.generate(min(values.length, itemCount), (index) { + /// The maximum wins among all players + final maxMatches = values.isNotEmpty ? values[0].$2 : 0; - /// Fraction of wins - final double fraction = (maxMatches > 0) - ? (values[index].$2 / maxMatches) - : 0.0; + /// Fraction of wins + final double fraction = (maxMatches > 0) + ? (values[index].$2 / maxMatches) + : 0.0; - /// Calculated width for current the bar - final double barWidth = maxBarWidth * fraction; + /// Calculated width for current the bar + final double barWidth = maxBarWidth * fraction; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Stack( + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Container( - height: 24, - width: barWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: barColor, - ), + Stack( + children: [ + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barColor, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + values[index].$1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), - Padding( - padding: const EdgeInsets.only(left: 4.0), + const Spacer(), + Center( child: Text( - values[index].$1, + values[index].$2 <= 1 && values[index].$2 is double + ? values[index].$2.toStringAsFixed(2) + : values[index].$2.toString(), + textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -97,23 +114,10 @@ class StatisticsTile extends StatelessWidget { ), ], ), - const Spacer(), - Center( - child: Text( - values[index].$2 <= 1 && values[index].$2 is double - ? values[index].$2.toStringAsFixed(2) - : values[index].$2.toString(), - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), + ); + }), ); - }), + }, ), ), ), -- 2.49.1 From 4b1d3923a094cdc977db2011dcbb55fbe2022d30 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:11:14 +0100 Subject: [PATCH 547/563] Optimized ruleset touples --- .../create_match/create_match_view.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 775f29d..2bc56e6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -67,6 +67,9 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; + /// List of available rulesets with their localized string representations + late final List<(Ruleset, String)> _rulesets; + @override void initState() { super.initState(); @@ -88,9 +91,11 @@ class _CreateMatchViewState extends State { }); } - List<(Ruleset, String)> _getRulesets(BuildContext context) { + @override + void didChangeDependencies() { + super.didChangeDependencies(); final loc = AppLocalizations.of(context); - return [ + _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), (Ruleset.mostPoints, loc.ruleset_most_points), @@ -147,9 +152,9 @@ class _CreateMatchViewState extends State { if (selectedGameIndex != -1) { hintText = games[selectedGameIndex].$1; selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + selectedRulesetIndex = _rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); } else { hintText = 'Match Name'; selectedRuleset = null; @@ -163,17 +168,16 @@ class _CreateMatchViewState extends State { ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { - final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); if (!mounted) return; - selectedRulesetIndex = rulesets.indexWhere( + selectedRulesetIndex = _rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); selectedGameIndex = -1; -- 2.49.1 From 2811ea892e013359e4a1f4572437feafa621f189 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:15:23 +0100 Subject: [PATCH 548/563] Added dispose & formatting --- .../match_view/create_match/create_match_view.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 2bc56e6..fe1e8f5 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -91,6 +91,12 @@ class _CreateMatchViewState extends State { }); } + @override + void dispose() { + _matchNameController.dispose(); + super.dispose(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -262,8 +268,8 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1) && - selectedRuleset != null; + return (selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.length > 1)) && + selectedRuleset != null; } } -- 2.49.1 From aa936a938d71a8b6d9df6392d73b9f8ceacd45ca Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:23:47 +0100 Subject: [PATCH 549/563] Corrected translation --- lib/l10n/arb/app_de.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 89354bd..14e3213 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:in: {count}", + "selected_players": "Ausgewählte Spieler:innen: {count}", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", -- 2.49.1 From bc3beac866f6ec95bea480a58ebf6646db2e068e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:25:14 +0100 Subject: [PATCH 550/563] Replaced Matches with Spiele --- lib/l10n/arb/app_de.arb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 14e3213..de7db12 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2,16 +2,16 @@ "@@locale": "de", "all_players": "Alle Spieler:innen:", "all_players_selected": "Alle Spieler:innen ausgewählt", - "amount_of_matches": "Anzahl der Matches", + "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", "choose_ruleset": "Regelwerk wählen", "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", "create_group": "Gruppe erstellen", - "create_match": "Match erstellen", + "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", - "create_new_match": "Neues Match erstellen", + "create_new_match": "Neues Spiel erstellen", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_imported": "Daten erfolgreich importiert", @@ -34,19 +34,19 @@ "info": "Info", "invalid_schema": "Ungültiges Schema", "least_points": "Niedrigste Punkte", - "match_in_progress": "Match läuft...", - "match_name": "Matchname", - "matches": "Matches", + "match_in_progress": "Spiel läuft...", + "match_name": "Spieltitel", + "matches": "Spiele", "menu": "Menü", "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", - "no_matches_created_yet": "Noch keine Matches erstellt", + "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "no_players_selected": "Keine Spieler:in ausgewählt", - "no_recent_matches_available": "Keine letzten Matches verfügbar", - "no_second_match_available": "Kein zweites Match verfügbar", + "no_recent_matches_available": "Keine letzten Spiele verfügbar", + "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", "none": "Kein", "none_group": "Keine", @@ -55,7 +55,7 @@ "players": "Spieler:in", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", - "recent_matches": "Letzte Matches", + "recent_matches": "Letzte Spiele", "ruleset": "Regelwerk", "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", -- 2.49.1 From e196d6e5ef12a69d15fde17d105b52b857ff1960 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:32:34 +0100 Subject: [PATCH 551/563] Corrected strings --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/generated/app_localizations.dart | 732 +++++++++---------- lib/l10n/generated/app_localizations_de.dart | 306 ++++---- lib/l10n/generated/app_localizations_en.dart | 388 +++++----- 4 files changed, 713 insertions(+), 717 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index de7db12..f55668e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -52,7 +52,7 @@ "none_group": "Keine", "not_available": "Nicht verfügbar", "player_name": "Spieler:innenname", - "players": "Spieler:in", + "players": "Spieler:innen", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner:in: {winnerName}", + "winner": "Gewinner*in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 951ff22..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -98,23 +98,29 @@ abstract class AppLocalizations { Locale('en'), ]; - /// Label for choosing a group + /// Label for all players list /// /// In en, this message translates to: - /// **'Choose Group'** - String get choose_group; + /// **'All players:'** + String get all_players; - /// Button text to create a new match + /// Message when all players are added to selection /// /// In en, this message translates to: - /// **'Create new match'** - String get create_new_match; + /// **'All players selected'** + String get all_players_selected; - /// Label for choosing a ruleset + /// Label for amount of matches statistic /// /// In en, this message translates to: - /// **'Choose Ruleset'** - String get choose_ruleset; + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; /// Label for choosing a game /// @@ -122,11 +128,125 @@ abstract class AppLocalizations { /// **'Choose Game'** String get choose_game; - /// Label to select the winner + /// Label for choosing a group /// /// In en, this message translates to: - /// **'Select Winner:'** - String get select_winner; + /// **'Choose Group'** + String get choose_group; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_creating_group; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; /// App Name /// @@ -134,6 +254,126 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get game_tracker; + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data_available; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -146,294 +386,12 @@ abstract class AppLocalizations { /// **'No second match available'** String get no_second_match_available; - /// Confirmation dialog for deleting all data - /// - /// In en, this message translates to: - /// **'Delete all data?'** - String get delete_all_data; - - /// Cancel button text - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Delete button text - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// Button text to create a new group - /// - /// In en, this message translates to: - /// **'Create new group'** - String get create_new_group; - - /// Error message when group creation fails - /// - /// In en, this message translates to: - /// **'Error while creating group, please try again'** - String get error_creating_group; - - /// Shows the number of selected players - /// - /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); - - /// Message when no players are selected - /// - /// In en, this message translates to: - /// **'No players selected'** - String get no_players_selected; - - /// Label for all players list - /// - /// In en, this message translates to: - /// **'All players:'** - String get all_players; - - /// Success message when adding a player - /// - /// In en, this message translates to: - /// **'Successfully added player {playerName}'** - String successfully_added_player(String playerName); - - /// Error message when adding a player fails - /// - /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); - - /// Shows the winner's name - /// - /// In en, this message translates to: - /// **'Winner: {winnerName}'** - String winner(String winnerName); - - /// Players label - /// - /// In en, this message translates to: - /// **'Players'** - String get players; - /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: /// **'No statistics available'** String get no_statistics_available; - /// Message when no data in the statistic tiles is given - /// - /// In en, this message translates to: - /// **'No data available'** - String get no_data_available; - - /// Label for matches - /// - /// In en, this message translates to: - /// **'Matches'** - String get matches; - - /// Label for groups - /// - /// In en, this message translates to: - /// **'Groups'** - String get groups; - - /// Title for recent matches section - /// - /// In en, this message translates to: - /// **'Recent Matches'** - String get recent_matches; - - /// Title for quick create section - /// - /// In en, this message translates to: - /// **'Quick Create'** - String get quick_create; - - /// Message when match is in progress - /// - /// In en, this message translates to: - /// **'Match in progress...'** - String get match_in_progress; - - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Export data menu item - /// - /// In en, this message translates to: - /// **'Export data'** - String get export_data; - - /// Import data menu item - /// - /// In en, this message translates to: - /// **'Import data'** - String get import_data; - - /// Warning message for irreversible actions - /// - /// In en, this message translates to: - /// **'This can\'t be undone'** - String get this_cannot_be_undone; - - /// Success message after deleting data - /// - /// In en, this message translates to: - /// **'Data successfully deleted'** - String get data_successfully_deleted; - - /// Success message after importing data - /// - /// In en, this message translates to: - /// **'Data successfully imported'** - String get data_successfully_imported; - - /// Error message for invalid schema - /// - /// In en, this message translates to: - /// **'Invalid Schema'** - String get invalid_schema; - - /// Error message when file cannot be read - /// - /// In en, this message translates to: - /// **'Error reading file'** - String get error_reading_file; - - /// Message when import is canceled - /// - /// In en, this message translates to: - /// **'Import canceled'** - String get import_canceled; - - /// Error message for format exceptions - /// - /// In en, this message translates to: - /// **'Format Exception (see console)'** - String get format_exception; - - /// Error message for unknown exceptions - /// - /// In en, this message translates to: - /// **'Unknown Exception (see console)'** - String get unknown_exception; - - /// Success message after exporting data - /// - /// In en, this message translates to: - /// **'Data successfully exported'** - String get data_successfully_exported; - - /// Message when export is canceled - /// - /// In en, this message translates to: - /// **'Export canceled'** - String get export_canceled; - - /// Undo button text - /// - /// In en, this message translates to: - /// **'Undo'** - String get undo; - - /// Label for wins statistic - /// - /// In en, this message translates to: - /// **'Wins'** - String get wins; - - /// Label for winrate statistic - /// - /// In en, this message translates to: - /// **'Winrate'** - String get winrate; - - /// Label for amount of matches statistic - /// - /// In en, this message translates to: - /// **'Amount of Matches'** - String get amount_of_matches; - - /// Info label - /// - /// In en, this message translates to: - /// **'Info'** - String get info; - - /// Message when no groups exist - /// - /// In en, this message translates to: - /// **'No groups created yet'** - String get no_groups_created_yet; - - /// Message when no players exist - /// - /// In en, this message translates to: - /// **'No players created yet'** - String get no_players_created_yet; - - /// Button text to create a group - /// - /// In en, this message translates to: - /// **'Create Group'** - String get create_group; - - /// Placeholder for group name input - /// - /// In en, this message translates to: - /// **'Group name'** - String get group_name; - - /// Placeholder for player name input - /// - /// In en, this message translates to: - /// **'Player name'** - String get player_name; - - /// Message when no matches exist - /// - /// In en, this message translates to: - /// **'No matches created yet'** - String get no_matches_created_yet; - - /// Placeholder for match name input - /// - /// In en, this message translates to: - /// **'Match name'** - String get match_name; - - /// Game label - /// - /// In en, this message translates to: - /// **'Game'** - String get game; - - /// Ruleset label - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset; - - /// Group label - /// - /// In en, this message translates to: - /// **'Group'** - String get group; - /// None option label /// /// In en, this message translates to: @@ -446,47 +404,113 @@ abstract class AppLocalizations { /// **'None'** String get none_group; - /// Button text to create a match + /// Abbreviation for not available /// /// In en, this message translates to: - /// **'Create match'** - String get create_match; + /// **'Not available'** + String get not_available; - /// Message when search returns no results + /// Placeholder for player name input /// /// In en, this message translates to: - /// **'No players found with that name'** - String get no_players_found_with_that_name; + /// **'Player name'** + String get player_name; - /// Message when all players are added to selection + /// Players label /// /// In en, this message translates to: - /// **'All players selected'** - String get all_players_selected; + /// **'Players'** + String get players; - /// Date format for today + /// Shows the number of players /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'{count} Players'** + String players_count(int count); - /// Date format for yesterday + /// Title for quick create section /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Quick Create'** + String get quick_create; - /// Date format for days ago + /// Title for recent matches section /// /// In en, this message translates to: - /// **'{count} days ago'** - String days_ago(int count); + /// **'Recent Matches'** + String get recent_matches; - /// Home tab label + /// Ruleset label /// /// In en, this message translates to: - /// **'Home'** - String get home; + /// **'Ruleset'** + String get ruleset; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; /// Statistics tab label /// @@ -500,11 +524,11 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; - /// Shows the number of players + /// Success message when adding a player /// /// In en, this message translates to: - /// **'{count} Players'** - String players_count(int count); + /// **'Successfully added player {playerName}'** + String successfully_added_player(String playerName); /// Message when search returns no groups /// @@ -512,77 +536,53 @@ abstract class AppLocalizations { /// **'There is no group matching your search'** String get there_is_no_group_matching_your_search; - /// Placeholder for game name search + /// Warning message for irreversible actions /// /// In en, this message translates to: - /// **'Game Name'** - String get game_name; + /// **'This can\'t be undone'** + String get this_cannot_be_undone; - /// Description for single winner ruleset + /// Date format for today /// /// In en, this message translates to: - /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner; + /// **'Today at {time}'** + String today_at(String time); - /// Description for single loser ruleset + /// Undo button text /// /// In en, this message translates to: - /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser; + /// **'Undo'** + String get undo; - /// Description for most points ruleset + /// Error message for unknown exceptions /// /// In en, this message translates to: - /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points; + /// **'Unknown Exception (see console)'** + String get unknown_exception; - /// Description for least points ruleset + /// Winner label /// /// In en, this message translates to: - /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points; + /// **'Winner'** + String get winner; - /// Title for single winner ruleset + /// Label for winrate statistic /// /// In en, this message translates to: - /// **'Single Winner'** - String get single_winner; + /// **'Winrate'** + String get winrate; - /// Title for single loser ruleset + /// Label for wins statistic /// /// In en, this message translates to: - /// **'Single Loser'** - String get single_loser; + /// **'Wins'** + String get wins; - /// Title for most points ruleset + /// Date format for yesterday /// /// In en, this message translates to: - /// **'Most Points'** - String get most_points; - - /// Title for least points ruleset - /// - /// In en, this message translates to: - /// **'Least Points'** - String get least_points; - - /// Hint text for player search input field - /// - /// In en, this message translates to: - /// **'Search for players'** - String get search_for_players; - - /// Hint text for group search input field - /// - /// In en, this message translates to: - /// **'Search for groups'** - String get search_for_groups; - - /// Abbreviation for not available - /// - /// In en, this message translates to: - /// **'Not available'** - String get not_available; + /// **'Yesterday at {time}'** + String yesterday_at(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3f3e36e..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,65 +9,25 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get choose_group => 'Gruppe wählen'; + String get all_players => 'Alle Spieler:innen:'; @override - String get create_new_match => 'Neues Match erstellen'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get choose_ruleset => 'Regelwerk wählen'; - - @override - String get choose_game => 'Spielvorlage wählen'; - - @override - String get select_winner => 'Gewinner:in wählen:'; - - @override - String get game_tracker => 'Game Tracker'; - - @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; - - @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; - - @override - String get delete_all_data => 'Alle Daten löschen?'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @override - String get delete => 'Löschen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get create_new_group => 'Neue Gruppe erstellen'; + String get choose_group => 'Gruppe wählen'; @override - String get error_creating_group => - 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; - - @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:in: $countString'; - } - - @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; - - @override - String get all_players => 'Alle Spieler:innen:'; - - @override - String successfully_added_player(String playerName) { - return 'Spieler:in $playerName erfolgreich hinzugefügt'; - } + String get choose_ruleset => 'Regelwerk wählen'; @override String could_not_add_player(String playerName) { @@ -75,121 +35,131 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String winner(String winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get create_group => 'Gruppe erstellen'; @override - String get players => 'Spieler:in'; + String get create_match => 'Spiel erstellen'; @override - String get no_statistics_available => 'Keine Statistiken verfügbar'; + String get create_new_group => 'Neue Gruppe erstellen'; @override - String get no_data_available => 'Keine Daten verfügbar'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Gruppen'; - - @override - String get recent_matches => 'Letzte Matches'; - - @override - String get quick_create => 'Schnellzugriff'; - - @override - String get match_in_progress => 'Match läuft...'; - - @override - String get menu => 'Menü'; - - @override - String get settings => 'Einstellungen'; - - @override - String get export_data => 'Daten exportieren'; - - @override - String get import_data => 'Daten importieren'; - - @override - String get this_cannot_be_undone => - 'Dies kann nicht rückgängig gemacht werden'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + @override String get data_successfully_imported => 'Daten erfolgreich importiert'; @override - String get invalid_schema => 'Ungültiges Schema'; + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get delete => 'Löschen'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get error_creating_group => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override String get error_reading_file => 'Fehler beim Lesen der Datei'; @override - String get import_canceled => 'Import abgebrochen'; + String get export_canceled => 'Export abgebrochen'; + + @override + String get export_data => 'Daten exportieren'; @override String get format_exception => 'Formatfehler (siehe Konsole)'; @override - String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + String get game => 'Spielvorlage'; @override - String get data_successfully_exported => 'Daten erfolgreich exportiert'; + String get game_name => 'Spielvorlagenname'; @override - String get export_canceled => 'Export abgebrochen'; + String get game_tracker => 'Game Tracker'; @override - String get undo => 'Rückgängig'; - - @override - String get wins => 'Siege'; - - @override - String get winrate => 'Siegquote'; - - @override - String get amount_of_matches => 'Anzahl der Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; - - @override - String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; - - @override - String get create_group => 'Gruppe erstellen'; + String get group => 'Gruppe'; @override String get group_name => 'Gruppenname'; @override - String get player_name => 'Spieler:innenname'; + String get groups => 'Gruppen'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get home => 'Startseite'; @override - String get match_name => 'Matchname'; + String get import_canceled => 'Import abgebrochen'; @override - String get game => 'Spielvorlage'; + String get import_data => 'Daten importieren'; @override - String get ruleset => 'Regelwerk'; + String get info => 'Info'; @override - String get group => 'Gruppe'; + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get least_points => 'Niedrigste Punkte'; + + @override + String get match_in_progress => 'Spiel läuft...'; + + @override + String get match_name => 'Spieltitel'; + + @override + String get matches => 'Spiele'; + + @override + String get menu => 'Menü'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler:in mit diesem Namen gefunden'; + + @override + String get no_players_selected => 'Keine Spieler:in ausgewählt'; + + @override + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; + + @override + String get no_statistics_available => 'Keine Statistiken verfügbar'; @override String get none => 'Kein'; @@ -198,32 +168,71 @@ class AppLocalizationsDe extends AppLocalizations { String get none_group => 'Keine'; @override - String get create_match => 'Match erstellen'; + String get not_available => 'Nicht verfügbar'; @override - String get no_players_found_with_that_name => - 'Keine Spieler:in mit diesem Namen gefunden'; + String get player_name => 'Spieler:innenname'; @override - String get all_players_selected => 'Alle Spieler:innen ausgewählt'; + String get players => 'Spieler:innen'; @override - String today_at(String time) { - return 'Heute um $time'; + String players_count(int count) { + return '$count Spieler'; } @override - String yesterday_at(String time) { - return 'Gestern um $time'; + String get quick_create => 'Schnellzugriff'; + + @override + String get recent_matches => 'Letzte Spiele'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + + @override + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get search_for_players => 'Nach Spieler:innen suchen'; + + @override + String get select_winner => 'Gewinner:in wählen:'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler:innen: $countString'; } @override - String days_ago(int count) { - return 'vor $count Tagen'; - } + String get settings => 'Einstellungen'; @override - String get home => 'Startseite'; + String get single_loser => 'Ein:e Verlierer:in'; + + @override + String get single_winner => 'Ein:e Gewinner:in'; @override String get statistics => 'Statistiken'; @@ -232,8 +241,8 @@ class AppLocalizationsDe extends AppLocalizations { String get stats => 'Statistiken'; @override - String players_count(int count) { - return '$count Spieler'; + String successfully_added_player(String playerName) { + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override @@ -241,42 +250,31 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielvorlagenname'; + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; @override - String get ruleset_single_winner => - 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String today_at(String time) { + return 'Heute um $time'; + } @override - String get ruleset_single_loser => - 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get undo => 'Rückgängig'; @override - String get ruleset_most_points => - 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get ruleset_least_points => - 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + String get winner => 'Gewinner*in'; @override - String get single_winner => 'Ein:e Gewinner:in'; + String get winrate => 'Siegquote'; @override - String get single_loser => 'Ein:e Verlierer:in'; + String get wins => 'Siege'; @override - String get most_points => 'Höchste Punkte'; - - @override - String get least_points => 'Niedrigste Punkte'; - - @override - String get search_for_players => 'Nach Spieler:innen suchen'; - - @override - String get search_for_groups => 'Nach Gruppen suchen'; - - @override - String get not_available => 'Nicht verfügbar'; + String yesterday_at(String time) { + return 'Gestern um $time'; + } } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 263714c..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,23 +9,149 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get choose_group => 'Choose Group'; + String get all_players => 'All players:'; @override - String get create_new_match => 'Create new match'; + String get all_players_selected => 'All players selected'; @override - String get choose_ruleset => 'Choose Ruleset'; + String get amount_of_matches => 'Amount of Matches'; + + @override + String get cancel => 'Cancel'; @override String get choose_game => 'Choose Game'; @override - String get select_winner => 'Select Winner:'; + String get choose_group => 'Choose Group'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; + } + + @override + String get create_group => 'Create Group'; + + @override + String get create_match => 'Create match'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get delete => 'Delete'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get error_creating_group => + 'Error while creating group, please try again'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get export_data => 'Export data'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get game => 'Game'; + + @override + String get game_name => 'Game Name'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Group'; + + @override + String get group_name => 'Group name'; + + @override + String get groups => 'Groups'; + + @override + String get home => 'Home'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get import_data => 'Import data'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get least_points => 'Least Points'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get match_name => 'Match name'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menu'; + + @override + String get most_points => 'Most Points'; + + @override + String get no_data_available => 'No data available'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get no_players_selected => 'No players selected'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -33,20 +159,61 @@ class AppLocalizationsEn extends AppLocalizations { String get no_second_match_available => 'No second match available'; @override - String get delete_all_data => 'Delete all data?'; + String get no_statistics_available => 'No statistics available'; @override - String get cancel => 'Cancel'; + String get none => 'None'; @override - String get delete => 'Delete'; + String get none_group => 'None'; @override - String get create_new_group => 'Create new group'; + String get not_available => 'Not available'; @override - String get error_creating_group => - 'Error while creating group, please try again'; + String get player_name => 'Player name'; + + @override + String get players => 'Players'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get quick_create => 'Quick Create'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get ruleset_least_points => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get ruleset_most_points => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_single_loser => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_single_winner => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get search_for_groups => 'Search for groups'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get select_winner => 'Select Winner:'; @override String selected_players(int count) { @@ -58,171 +225,14 @@ class AppLocalizationsEn extends AppLocalizations { return 'Selected players: $countString'; } - @override - String get no_players_selected => 'No players selected'; - - @override - String get all_players => 'All players:'; - - @override - String successfully_added_player(String playerName) { - return 'Successfully added player $playerName'; - } - - @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; - } - - @override - String winner(String winnerName) { - return 'Winner: $winnerName'; - } - - @override - String get players => 'Players'; - - @override - String get no_statistics_available => 'No statistics available'; - - @override - String get no_data_available => 'No data available'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Groups'; - - @override - String get recent_matches => 'Recent Matches'; - - @override - String get quick_create => 'Quick Create'; - - @override - String get match_in_progress => 'Match in progress...'; - - @override - String get menu => 'Menu'; - @override String get settings => 'Settings'; @override - String get export_data => 'Export data'; + String get single_loser => 'Single Loser'; @override - String get import_data => 'Import data'; - - @override - String get this_cannot_be_undone => 'This can\'t be undone'; - - @override - String get data_successfully_deleted => 'Data successfully deleted'; - - @override - String get data_successfully_imported => 'Data successfully imported'; - - @override - String get invalid_schema => 'Invalid Schema'; - - @override - String get error_reading_file => 'Error reading file'; - - @override - String get import_canceled => 'Import canceled'; - - @override - String get format_exception => 'Format Exception (see console)'; - - @override - String get unknown_exception => 'Unknown Exception (see console)'; - - @override - String get data_successfully_exported => 'Data successfully exported'; - - @override - String get export_canceled => 'Export canceled'; - - @override - String get undo => 'Undo'; - - @override - String get wins => 'Wins'; - - @override - String get winrate => 'Winrate'; - - @override - String get amount_of_matches => 'Amount of Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'No groups created yet'; - - @override - String get no_players_created_yet => 'No players created yet'; - - @override - String get create_group => 'Create Group'; - - @override - String get group_name => 'Group name'; - - @override - String get player_name => 'Player name'; - - @override - String get no_matches_created_yet => 'No matches created yet'; - - @override - String get match_name => 'Match name'; - - @override - String get game => 'Game'; - - @override - String get ruleset => 'Ruleset'; - - @override - String get group => 'Group'; - - @override - String get none => 'None'; - - @override - String get none_group => 'None'; - - @override - String get create_match => 'Create match'; - - @override - String get no_players_found_with_that_name => - 'No players found with that name'; - - @override - String get all_players_selected => 'All players selected'; - - @override - String today_at(String time) { - return 'Today at $time'; - } - - @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } - - @override - String days_ago(int count) { - return '$count days ago'; - } - - @override - String get home => 'Home'; + String get single_winner => 'Single Winner'; @override String get statistics => 'Statistics'; @@ -231,8 +241,8 @@ class AppLocalizationsEn extends AppLocalizations { String get stats => 'Stats'; @override - String players_count(int count) { - return '$count Players'; + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName'; } @override @@ -240,42 +250,30 @@ class AppLocalizationsEn extends AppLocalizations { 'There is no group matching your search'; @override - String get game_name => 'Game Name'; + String get this_cannot_be_undone => 'This can\'t be undone'; @override - String get ruleset_single_winner => - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + String today_at(String time) { + return 'Today at $time'; + } @override - String get ruleset_single_loser => - 'Exactly one loser is determined; last place receives the penalty or consequence.'; + String get undo => 'Undo'; @override - String get ruleset_most_points => - 'Traditional ruleset: the player with the most points wins.'; + String get unknown_exception => 'Unknown Exception (see console)'; @override - String get ruleset_least_points => - 'Inverse scoring: the player with the fewest points wins.'; + String get winner => 'Winner'; @override - String get single_winner => 'Single Winner'; + String get winrate => 'Winrate'; @override - String get single_loser => 'Single Loser'; + String get wins => 'Wins'; @override - String get most_points => 'Most Points'; - - @override - String get least_points => 'Least Points'; - - @override - String get search_for_players => 'Search for players'; - - @override - String get search_for_groups => 'Search for groups'; - - @override - String get not_available => 'Not available'; + String yesterday_at(String time) { + return 'Yesterday at $time'; + } } -- 2.49.1 From cd2770be263ce47fafca0583f6276eaa4b3d67de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:33:52 +0100 Subject: [PATCH 552/563] Removed unnessecary setStates --- .../views/main_menu/group_view/create_group_view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 022a4b5..8037de4 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -64,9 +64,6 @@ class _CreateGroupViewState extends State { child: TextInputField( controller: _groupNameController, hintText: loc.group_name, - onChanged: (value) { - setState(() {}); - }, ), ), Expanded( @@ -111,7 +108,6 @@ class _CreateGroupViewState extends State { ), ); } - setState(() {}); }, ), const SizedBox(height: 20), -- 2.49.1 From fdd0e7579ac6e486ad57cfb4fb74d108e081f7b7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:34:37 +0100 Subject: [PATCH 553/563] Updated game title process --- .../create_match/create_match_view.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index fe1e8f5..7b7deb0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -32,7 +32,7 @@ class _CreateMatchViewState extends State { final TextEditingController _matchNameController = TextEditingController(); /// Hint text for the match name input field - String hintText = 'Match Name'; + String? hintText; /// List of all groups from the database List groupsList = []; @@ -101,6 +101,7 @@ class _CreateMatchViewState extends State { void didChangeDependencies() { super.didChangeDependencies(); final loc = AppLocalizations.of(context); + hintText ??= loc.match_name; _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), @@ -137,7 +138,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: hintText, + hintText: hintText ?? '', ), ), ChooseTile( @@ -162,7 +163,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = 'Match Name'; + hintText = AppLocalizations.of(context).match_name; selectedRuleset = null; } }); @@ -237,7 +238,7 @@ class _CreateMatchViewState extends State { ? () async { Match match = Match( name: _matchNameController.text.isEmpty - ? hintText + ? (hintText ?? '') : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, @@ -265,8 +266,11 @@ class _CreateMatchViewState extends State { ); } - /// Determines whether the "Create Game" button should be enabled based on - /// the current state of the input fields. + /// Determines whether the "Create Match" button should be enabled. + /// + /// Returns `true` if: + /// - A ruleset is selected AND + /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && -- 2.49.1 From 02d79574dd024194221b0df8f8a5c7a24b18a406 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:43:13 +0100 Subject: [PATCH 554/563] Simplified app bar logic in views --- lib/core/custom_theme.dart | 3 +++ .../main_menu/group_view/create_group_view.dart | 10 +--------- .../match_view/create_match/choose_game_view.dart | 8 +------- .../match_view/create_match/choose_group_view.dart | 8 +------- .../match_view/create_match/choose_ruleset_view.dart | 8 +------- .../match_view/create_match/create_match_view.dart | 10 +--------- .../main_menu/match_view/match_result_view.dart | 12 +----------- 7 files changed, 9 insertions(+), 50 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 5930901..a9a31b2 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -25,10 +25,13 @@ class CustomTheme { backgroundColor: backgroundColor, foregroundColor: Colors.white, elevation: 0, + scrolledUnderElevation: 0, + centerTitle: true, titleTextStyle: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, ), iconTheme: const IconThemeData(color: Colors.white), ); diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 8037de4..3d09561 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,15 +46,7 @@ class _CreateGroupViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_group)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 01981e2..5976f72 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -38,19 +38,13 @@ class _ChooseGameViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop(selectedGameIndex); }, ), - title: Text( - loc.choose_game, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_game), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 011b819..97fbcef 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -38,8 +38,6 @@ class _ChooseGroupViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -52,11 +50,7 @@ class _ChooseGroupViewState extends State { ); }, ), - title: Text( - loc.choose_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_group), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 73be471..ca021af 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -37,8 +37,6 @@ class _ChooseRulesetViewState extends State { child: Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -49,11 +47,7 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: Text( - loc.choose_ruleset, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_ruleset), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 7b7deb0..e99dfc1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -121,15 +121,7 @@ class _CreateMatchViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_match, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_match)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 93ebbc6..0d624f0 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -51,17 +51,7 @@ class _MatchResultViewState extends State { Navigator.of(context).pop(); }, ), - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - widget.match.name, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), - ), - centerTitle: true, + title: Text(widget.match.name), ), body: SafeArea( child: Column( -- 2.49.1 From aef12bd65ab3b68140b6174e636a6a3e347a93ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:54:53 +0100 Subject: [PATCH 555/563] Refactored several theme settings into custom theme --- lib/core/custom_theme.dart | 31 ++++++++++++++++--- .../group_view/create_group_view.dart | 2 +- .../create_match/create_match_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 4 +-- .../widgets/buttons/custom_width_button.dart | 4 +-- .../widgets/buttons/quick_create_button.dart | 4 ++- .../widgets/player_selection.dart | 2 +- .../widgets/tiles/choose_tile.dart | 4 +-- .../widgets/tiles/custom_radio_list_tile.dart | 2 +- .../widgets/tiles/group_tile.dart | 2 +- .../widgets/tiles/match_tile.dart | 6 ++-- 11 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index a9a31b2..a6c6376 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,38 +1,59 @@ import 'package:flutter/material.dart'; class CustomTheme { + CustomTheme._(); // Private constructor to prevent instantiation + + // ==================== Colors ==================== static Color primaryColor = const Color(0xFF7505E4); static Color secondaryColor = const Color(0xFFAFA2FF); static Color backgroundColor = const Color(0xFF0B0B0B); static Color boxColor = const Color(0xFF101010); static Color onBoxColor = const Color(0xFF181818); static Color boxBorder = const Color(0xFF272727); + static const Color textColor = Colors.white; + // ==================== Border Radius ==================== + static const double standardBorderRadius = 12.0; + static BorderRadius get standardBorderRadiusAll => + BorderRadius.circular(standardBorderRadius); + + // ==================== Padding & Margins ==================== + static const EdgeInsets standardMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ); + static const EdgeInsets tileMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ); + + // ==================== Decorations ==================== static BoxDecoration standardBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, ); static BoxDecoration highlightedBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: primaryColor), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)], ); + // ==================== App Bar Theme ==================== static AppBarTheme appBarTheme = AppBarTheme( backgroundColor: backgroundColor, - foregroundColor: Colors.white, + foregroundColor: textColor, elevation: 0, scrolledUnderElevation: 0, centerTitle: true, titleTextStyle: const TextStyle( - color: Colors.white, + color: textColor, fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, ), - iconTheme: const IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: textColor), ); } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 3d09561..f92df0f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -52,7 +52,7 @@ class _CreateGroupViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, child: TextInputField( controller: _groupNameController, hintText: loc.group_name, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index e99dfc1..0cc25d0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -127,7 +127,7 @@ class _CreateMatchViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + margin: CustomTheme.tileMargin, child: TextInputField( controller: _matchNameController, hintText: hintText ?? '', diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index a60b854..53569ad 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -57,7 +57,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, - barColor: Colors.blue, + barColor: Colors.green, ), SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( @@ -75,7 +75,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, - barColor: Colors.green, + barColor: Colors.blue, ), ], ), diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index e27f009..7e52648 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -60,7 +60,7 @@ class CustomWidthButton extends StatelessWidget { 60, ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( @@ -91,7 +91,7 @@ class CustomWidthButton extends StatelessWidget { ), side: BorderSide(color: borderSideColor, width: 2), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 2f61805..40ebeab 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -29,7 +29,9 @@ class _QuickCreateButtonState extends State { style: ElevatedButton.styleFrom( minimumSize: const Size(140, 45), backgroundColor: CustomTheme.primaryColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: CustomTheme.standardBorderRadiusAll, + ), ), child: Text( widget.text, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 99b1e1c..a582427 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -77,7 +77,7 @@ class _PlayerSelectionState extends State { Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), decoration: CustomTheme.standardBoxDecoration, child: Column( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index ba12c3d..f6ec940 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -32,8 +32,8 @@ class _ChooseTileState extends State { return GestureDetector( onTap: widget.onPressed, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: CustomTheme.standardBoxDecoration, child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index d76cf3f..706aabb 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -32,7 +32,7 @@ class CustomRadioListTile extends StatelessWidget { decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 8dee1cd..64d9caa 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -18,7 +18,7 @@ class GroupTile extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedContainer( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: isHighlighted ? CustomTheme.highlightedBoxDecoration diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index d764dd9..bc349d3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -41,8 +41,8 @@ class _MatchTileState extends State { return GestureDetector( onTap: widget.onTap, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), @@ -118,7 +118,7 @@ class _MatchTileState extends State { style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white, + color: CustomTheme.textColor, ), overflow: TextOverflow.ellipsis, ), -- 2.49.1 From 13be75a65b2032c6eff4ef4127d4ff7e98bc6636 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 15:12:12 +0100 Subject: [PATCH 556/563] change settings icons to filled and rounded --- lib/presentation/views/main_menu/settings_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 8f1e68a..374c463 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -58,7 +58,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.export_data, - icon: Icons.upload_outlined, + icon: Icons.upload_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final String json = @@ -73,7 +73,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.import_data, - icon: Icons.download_outlined, + icon: Icons.download_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final result = await DataTransferService.importData( @@ -85,7 +85,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.delete_all_data, - icon: Icons.download_outlined, + icon: Icons.delete_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( -- 2.49.1 From 803cf8a97269c1096993f2a57f1b4c411d075eec Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 15:17:13 +0100 Subject: [PATCH 557/563] update app_localizations --- lib/l10n/generated/app_localizations.dart | 732 +++++++++---------- lib/l10n/generated/app_localizations_de.dart | 392 +++++----- lib/l10n/generated/app_localizations_en.dart | 390 +++++----- 3 files changed, 757 insertions(+), 757 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 951ff22..aea4457 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -98,23 +98,29 @@ abstract class AppLocalizations { Locale('en'), ]; - /// Label for choosing a group + /// Label for all players list /// /// In en, this message translates to: - /// **'Choose Group'** - String get choose_group; + /// **'All players:'** + String get all_players; - /// Button text to create a new match + /// Message when all players are added to selection /// /// In en, this message translates to: - /// **'Create new match'** - String get create_new_match; + /// **'All players selected'** + String get all_players_selected; - /// Label for choosing a ruleset + /// Label for amount of matches statistic /// /// In en, this message translates to: - /// **'Choose Ruleset'** - String get choose_ruleset; + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; /// Label for choosing a game /// @@ -122,11 +128,125 @@ abstract class AppLocalizations { /// **'Choose Game'** String get choose_game; - /// Label to select the winner + /// Label for choosing a group /// /// In en, this message translates to: - /// **'Select Winner:'** - String get select_winner; + /// **'Choose Group'** + String get choose_group; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_creating_group; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; /// App Name /// @@ -134,6 +254,126 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get game_tracker; + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data_available; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -146,294 +386,12 @@ abstract class AppLocalizations { /// **'No second match available'** String get no_second_match_available; - /// Confirmation dialog for deleting all data - /// - /// In en, this message translates to: - /// **'Delete all data?'** - String get delete_all_data; - - /// Cancel button text - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Delete button text - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// Button text to create a new group - /// - /// In en, this message translates to: - /// **'Create new group'** - String get create_new_group; - - /// Error message when group creation fails - /// - /// In en, this message translates to: - /// **'Error while creating group, please try again'** - String get error_creating_group; - - /// Shows the number of selected players - /// - /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); - - /// Message when no players are selected - /// - /// In en, this message translates to: - /// **'No players selected'** - String get no_players_selected; - - /// Label for all players list - /// - /// In en, this message translates to: - /// **'All players:'** - String get all_players; - - /// Success message when adding a player - /// - /// In en, this message translates to: - /// **'Successfully added player {playerName}'** - String successfully_added_player(String playerName); - - /// Error message when adding a player fails - /// - /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); - - /// Shows the winner's name - /// - /// In en, this message translates to: - /// **'Winner: {winnerName}'** - String winner(String winnerName); - - /// Players label - /// - /// In en, this message translates to: - /// **'Players'** - String get players; - /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: /// **'No statistics available'** String get no_statistics_available; - /// Message when no data in the statistic tiles is given - /// - /// In en, this message translates to: - /// **'No data available'** - String get no_data_available; - - /// Label for matches - /// - /// In en, this message translates to: - /// **'Matches'** - String get matches; - - /// Label for groups - /// - /// In en, this message translates to: - /// **'Groups'** - String get groups; - - /// Title for recent matches section - /// - /// In en, this message translates to: - /// **'Recent Matches'** - String get recent_matches; - - /// Title for quick create section - /// - /// In en, this message translates to: - /// **'Quick Create'** - String get quick_create; - - /// Message when match is in progress - /// - /// In en, this message translates to: - /// **'Match in progress...'** - String get match_in_progress; - - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Export data menu item - /// - /// In en, this message translates to: - /// **'Export data'** - String get export_data; - - /// Import data menu item - /// - /// In en, this message translates to: - /// **'Import data'** - String get import_data; - - /// Warning message for irreversible actions - /// - /// In en, this message translates to: - /// **'This can\'t be undone'** - String get this_cannot_be_undone; - - /// Success message after deleting data - /// - /// In en, this message translates to: - /// **'Data successfully deleted'** - String get data_successfully_deleted; - - /// Success message after importing data - /// - /// In en, this message translates to: - /// **'Data successfully imported'** - String get data_successfully_imported; - - /// Error message for invalid schema - /// - /// In en, this message translates to: - /// **'Invalid Schema'** - String get invalid_schema; - - /// Error message when file cannot be read - /// - /// In en, this message translates to: - /// **'Error reading file'** - String get error_reading_file; - - /// Message when import is canceled - /// - /// In en, this message translates to: - /// **'Import canceled'** - String get import_canceled; - - /// Error message for format exceptions - /// - /// In en, this message translates to: - /// **'Format Exception (see console)'** - String get format_exception; - - /// Error message for unknown exceptions - /// - /// In en, this message translates to: - /// **'Unknown Exception (see console)'** - String get unknown_exception; - - /// Success message after exporting data - /// - /// In en, this message translates to: - /// **'Data successfully exported'** - String get data_successfully_exported; - - /// Message when export is canceled - /// - /// In en, this message translates to: - /// **'Export canceled'** - String get export_canceled; - - /// Undo button text - /// - /// In en, this message translates to: - /// **'Undo'** - String get undo; - - /// Label for wins statistic - /// - /// In en, this message translates to: - /// **'Wins'** - String get wins; - - /// Label for winrate statistic - /// - /// In en, this message translates to: - /// **'Winrate'** - String get winrate; - - /// Label for amount of matches statistic - /// - /// In en, this message translates to: - /// **'Amount of Matches'** - String get amount_of_matches; - - /// Info label - /// - /// In en, this message translates to: - /// **'Info'** - String get info; - - /// Message when no groups exist - /// - /// In en, this message translates to: - /// **'No groups created yet'** - String get no_groups_created_yet; - - /// Message when no players exist - /// - /// In en, this message translates to: - /// **'No players created yet'** - String get no_players_created_yet; - - /// Button text to create a group - /// - /// In en, this message translates to: - /// **'Create Group'** - String get create_group; - - /// Placeholder for group name input - /// - /// In en, this message translates to: - /// **'Group name'** - String get group_name; - - /// Placeholder for player name input - /// - /// In en, this message translates to: - /// **'Player name'** - String get player_name; - - /// Message when no matches exist - /// - /// In en, this message translates to: - /// **'No matches created yet'** - String get no_matches_created_yet; - - /// Placeholder for match name input - /// - /// In en, this message translates to: - /// **'Match name'** - String get match_name; - - /// Game label - /// - /// In en, this message translates to: - /// **'Game'** - String get game; - - /// Ruleset label - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset; - - /// Group label - /// - /// In en, this message translates to: - /// **'Group'** - String get group; - /// None option label /// /// In en, this message translates to: @@ -446,47 +404,113 @@ abstract class AppLocalizations { /// **'None'** String get none_group; - /// Button text to create a match + /// Abbreviation for not available /// /// In en, this message translates to: - /// **'Create match'** - String get create_match; + /// **'Not available'** + String get not_available; - /// Message when search returns no results + /// Placeholder for player name input /// /// In en, this message translates to: - /// **'No players found with that name'** - String get no_players_found_with_that_name; + /// **'Player name'** + String get player_name; - /// Message when all players are added to selection + /// Players label /// /// In en, this message translates to: - /// **'All players selected'** - String get all_players_selected; + /// **'Players'** + String get players; - /// Date format for today + /// Shows the number of players /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'{count} Players'** + String players_count(int count); - /// Date format for yesterday + /// Title for quick create section /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Quick Create'** + String get quick_create; - /// Date format for days ago + /// Title for recent matches section /// /// In en, this message translates to: - /// **'{count} days ago'** - String days_ago(int count); + /// **'Recent Matches'** + String get recent_matches; - /// Home tab label + /// Ruleset label /// /// In en, this message translates to: - /// **'Home'** - String get home; + /// **'Ruleset'** + String get ruleset; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; /// Statistics tab label /// @@ -500,11 +524,11 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; - /// Shows the number of players + /// Success message when adding a player /// /// In en, this message translates to: - /// **'{count} Players'** - String players_count(int count); + /// **'Successfully added player {playerName}'** + String successfully_added_player(String playerName); /// Message when search returns no groups /// @@ -512,77 +536,53 @@ abstract class AppLocalizations { /// **'There is no group matching your search'** String get there_is_no_group_matching_your_search; - /// Placeholder for game name search + /// Warning message for irreversible actions /// /// In en, this message translates to: - /// **'Game Name'** - String get game_name; + /// **'This can\'t be undone'** + String get this_cannot_be_undone; - /// Description for single winner ruleset + /// Date format for today /// /// In en, this message translates to: - /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner; + /// **'Today at {time}'** + String today_at(String time); - /// Description for single loser ruleset + /// Undo button text /// /// In en, this message translates to: - /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser; + /// **'Undo'** + String get undo; - /// Description for most points ruleset + /// Error message for unknown exceptions /// /// In en, this message translates to: - /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points; + /// **'Unknown Exception (see console)'** + String get unknown_exception; - /// Description for least points ruleset + /// Winner label /// /// In en, this message translates to: - /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points; + /// **'Winner'** + String winner(Object winnerName); - /// Title for single winner ruleset + /// Label for winrate statistic /// /// In en, this message translates to: - /// **'Single Winner'** - String get single_winner; + /// **'Winrate'** + String get winrate; - /// Title for single loser ruleset + /// Label for wins statistic /// /// In en, this message translates to: - /// **'Single Loser'** - String get single_loser; + /// **'Wins'** + String get wins; - /// Title for most points ruleset + /// Date format for yesterday /// /// In en, this message translates to: - /// **'Most Points'** - String get most_points; - - /// Title for least points ruleset - /// - /// In en, this message translates to: - /// **'Least Points'** - String get least_points; - - /// Hint text for player search input field - /// - /// In en, this message translates to: - /// **'Search for players'** - String get search_for_players; - - /// Hint text for group search input field - /// - /// In en, this message translates to: - /// **'Search for groups'** - String get search_for_groups; - - /// Abbreviation for not available - /// - /// In en, this message translates to: - /// **'Not available'** - String get not_available; + /// **'Yesterday at {time}'** + String yesterday_at(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3f3e36e..bfb9870 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,23 +9,149 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get choose_group => 'Gruppe wählen'; + String get all_players => 'Alle Spieler:innen:'; @override - String get create_new_match => 'Neues Match erstellen'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get choose_ruleset => 'Regelwerk wählen'; + String get amount_of_matches => 'Anzahl der Matches'; + + @override + String get cancel => 'Abbrechen'; @override String get choose_game => 'Spielvorlage wählen'; @override - String get select_winner => 'Gewinner:in wählen:'; + String get choose_group => 'Gruppe wählen'; + + @override + String get choose_ruleset => 'Regelwerk wählen'; + + @override + String could_not_add_player(String playerName) { + return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; + } + + @override + String get create_group => 'Gruppe erstellen'; + + @override + String get create_match => 'Match erstellen'; + + @override + String get create_new_group => 'Neue Gruppe erstellen'; + + @override + String get create_new_match => 'Neues Match erstellen'; + + @override + String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + + @override + String get data_successfully_imported => 'Daten erfolgreich importiert'; + + @override + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get delete => 'Löschen'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get error_creating_group => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + + @override + String get error_reading_file => 'Fehler beim Lesen der Datei'; + + @override + String get export_canceled => 'Export abgebrochen'; + + @override + String get export_data => 'Daten exportieren'; + + @override + String get format_exception => 'Formatfehler (siehe Konsole)'; + + @override + String get game => 'Spielvorlage'; + + @override + String get game_name => 'Spielvorlagenname'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Gruppe'; + + @override + String get group_name => 'Gruppenname'; + + @override + String get groups => 'Gruppen'; + + @override + String get home => 'Startseite'; + + @override + String get import_canceled => 'Import abgebrochen'; + + @override + String get import_data => 'Daten importieren'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get least_points => 'Niedrigste Punkte'; + + @override + String get match_in_progress => 'Match läuft...'; + + @override + String get match_name => 'Matchname'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menü'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_matches_created_yet => 'Noch keine Matches erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler:in mit diesem Namen gefunden'; + + @override + String get no_players_selected => 'Keine Spieler:in ausgewählt'; + @override String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; @@ -33,20 +159,61 @@ class AppLocalizationsDe extends AppLocalizations { String get no_second_match_available => 'Kein zweites Match verfügbar'; @override - String get delete_all_data => 'Alle Daten löschen?'; + String get no_statistics_available => 'Keine Statistiken verfügbar'; @override - String get cancel => 'Abbrechen'; + String get none => 'Kein'; @override - String get delete => 'Löschen'; + String get none_group => 'Keine'; @override - String get create_new_group => 'Neue Gruppe erstellen'; + String get not_available => 'Nicht verfügbar'; @override - String get error_creating_group => - 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + String get player_name => 'Spieler:innenname'; + + @override + String get players => 'Spieler:in'; + + @override + String players_count(int count) { + return '$count Spieler'; + } + + @override + String get quick_create => 'Schnellzugriff'; + + @override + String get recent_matches => 'Letzte Matches'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + + @override + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get search_for_players => 'Nach Spieler:innen suchen'; + + @override + String get select_winner => 'Gewinner:in wählen:'; @override String selected_players(int count) { @@ -58,172 +225,14 @@ class AppLocalizationsDe extends AppLocalizations { return 'Ausgewählte Spieler:in: $countString'; } - @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; - - @override - String get all_players => 'Alle Spieler:innen:'; - - @override - String successfully_added_player(String playerName) { - return 'Spieler:in $playerName erfolgreich hinzugefügt'; - } - - @override - String could_not_add_player(String playerName) { - return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; - } - - @override - String winner(String winnerName) { - return 'Gewinner:in: $winnerName'; - } - - @override - String get players => 'Spieler:in'; - - @override - String get no_statistics_available => 'Keine Statistiken verfügbar'; - - @override - String get no_data_available => 'Keine Daten verfügbar'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Gruppen'; - - @override - String get recent_matches => 'Letzte Matches'; - - @override - String get quick_create => 'Schnellzugriff'; - - @override - String get match_in_progress => 'Match läuft...'; - - @override - String get menu => 'Menü'; - @override String get settings => 'Einstellungen'; @override - String get export_data => 'Daten exportieren'; + String get single_loser => 'Ein:e Verlierer:in'; @override - String get import_data => 'Daten importieren'; - - @override - String get this_cannot_be_undone => - 'Dies kann nicht rückgängig gemacht werden'; - - @override - String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; - - @override - String get data_successfully_imported => 'Daten erfolgreich importiert'; - - @override - String get invalid_schema => 'Ungültiges Schema'; - - @override - String get error_reading_file => 'Fehler beim Lesen der Datei'; - - @override - String get import_canceled => 'Import abgebrochen'; - - @override - String get format_exception => 'Formatfehler (siehe Konsole)'; - - @override - String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; - - @override - String get data_successfully_exported => 'Daten erfolgreich exportiert'; - - @override - String get export_canceled => 'Export abgebrochen'; - - @override - String get undo => 'Rückgängig'; - - @override - String get wins => 'Siege'; - - @override - String get winrate => 'Siegquote'; - - @override - String get amount_of_matches => 'Anzahl der Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; - - @override - String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; - - @override - String get create_group => 'Gruppe erstellen'; - - @override - String get group_name => 'Gruppenname'; - - @override - String get player_name => 'Spieler:innenname'; - - @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; - - @override - String get match_name => 'Matchname'; - - @override - String get game => 'Spielvorlage'; - - @override - String get ruleset => 'Regelwerk'; - - @override - String get group => 'Gruppe'; - - @override - String get none => 'Kein'; - - @override - String get none_group => 'Keine'; - - @override - String get create_match => 'Match erstellen'; - - @override - String get no_players_found_with_that_name => - 'Keine Spieler:in mit diesem Namen gefunden'; - - @override - String get all_players_selected => 'Alle Spieler:innen ausgewählt'; - - @override - String today_at(String time) { - return 'Heute um $time'; - } - - @override - String yesterday_at(String time) { - return 'Gestern um $time'; - } - - @override - String days_ago(int count) { - return 'vor $count Tagen'; - } - - @override - String get home => 'Startseite'; + String get single_winner => 'Ein:e Gewinner:in'; @override String get statistics => 'Statistiken'; @@ -232,8 +241,8 @@ class AppLocalizationsDe extends AppLocalizations { String get stats => 'Statistiken'; @override - String players_count(int count) { - return '$count Spieler'; + String successfully_added_player(String playerName) { + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override @@ -241,42 +250,33 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielvorlagenname'; + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; @override - String get ruleset_single_winner => - 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String today_at(String time) { + return 'Heute um $time'; + } @override - String get ruleset_single_loser => - 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get undo => 'Rückgängig'; @override - String get ruleset_most_points => - 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get ruleset_least_points => - 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + String winner(Object winnerName) { + return 'Gewinner:in: $winnerName'; + } @override - String get single_winner => 'Ein:e Gewinner:in'; + String get winrate => 'Siegquote'; @override - String get single_loser => 'Ein:e Verlierer:in'; + String get wins => 'Siege'; @override - String get most_points => 'Höchste Punkte'; - - @override - String get least_points => 'Niedrigste Punkte'; - - @override - String get search_for_players => 'Nach Spieler:innen suchen'; - - @override - String get search_for_groups => 'Nach Gruppen suchen'; - - @override - String get not_available => 'Nicht verfügbar'; + String yesterday_at(String time) { + return 'Gestern um $time'; + } } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 263714c..38ea20f 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,23 +9,149 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get choose_group => 'Choose Group'; + String get all_players => 'All players:'; @override - String get create_new_match => 'Create new match'; + String get all_players_selected => 'All players selected'; @override - String get choose_ruleset => 'Choose Ruleset'; + String get amount_of_matches => 'Amount of Matches'; + + @override + String get cancel => 'Cancel'; @override String get choose_game => 'Choose Game'; @override - String get select_winner => 'Select Winner:'; + String get choose_group => 'Choose Group'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; + } + + @override + String get create_group => 'Create Group'; + + @override + String get create_match => 'Create match'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get delete => 'Delete'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get error_creating_group => + 'Error while creating group, please try again'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get export_data => 'Export data'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get game => 'Game'; + + @override + String get game_name => 'Game Name'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Group'; + + @override + String get group_name => 'Group name'; + + @override + String get groups => 'Groups'; + + @override + String get home => 'Home'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get import_data => 'Import data'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get least_points => 'Least Points'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get match_name => 'Match name'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menu'; + + @override + String get most_points => 'Most Points'; + + @override + String get no_data_available => 'No data available'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get no_players_selected => 'No players selected'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -33,20 +159,61 @@ class AppLocalizationsEn extends AppLocalizations { String get no_second_match_available => 'No second match available'; @override - String get delete_all_data => 'Delete all data?'; + String get no_statistics_available => 'No statistics available'; @override - String get cancel => 'Cancel'; + String get none => 'None'; @override - String get delete => 'Delete'; + String get none_group => 'None'; @override - String get create_new_group => 'Create new group'; + String get not_available => 'Not available'; @override - String get error_creating_group => - 'Error while creating group, please try again'; + String get player_name => 'Player name'; + + @override + String get players => 'Players'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get quick_create => 'Quick Create'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get ruleset_least_points => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get ruleset_most_points => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_single_loser => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_single_winner => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get search_for_groups => 'Search for groups'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get select_winner => 'Select Winner:'; @override String selected_players(int count) { @@ -58,171 +225,14 @@ class AppLocalizationsEn extends AppLocalizations { return 'Selected players: $countString'; } - @override - String get no_players_selected => 'No players selected'; - - @override - String get all_players => 'All players:'; - - @override - String successfully_added_player(String playerName) { - return 'Successfully added player $playerName'; - } - - @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; - } - - @override - String winner(String winnerName) { - return 'Winner: $winnerName'; - } - - @override - String get players => 'Players'; - - @override - String get no_statistics_available => 'No statistics available'; - - @override - String get no_data_available => 'No data available'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Groups'; - - @override - String get recent_matches => 'Recent Matches'; - - @override - String get quick_create => 'Quick Create'; - - @override - String get match_in_progress => 'Match in progress...'; - - @override - String get menu => 'Menu'; - @override String get settings => 'Settings'; @override - String get export_data => 'Export data'; + String get single_loser => 'Single Loser'; @override - String get import_data => 'Import data'; - - @override - String get this_cannot_be_undone => 'This can\'t be undone'; - - @override - String get data_successfully_deleted => 'Data successfully deleted'; - - @override - String get data_successfully_imported => 'Data successfully imported'; - - @override - String get invalid_schema => 'Invalid Schema'; - - @override - String get error_reading_file => 'Error reading file'; - - @override - String get import_canceled => 'Import canceled'; - - @override - String get format_exception => 'Format Exception (see console)'; - - @override - String get unknown_exception => 'Unknown Exception (see console)'; - - @override - String get data_successfully_exported => 'Data successfully exported'; - - @override - String get export_canceled => 'Export canceled'; - - @override - String get undo => 'Undo'; - - @override - String get wins => 'Wins'; - - @override - String get winrate => 'Winrate'; - - @override - String get amount_of_matches => 'Amount of Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'No groups created yet'; - - @override - String get no_players_created_yet => 'No players created yet'; - - @override - String get create_group => 'Create Group'; - - @override - String get group_name => 'Group name'; - - @override - String get player_name => 'Player name'; - - @override - String get no_matches_created_yet => 'No matches created yet'; - - @override - String get match_name => 'Match name'; - - @override - String get game => 'Game'; - - @override - String get ruleset => 'Ruleset'; - - @override - String get group => 'Group'; - - @override - String get none => 'None'; - - @override - String get none_group => 'None'; - - @override - String get create_match => 'Create match'; - - @override - String get no_players_found_with_that_name => - 'No players found with that name'; - - @override - String get all_players_selected => 'All players selected'; - - @override - String today_at(String time) { - return 'Today at $time'; - } - - @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } - - @override - String days_ago(int count) { - return '$count days ago'; - } - - @override - String get home => 'Home'; + String get single_winner => 'Single Winner'; @override String get statistics => 'Statistics'; @@ -231,8 +241,8 @@ class AppLocalizationsEn extends AppLocalizations { String get stats => 'Stats'; @override - String players_count(int count) { - return '$count Players'; + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName'; } @override @@ -240,42 +250,32 @@ class AppLocalizationsEn extends AppLocalizations { 'There is no group matching your search'; @override - String get game_name => 'Game Name'; + String get this_cannot_be_undone => 'This can\'t be undone'; @override - String get ruleset_single_winner => - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + String today_at(String time) { + return 'Today at $time'; + } @override - String get ruleset_single_loser => - 'Exactly one loser is determined; last place receives the penalty or consequence.'; + String get undo => 'Undo'; @override - String get ruleset_most_points => - 'Traditional ruleset: the player with the most points wins.'; + String get unknown_exception => 'Unknown Exception (see console)'; @override - String get ruleset_least_points => - 'Inverse scoring: the player with the fewest points wins.'; + String winner(Object winnerName) { + return 'Winner'; + } @override - String get single_winner => 'Single Winner'; + String get winrate => 'Winrate'; @override - String get single_loser => 'Single Loser'; + String get wins => 'Wins'; @override - String get most_points => 'Most Points'; - - @override - String get least_points => 'Least Points'; - - @override - String get search_for_players => 'Search for players'; - - @override - String get search_for_groups => 'Search for groups'; - - @override - String get not_available => 'Not available'; + String yesterday_at(String time) { + return 'Yesterday at $time'; + } } -- 2.49.1 From d5ee6449b05461b87992410d851fd2c1a2ac7065 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:02:06 +0100 Subject: [PATCH 558/563] Updated localizations --- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 28 +++++++++----------- lib/l10n/generated/app_localizations_en.dart | 4 +-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index aea4457..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -564,7 +564,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Winner'** - String winner(Object winnerName); + String get winner; /// Label for winrate statistic /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index bfb9870..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -15,7 +15,7 @@ class AppLocalizationsDe extends AppLocalizations { String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get amount_of_matches => 'Anzahl der Matches'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @@ -38,13 +38,13 @@ class AppLocalizationsDe extends AppLocalizations { String get create_group => 'Gruppe erstellen'; @override - String get create_match => 'Match erstellen'; + String get create_match => 'Spiel erstellen'; @override String get create_new_group => 'Neue Gruppe erstellen'; @override - String get create_new_match => 'Neues Match erstellen'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; @@ -119,13 +119,13 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get match_in_progress => 'Match läuft...'; + String get match_in_progress => 'Spiel läuft...'; @override - String get match_name => 'Matchname'; + String get match_name => 'Spieltitel'; @override - String get matches => 'Matches'; + String get matches => 'Spiele'; @override String get menu => 'Menü'; @@ -140,7 +140,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; @override String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @@ -153,10 +153,10 @@ class AppLocalizationsDe extends AppLocalizations { String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -174,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get player_name => 'Spieler:innenname'; @override - String get players => 'Spieler:in'; + String get players => 'Spieler:innen'; @override String players_count(int count) { @@ -185,7 +185,7 @@ class AppLocalizationsDe extends AppLocalizations { String get quick_create => 'Schnellzugriff'; @override - String get recent_matches => 'Letzte Matches'; + String get recent_matches => 'Letzte Spiele'; @override String get ruleset => 'Regelwerk'; @@ -222,7 +222,7 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler:in: $countString'; + return 'Ausgewählte Spieler:innen: $countString'; } @override @@ -265,9 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String winner(Object winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get winner => 'Gewinner*in'; @override String get winrate => 'Siegquote'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 38ea20f..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -264,9 +264,7 @@ class AppLocalizationsEn extends AppLocalizations { String get unknown_exception => 'Unknown Exception (see console)'; @override - String winner(Object winnerName) { - return 'Winner'; - } + String get winner => 'Winner'; @override String get winrate => 'Winrate'; -- 2.49.1 From 76121eb4fb5d02db5c9bf27fd43a38bf35d059ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:30:19 +0100 Subject: [PATCH 559/563] Updated localizations --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f55668e..4adbfa2 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner*in", + "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 4421fd1..88374a1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -265,7 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get winner => 'Gewinner*in'; + String get winner => 'Gewinner:in'; @override String get winrate => 'Siegquote'; -- 2.49.1 From d741990f2fad4e11c68c2b009fd8ffc172bf6fde Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:07:30 +0100 Subject: [PATCH 560/563] Fixed navbar issue --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c1ed977..8219fcb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,8 +35,6 @@ class GameTracker extends StatelessWidget { }, debugShowCheckedModeBanner: false, onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, - darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, -- 2.49.1 From cfb07bfe28b9af85b32b2b9c60ec15312b3009cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:13:24 +0100 Subject: [PATCH 561/563] Updated localizations --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/arb/app_en.arb | 712 +++++++++--------- lib/l10n/generated/app_localizations.dart | 24 +- lib/l10n/generated/app_localizations_de.dart | 16 +- lib/l10n/generated/app_localizations_en.dart | 18 +- lib/main.dart | 2 +- .../widgets/tiles/match_tile.dart | 8 +- 7 files changed, 377 insertions(+), 407 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4adbfa2..4ed0997 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -73,11 +73,11 @@ "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "today_at": "Heute um {time}", + "today_at": "Heute um", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", - "yesterday_at": "Gestern um {time}" + "yesterday_at": "Gestern um" } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d567f50..dd1e593 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,349 @@ { - "@@locale": "en", - "@all_players": { - "description": "Label for all players list" - }, - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "@cancel": { - "description": "Cancel button text" - }, - "@choose_game": { - "description": "Label for choosing a game" - }, - "@choose_group": { - "description": "Label for choosing a group" - }, - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@create_group": { - "description": "Button text to create a group" - }, - "@create_match": { - "description": "Button text to create a match" - }, - "@create_new_group": { - "description": "Button text to create a new group" - }, - "@create_new_match": { - "description": "Button text to create a new match" - }, - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@delete": { - "description": "Delete button text" - }, - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "@export_canceled": { - "description": "Message when export is canceled" - }, - "@export_data": { - "description": "Export data menu item" - }, - "@format_exception": { - "description": "Error message for format exceptions" - }, - "@game": { - "description": "Game label" - }, - "@game_name": { - "description": "Placeholder for game name search" - }, - "@game_tracker": { - "description": "App Name" - }, - "@group": { - "description": "Group label" - }, - "@group_name": { - "description": "Placeholder for group name input" - }, - "@groups": { - "description": "Label for groups" - }, - "@home": { - "description": "Home tab label" - }, - "@import_canceled": { - "description": "Message when import is canceled" - }, - "@import_data": { - "description": "Import data menu item" - }, - "@info": { - "description": "Info label" - }, - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "@least_points": { - "description": "Title for least points ruleset" - }, - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "@match_name": { - "description": "Placeholder for match name input" - }, - "@matches": { - "description": "Label for matches" - }, - "@menu": { - "description": "Menu label" - }, - "@most_points": { - "description": "Title for most points ruleset" - }, - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "@none": { - "description": "None option label" - }, - "@none_group": { - "description": "None group option label" - }, - "@not_available": { - "description": "Abbreviation for not available" - }, - "@player_name": { - "description": "Placeholder for player name input" - }, - "@players": { - "description": "Players label" - }, - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@quick_create": { - "description": "Title for quick create section" - }, - "@recent_matches": { - "description": "Title for recent matches section" - }, - "@ruleset": { - "description": "Ruleset label" - }, - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "@select_winner": { - "description": "Label to select the winner" - }, - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "@settings": { - "description": "Settings label" - }, - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "@statistics": { - "description": "Statistics tab label" - }, - "@stats": { - "description": "Stats tab label (short)" - }, - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "@undo": { - "description": "Undo button text" - }, - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "@winner": { - "description": "Winner label" - }, - "@winrate": { - "description": "Label for winrate statistic" - }, - "@wins": { - "description": "Label for wins statistic" - }, - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "all_players": "All players:", - "all_players_selected": "All players selected", - "amount_of_matches": "Amount of Matches", - "cancel": "Cancel", - "choose_game": "Choose Game", - "choose_group": "Choose Group", - "choose_ruleset": "Choose Ruleset", - "could_not_add_player": "Could not add player {playerName}", - "create_group": "Create Group", - "create_match": "Create match", - "create_new_group": "Create new group", - "create_new_match": "Create new match", - "data_successfully_deleted": "Data successfully deleted", - "data_successfully_exported": "Data successfully exported", - "data_successfully_imported": "Data successfully imported", - "days_ago": "{count} days ago", - "delete": "Delete", - "delete_all_data": "Delete all data?", - "error_creating_group": "Error while creating group, please try again", - "error_reading_file": "Error reading file", - "export_canceled": "Export canceled", - "export_data": "Export data", - "format_exception": "Format Exception (see console)", - "game": "Game", - "game_name": "Game Name", - "game_tracker": "Game Tracker", - "group": "Group", - "group_name": "Group name", - "groups": "Groups", - "home": "Home", - "import_canceled": "Import canceled", - "import_data": "Import data", - "info": "Info", - "invalid_schema": "Invalid Schema", - "least_points": "Least Points", - "match_in_progress": "Match in progress...", - "match_name": "Match name", - "matches": "Matches", - "menu": "Menu", - "most_points": "Most Points", - "no_data_available": "No data available", - "no_groups_created_yet": "No groups created yet", - "no_matches_created_yet": "No matches created yet", - "no_players_created_yet": "No players created yet", - "no_players_found_with_that_name": "No players found with that name", - "no_players_selected": "No players selected", - "no_recent_matches_available": "No recent matches available", - "no_second_match_available": "No second match available", - "no_statistics_available": "No statistics available", - "none": "None", - "none_group": "None", - "not_available": "Not available", - "player_name": "Player name", - "players": "Players", - "players_count": "{count} Players", - "quick_create": "Quick Create", - "recent_matches": "Recent Matches", - "ruleset": "Ruleset", - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "search_for_groups": "Search for groups", - "search_for_players": "Search for players", - "select_winner": "Select Winner:", - "selected_players": "Selected players: {count}", - "settings": "Settings", - "single_loser": "Single Loser", - "single_winner": "Single Winner", - "statistics": "Statistics", - "stats": "Stats", - "successfully_added_player": "Successfully added player {playerName}", - "there_is_no_group_matching_your_search": "There is no group matching your search", - "this_cannot_be_undone": "This can't be undone", - "today_at": "Today at {time}", - "undo": "Undo", - "unknown_exception": "Unknown Exception (see console)", - "winner": "Winner", - "winrate": "Winrate", - "wins": "Wins", - "yesterday_at": "Yesterday at {time}" + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@app_name": { + "description": "The name of the App" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails" + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today" + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday" + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "app_name": "Game Tracker", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "search_for_groups": "Search for groups", + "search_for_players": "Search for players", + "select_winner": "Select Winner:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 1743997..79ae804 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -116,6 +116,12 @@ abstract class AppLocalizations { /// **'Amount of Matches'** String get amount_of_matches; + /// The name of the App + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get app_name; + /// Cancel button text /// /// In en, this message translates to: @@ -143,8 +149,8 @@ abstract class AppLocalizations { /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); + /// **'Could not add player'** + String could_not_add_player(Object playerName); /// Button text to create a group /// @@ -248,12 +254,6 @@ abstract class AppLocalizations { /// **'Game Name'** String get game_name; - /// App Name - /// - /// In en, this message translates to: - /// **'Game Tracker'** - String get game_tracker; - /// Group label /// /// In en, this message translates to: @@ -545,8 +545,8 @@ abstract class AppLocalizations { /// Date format for today /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'Today at'** + String get today_at; /// Undo button text /// @@ -581,8 +581,8 @@ abstract class AppLocalizations { /// Date format for yesterday /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Yesterday at'** + String get yesterday_at; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 88374a1..17c459b 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -17,6 +17,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get amount_of_matches => 'Anzahl der Spiele'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Abbrechen'; @@ -30,7 +33,7 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String could_not_add_player(String playerName) { + String could_not_add_player(Object playerName) { return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @@ -88,9 +91,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get game_name => 'Spielvorlagenname'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Gruppe'; @@ -254,9 +254,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Dies kann nicht rückgängig gemacht werden'; @override - String today_at(String time) { - return 'Heute um $time'; - } + String get today_at => 'Heute um'; @override String get undo => 'Rückgängig'; @@ -274,7 +272,5 @@ class AppLocalizationsDe extends AppLocalizations { String get wins => 'Siege'; @override - String yesterday_at(String time) { - return 'Gestern um $time'; - } + String get yesterday_at => 'Gestern um'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 0cd8842..1bf24ca 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -17,6 +17,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get amount_of_matches => 'Amount of Matches'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Cancel'; @@ -30,8 +33,8 @@ class AppLocalizationsEn extends AppLocalizations { String get choose_ruleset => 'Choose Ruleset'; @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; + String could_not_add_player(Object playerName) { + return 'Could not add player'; } @override @@ -88,9 +91,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get game_name => 'Game Name'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Group'; @@ -253,9 +253,7 @@ class AppLocalizationsEn extends AppLocalizations { String get this_cannot_be_undone => 'This can\'t be undone'; @override - String today_at(String time) { - return 'Today at $time'; - } + String get today_at => 'Today at'; @override String get undo => 'Undo'; @@ -273,7 +271,5 @@ class AppLocalizationsEn extends AppLocalizations { String get wins => 'Wins'; @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } + String get yesterday_at => 'Yesterday at'; } diff --git a/lib/main.dart b/lib/main.dart index 8219fcb..656db90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, + onGenerateTitle: (context) => AppLocalizations.of(context).app_name, themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index bc349d3..55d81c3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -161,13 +161,9 @@ class _MatchTileState extends State { final loc = AppLocalizations.of(context); if (difference.inDays == 0) { - return AppLocalizations.of( - context, - ).today_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.today_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays == 1) { - return AppLocalizations.of( - context, - ).yesterday_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.yesterday_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays < 7) { return loc.days_ago(difference.inDays); } else { -- 2.49.1 From 2a1573ee2da3a3e5f4f0103462bcbd83bde77233 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:15:01 +0100 Subject: [PATCH 562/563] Remove Whitespace --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 656db90..1dee10b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,13 +40,11 @@ class GameTracker extends StatelessWidget { primaryColor: CustomTheme.primaryColor, scaffoldBackgroundColor: CustomTheme.backgroundColor, appBarTheme: CustomTheme.appBarTheme, - colorScheme: ColorScheme.fromSeed( seedColor: CustomTheme.primaryColor, brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), ), - home: const CustomNavigationBar(), ); } -- 2.49.1 From 88766652b964320cf89c9f8158e2b68425c204d5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:21:09 +0100 Subject: [PATCH 563/563] Small loc corrections --- lib/l10n/arb/app_de.arb | 6 +++--- lib/l10n/arb/app_en.arb | 12 +++--------- lib/l10n/generated/app_localizations.dart | 6 +++--- lib/l10n/generated/app_localizations_de.dart | 13 +++---------- lib/l10n/generated/app_localizations_en.dart | 11 ++--------- .../match_view/create_match/create_match_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 4 +--- 7 files changed, 16 insertions(+), 38 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4ed0997..4d86460 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "all_players": "Alle Spieler:innen:", + "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", @@ -44,7 +44,7 @@ "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "no_players_selected": "Keine Spieler:in ausgewählt", + "no_players_selected": "Keine Spieler:innen ausgewählt", "no_recent_matches_available": "Keine letzten Spiele verfügbar", "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:innen: {count}", + "selected_players": "Ausgewählte Spieler:innen", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index dd1e593..17c3b06 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -206,13 +206,7 @@ "description": "Label to select the winner" }, "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } + "description": "Shows the number of selected players" }, "@settings": { "description": "Settings label" @@ -265,7 +259,7 @@ "@yesterday_at": { "description": "Date format for yesterday" }, - "all_players": "All players:", + "all_players": "All players", "all_players_selected": "All players selected", "amount_of_matches": "Amount of Matches", "app_name": "Game Tracker", @@ -330,7 +324,7 @@ "search_for_groups": "Search for groups", "search_for_players": "Search for players", "select_winner": "Select Winner:", - "selected_players": "Selected players: {count}", + "selected_players": "Selected players", "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 79ae804..5080ff3 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -101,7 +101,7 @@ abstract class AppLocalizations { /// Label for all players list /// /// In en, this message translates to: - /// **'All players:'** + /// **'All players'** String get all_players; /// Message when all players are added to selection @@ -491,8 +491,8 @@ abstract class AppLocalizations { /// Shows the number of selected players /// /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); + /// **'Selected players'** + String get selected_players; /// Settings label /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 17c459b..c720941 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,7 +9,7 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get all_players => 'Alle Spieler:innen:'; + String get all_players => 'Alle Spieler:innen'; @override String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @@ -150,7 +150,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; + String get no_players_selected => 'Keine Spieler:innen ausgewählt'; @override String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @@ -216,14 +216,7 @@ class AppLocalizationsDe extends AppLocalizations { String get select_winner => 'Gewinner:in wählen:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:innen: $countString'; - } + String get selected_players => 'Ausgewählte Spieler:innen'; @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 1bf24ca..cd71035 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get all_players => 'All players:'; + String get all_players => 'All players'; @override String get all_players_selected => 'All players selected'; @@ -216,14 +216,7 @@ class AppLocalizationsEn extends AppLocalizations { String get select_winner => 'Select Winner:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Selected players: $countString'; - } + String get selected_players => 'Selected players'; @override String get settings => 'Settings'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 0cc25d0..dc6690b 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -155,7 +155,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = AppLocalizations.of(context).match_name; + hintText = loc.match_name; selectedRuleset = null; } }); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a582427..9280ae0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -119,9 +119,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of( - context, - ).selected_players(selectedPlayers.length), + loc.selected_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), -- 2.49.1