diff --git a/lib/core/common.dart b/lib/core/common.dart index 312e3fa..df88ea3 100644 --- a/lib/core/common.dart +++ b/lib/core/common.dart @@ -24,48 +24,48 @@ String translateRulesetToString(Ruleset ruleset, BuildContext context) { } } -/// Translates a [GameColor] enum value to its corresponding localized string. -String translateGameColorToString(GameColor color, BuildContext context) { +/// Translates a [AppColor] enum value to its corresponding localized string. +String translateAppColorToString(AppColor color, BuildContext context) { final loc = AppLocalizations.of(context); switch (color) { - case GameColor.red: + case AppColor.red: return loc.color_red; - case GameColor.blue: + case AppColor.blue: return loc.color_blue; - case GameColor.green: + case AppColor.green: return loc.color_green; - case GameColor.yellow: + case AppColor.yellow: return loc.color_yellow; - case GameColor.purple: + case AppColor.purple: return loc.color_purple; - case GameColor.orange: + case AppColor.orange: return loc.color_orange; - case GameColor.pink: + case AppColor.pink: return loc.color_pink; - case GameColor.teal: + case AppColor.teal: return loc.color_teal; } } -/// Returns the [Color] object corresponding to a [GameColor] enum value. -Color getColorFromGameColor(GameColor color) { +/// Returns the [Color] object corresponding to a [AppColor] enum value. +Color getColorFromAppColor(AppColor color) { switch (color) { - case GameColor.red: + case AppColor.red: return Colors.red; - case GameColor.blue: + case AppColor.blue: return Colors.blue; - case GameColor.green: + case AppColor.green: return Colors.green; - case GameColor.yellow: + case AppColor.yellow: return const Color(0xFFF7CA28); - case GameColor.purple: + case AppColor.purple: return Colors.purple; - case GameColor.orange: + case AppColor.orange: return const Color(0xFFef681f); - case GameColor.pink: - return Colors.pink; - case GameColor.teal: - return Colors.teal; + case AppColor.pink: + return const Color(0xFFE91E63); + case AppColor.teal: + return const Color(0xFF00BCD4); } } diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 99141e4..073fd7a 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -43,4 +43,32 @@ enum Ruleset { } /// Different colors for highlighting games -enum GameColor { red, orange, yellow, green, teal, blue, purple, pink } +enum AppColor { red, orange, yellow, green, teal, blue, purple, pink } + +enum StatisticType { + totalMatches, + totalWins, + totalScore, + totalLosses, + averageScore, + bestScore, + worstScore, + winrate, +} + +enum StatisticScope { + allPlayers, + //selectedPlayer, + selectedGroups, + selectedGames, + timeframe, +} + +enum Timeframe { + last7Days, + last30Days, + last90Days, + last180Days, + lastYear, + allTime, +} diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index a4c2300..3ee8ebd 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -77,8 +77,8 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Returns `true` if the game exists, `false` otherwise. Future gameExists({required String gameId}) async { final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - return result != null; + final row = await query.getSingleOrNull(); + return row != null; } /// Retrieves all games from the database. @@ -92,7 +92,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { name: row.name, ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset), description: row.description, - color: GameColor.values.firstWhere((e) => e.name == row.color), + color: AppColor.values.firstWhere((e) => e.name == row.color), icon: row.icon, createdAt: row.createdAt, ), @@ -103,15 +103,15 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// 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(); + final row = await query.getSingle(); return Game( - id: result.id, - name: result.name, - ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset), - description: result.description, - color: GameColor.values.firstWhere((e) => e.name == result.color), - icon: result.icon, - createdAt: result.createdAt, + id: row.id, + name: row.name, + ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset), + description: row.description, + color: AppColor.values.firstWhere((e) => e.name == row.color), + icon: row.icon, + createdAt: row.createdAt, ); } @@ -123,7 +123,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { required String name, }) async { final rowsAffected = - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + await (update(gameTable)..where((tbl) => tbl.id.equals(gameId))).write( GameTableCompanion(name: Value(name)), ); return rowsAffected > 0; @@ -135,7 +135,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { required Ruleset ruleset, }) async { final rowsAffected = - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + await (update(gameTable)..where((tbl) => tbl.id.equals(gameId))).write( GameTableCompanion(ruleset: Value(ruleset.name)), ); return rowsAffected > 0; @@ -147,7 +147,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { required String description, }) async { final rowsAffected = - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + await (update(gameTable)..where((tbl) => tbl.id.equals(gameId))).write( GameTableCompanion(description: Value(description)), ); return rowsAffected > 0; @@ -156,10 +156,10 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Updates the color of the game with the given [gameId]. Future updateGameColor({ required String gameId, - required GameColor color, + required AppColor color, }) async { final rowsAffected = - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + await (update(gameTable)..where((tbl) => tbl.id.equals(gameId))).write( GameTableCompanion(color: Value(color.name)), ); return rowsAffected > 0; @@ -171,7 +171,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { required String icon, }) async { final rowsAffected = - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + await (update(gameTable)..where((tbl) => tbl.id.equals(gameId))).write( GameTableCompanion(icon: Value(icon)), ); return rowsAffected > 0; @@ -182,7 +182,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Deletes the game with the given [gameId] from the database. /// Returns `true` if the game was deleted, `false` if the game did not exist. Future deleteGame({required String gameId}) async { - final query = delete(gameTable)..where((g) => g.id.equals(gameId)); + final query = delete(gameTable)..where((tbl) => tbl.id.equals(gameId)); 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 bffe5a4..8d1c0a2 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -143,16 +143,16 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { final query = select(groupTable); final result = await query.get(); return Future.wait( - result.map((groupData) async { + result.map((row) async { final members = await db.playerGroupDao.getPlayersOfGroup( - groupId: groupData.id, + groupId: row.id, ); return Group( - id: groupData.id, - name: groupData.name, - description: groupData.description, + id: row.id, + name: row.name, + description: row.description, members: members, - createdAt: groupData.createdAt, + createdAt: row.createdAt, ); }), ); @@ -161,18 +161,18 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Retrieves a [Group] by its [groupId], including its members. Future getGroupById({required String groupId}) async { final query = select(groupTable)..where((g) => g.id.equals(groupId)); - final result = await query.getSingle(); + final row = await query.getSingle(); List members = await db.playerGroupDao.getPlayersOfGroup( groupId: groupId, ); return Group( - id: result.id, - name: result.name, - description: result.description, + id: row.id, + name: row.name, + description: row.description, members: members, - createdAt: result.createdAt, + createdAt: row.createdAt, ); } @@ -180,17 +180,49 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { Future getGroupCount() async { final count = await (selectOnly(groupTable)..addColumns([groupTable.id.count()])) - .map((row) => row.read(groupTable.id.count())) + .map((tbl) => tbl.read(groupTable.id.count())) .getSingle(); return count ?? 0; } + /// Retrieves all groups a specific player belongs to. + /// Returns an empty list if the player is not part of any group. + Future> getGroupsByPlayer({required String playerId}) async { + final playerGroups = await (select( + playerGroupTable, + )..where((tbl) => tbl.playerId.equals(playerId))).get(); + + if (playerGroups.isEmpty) return []; + + final groupIds = playerGroups.map((pg) => pg.groupId).toSet().toList(); + final result = + await (select(groupTable) + ..where((tbl) => tbl.id.isIn(groupIds)) + ..orderBy([(tbl) => OrderingTerm.desc(tbl.createdAt)])) + .get(); + + return Future.wait( + result.map((row) async { + final members = await db.playerGroupDao.getPlayersOfGroup( + groupId: row.id, + ); + return Group( + id: row.id, + name: row.name, + description: row.description, + members: members, + createdAt: row.createdAt, + ); + }), + ); + } + /// 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; + final row = await query.getSingleOrNull(); + return row != null; } /* Delete */ @@ -220,9 +252,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { required String name, }) async { final rowsAffected = - await (update(groupTable)..where((g) => g.id.equals(groupId))).write( - GroupTableCompanion(name: Value(name)), - ); + await (update(groupTable)..where((tbl) => tbl.id.equals(groupId))) + .write(GroupTableCompanion(name: Value(name))); return rowsAffected > 0; } @@ -233,9 +264,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { required String description, }) async { final rowsAffected = - await (update(groupTable)..where((g) => g.id.equals(groupId))).write( - GroupTableCompanion(description: Value(description)), - ); + await (update(groupTable)..where((tbl) => tbl.id.equals(groupId))) + .write(GroupTableCompanion(description: Value(description))); return rowsAffected > 0; } } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 88cca35..74611b6 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -258,15 +258,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// 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; + final row = await query.getSingleOrNull(); + return row != null; } /// 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())) + .map((tbl) => tbl.read(matchTable.id.count())) .getSingle(); return count ?? 0; } @@ -279,10 +279,12 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return Future.wait( result.map((row) async { final game = await db.gameDao.getGameById(gameId: row.gameId); + Group? group; if (row.groupId != null) { group = await db.groupDao.getGroupById(groupId: row.groupId!); } + final players = await db.playerMatchDao.getPlayersOfMatch( matchId: row.id, ); @@ -312,13 +314,13 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// 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(); + final row = await query.getSingle(); - final game = await db.gameDao.getGameById(gameId: result.gameId); + final game = await db.gameDao.getGameById(gameId: row.gameId); Group? group; - if (result.groupId != null) { - group = await db.groupDao.getGroupById(groupId: result.groupId!); + if (row.groupId != null) { + group = await db.groupDao.getGroupById(groupId: row.groupId!); } final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId); @@ -328,15 +330,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final teams = await _getMatchTeams(matchId: matchId); return Match( - id: result.id, - name: result.name, + id: row.id, + name: row.name, game: game, group: group, players: players, teams: teams.isEmpty ? null : teams, - notes: result.notes, - createdAt: result.createdAt, - endedAt: result.endedAt, + notes: row.notes, + createdAt: row.createdAt, + endedAt: row.endedAt, scores: scores, ); } @@ -347,25 +349,73 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { await (selectOnly(matchTable) ..where(matchTable.gameId.equals(gameId)) ..addColumns([matchTable.id.count()])) - .map((row) => row.read(matchTable.id.count())) + .map((tbl) => tbl.read(matchTable.id.count())) .getSingle(); return count ?? 0; } + Future> getMatchesByPlayer({required String playerId}) async { + final playerMatches = await (select( + playerMatchTable, + )..where((tbl) => tbl.playerId.equals(playerId))).get(); + + if (playerMatches.isEmpty) return []; + + final matchIds = playerMatches.map((tbl) => tbl.matchId).toSet().toList(); + final result = + await (select(matchTable) + ..where((tbl) => tbl.id.isIn(matchIds)) + ..orderBy([(tbl) => OrderingTerm.desc(tbl.createdAt)])) + .get(); + + return Future.wait( + result.map((row) async { + final game = await db.gameDao.getGameById(gameId: row.gameId); + + Group? group; + if (row.groupId != null) { + group = await db.groupDao.getGroupById(groupId: row.groupId!); + } + + final players = await db.playerMatchDao.getPlayersOfMatch( + matchId: row.id, + ); + final scores = await db.scoreEntryDao.getAllMatchScores( + matchId: row.id, + ); + final teams = await _getMatchTeams(matchId: row.id); + + return Match( + id: row.id, + name: row.name, + game: game, + group: group, + players: players, + teams: teams.isEmpty ? null : teams, + notes: row.notes, + createdAt: row.createdAt, + endedAt: row.endedAt, + scores: scores, + ); + }), + ); + } + /// Retrieves all matches associated with the given [groupId]. /// Queries the database directly, filtering by [groupId]. Future> getMatchesByGroup({required String groupId}) async { final query = select(matchTable)..where((m) => m.groupId.equals(groupId)); - final rows = await query.get(); + final result = await query.get(); return Future.wait( - rows.map((row) async { + result.map((row) async { final game = await db.gameDao.getGameById(gameId: row.gameId); final group = await db.groupDao.getGroupById(groupId: groupId); final players = await db.playerMatchDao.getPlayersOfMatch( matchId: row.id, ); final teams = await _getMatchTeams(matchId: row.id); + return Match( id: row.id, name: row.name, @@ -385,7 +435,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { Future> _getMatchTeams({required String matchId}) async { // Get all unique team IDs from PlayerMatchTable for this match final playerMatchQuery = select(db.playerMatchTable) - ..where((pm) => pm.matchId.equals(matchId) & pm.teamId.isNotNull()); + ..where((tbl) => tbl.matchId.equals(matchId) & tbl.teamId.isNotNull()); final playerMatches = await playerMatchQuery.get(); if (playerMatches.isEmpty) return []; @@ -412,7 +462,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { required String matchId, required String name, }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final query = update(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.write( MatchTableCompanion(name: Value(name)), ); @@ -427,7 +477,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { required String matchId, required String? groupId, }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final query = update(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.write( MatchTableCompanion(groupId: Value(groupId)), ); @@ -440,7 +490,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { required String matchId, required String notes, }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final query = update(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.write( MatchTableCompanion(notes: Value(notes)), ); @@ -451,7 +501,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// Sets the groupId to null. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future removeMatchGroup({required String matchId}) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final query = update(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.write( const MatchTableCompanion(groupId: Value(null)), ); @@ -465,7 +515,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { required String matchId, required DateTime endedAt, }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final query = update(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.write( MatchTableCompanion(endedAt: Value(endedAt)), ); @@ -477,7 +527,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// 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 query = delete(matchTable)..where((tbl) => tbl.id.equals(matchId)); final rowsAffected = await query.go(); return rowsAffected > 0; } @@ -493,7 +543,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// Deletes all matches associated with a specific game. /// Returns the number of matches deleted. Future deleteMatchesByGame({required String gameId}) async { - final query = delete(matchTable)..where((m) => m.gameId.equals(gameId)); + final query = delete(matchTable)..where((tbl) => tbl.gameId.equals(gameId)); final rowsAffected = await query.go(); return rowsAffected; } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 51e5845..1a60243 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -17,7 +17,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// the new one. Future addPlayer({required Player player}) async { if (!await playerExists(playerId: player.id)) { - final int nameCount = await calculateNameCount(name: player.name); + final int nameCount = await _processNameCount(name: player.name); await into(playerTable).insert( PlayerTableCompanion.insert( @@ -64,7 +64,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { final playersWithName = entry.value; // Get the current nameCount - var nameCount = await calculateNameCount(name: name); + var nameCount = await _processNameCount(name: name); // One player with the same name if (playersWithName.length == 1) { @@ -113,7 +113,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { Future getPlayerCount() async { final count = await (selectOnly(playerTable)..addColumns([playerTable.id.count()])) - .map((row) => row.read(playerTable.id.count())) + .map((tbl) => tbl.read(playerTable.id.count())) .getSingle(); return count ?? 0; } @@ -122,8 +122,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// Returns `true` if the player exists, `false` otherwise. Future playerExists({required String playerId}) async { final query = select(playerTable)..where((p) => p.id.equals(playerId)); - final result = await query.getSingleOrNull(); - return result != null; + final row = await query.getSingleOrNull(); + return row != null; } /// Retrieves all players from the database. @@ -146,57 +146,76 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// 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(); + final row = await query.getSingle(); return Player( - id: result.id, - name: result.name, - description: result.description, - createdAt: result.createdAt, - nameCount: result.nameCount, + id: row.id, + name: row.name, + description: row.description, + createdAt: row.createdAt, + nameCount: row.nameCount, ); } /* Update */ /// Updates the name of the player with the given [playerId] to [name]. + /// + /// Keeps the `nameCount` values of the affected name groups consistent: + /// - The renamed player gets a fresh `nameCount` for the new name group. + /// - All players in the previous name group whose `nameCount` was greater + /// than the removed one get decremented by 1, so the numbering stays + /// contiguous (1..N) in `createdAt` order. + /// - If only one player remains in the previous name group, their + /// `nameCount` is reset to 0. Future updatePlayerName({ required String playerId, required String name, }) async { - // Get previous name and name count for the player before updating - final previousPlayerName = - await (select(playerTable)..where((p) => p.id.equals(playerId))) - .map((row) => row.name) - .getSingleOrNull() ?? - ''; - final previousNameCount = await getNameCount(name: previousPlayerName); + return transaction(() async { + final previousPlayer = await (select( + playerTable, + )..where((tbl) => tbl.id.equals(playerId))).getSingleOrNull(); + if (previousPlayer == null) return false; - final rowsAffected = - await (update(playerTable)..where((p) => p.id.equals(playerId))).write( - PlayerTableCompanion(name: Value(name)), - ); + final previousName = previousPlayer.name; + final previousCount = previousPlayer.nameCount; - // Update name count for the new name - final count = await calculateNameCount(name: name); - if (count > 0) { - await (update(playerTable)..where((p) => p.name.equals(name))).write( - PlayerTableCompanion(nameCount: Value(count)), - ); - } + // Determine the nameCount for the renamed player in the new group. + final newNameCount = await _processNameCount(name: name); - if (previousNameCount > 0) { - // Get the player with that name and the hightest nameCount, and update their nameCount to previousNameCount - final player = await getPlayerWithHighestNameCount( - name: previousPlayerName, - ); - if (player != null) { - await updateNameCount( - playerId: player.id, - nameCount: previousNameCount, - ); + final rowsAffected = + await (update( + playerTable, + )..where((tbl) => tbl.id.equals(playerId))).write( + PlayerTableCompanion( + name: Value(name), + nameCount: Value(newNameCount), + ), + ); + + // Consolidate the previous name group. + final remainingCount = await getNameCount(name: previousName); + + if (remainingCount == 1) { + // Only one player left + await (update(playerTable)..where((p) => p.name.equals(previousName))) + .write(const PlayerTableCompanion(nameCount: Value(0))); + } else if (remainingCount > 1 && previousCount > 0) { + // Shift every player above the gap down by one to keep numbering in order. + await (update(playerTable)..where( + (tbl) => + tbl.name.equals(previousName) & + tbl.nameCount.isBiggerThanValue(previousCount), + )) + .write( + PlayerTableCompanion.custom( + nameCount: playerTable.nameCount - const Constant(1), + ), + ); } - } - return rowsAffected > 0; + + return rowsAffected > 0; + }); } /// Updates the description of the player with the given [playerId] to @@ -207,9 +226,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { required String description, }) async { final rowsAffected = - await (update(playerTable)..where((g) => g.id.equals(playerId))).write( - PlayerTableCompanion(description: Value(description)), - ); + await (update(playerTable)..where((tbl) => tbl.id.equals(playerId))) + .write(PlayerTableCompanion(description: Value(description))); return rowsAffected > 0; } @@ -218,7 +236,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /// 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 { - final query = delete(playerTable)..where((p) => p.id.equals(playerId)); + final query = delete(playerTable)..where((tbl) => tbl.id.equals(playerId)); final rowsAffected = await query.go(); return rowsAffected > 0; } @@ -226,8 +244,10 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /* Name count management */ /// Retrieves the count of players with the given [name]. + /// Returns the highest name count if players with the same name exist, + /// otherwise `null`. Future getNameCount({required String name}) async { - final query = select(playerTable)..where((p) => p.name.equals(name)); + final query = select(playerTable)..where((tbl) => tbl.name.equals(name)); final result = await query.get(); return result.length; } @@ -238,7 +258,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { required String playerId, required int nameCount, }) async { - final query = update(playerTable)..where((p) => p.id.equals(playerId)); + final query = update(playerTable)..where((tbl) => tbl.id.equals(playerId)); final rowsAffected = await query.write( PlayerTableCompanion(nameCount: Value(nameCount)), ); @@ -248,8 +268,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { @visibleForTesting Future getPlayerWithHighestNameCount({required String name}) async { final query = select(playerTable) - ..where((p) => p.name.equals(name)) - ..orderBy([(p) => OrderingTerm.desc(p.nameCount)]) + ..where((tbl) => tbl.name.equals(name)) + ..orderBy([(tbl) => OrderingTerm.desc(tbl.nameCount)]) ..limit(1); final result = await query.getSingleOrNull(); if (result != null) { @@ -264,34 +284,47 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { return null; } + /// Processes the name count for a new player with the given [name]. + ///- 0 Player: returning 0 + ///- 1 Player: returning 2, and initializes the nameCount for the existing player to 1 + ///- Other: returning the existing count + 1 + Future _processNameCount({required String name}) async { + final nameCount = await calculateNameCount(name: name); + if (nameCount == 2) { + // If one other player exists with the same name, initialize the nameCount + await initializeNameCount(name: name); + } + return nameCount; + } + @visibleForTesting + /// Calculates the name count for a new player with the given [name]. + /// - 0 Players: Name count is 0 + /// - 1 Player: Name count is 2 (since the existing player will be 1) + /// - Other: Name count is the existing count + 1 Future calculateNameCount({required String name}) async { final count = await getNameCount(name: name); final int nameCount; - if (count == 1) { - // If one other player exists with the same name, initialize the nameCount - await initializeNameCount(name: name); - // And for the new player, set nameCount to 2 + if (count == 0) { + // If no other players exist with the same name, the returned nameCount is 0 + nameCount = 0; + } else if (count == 1) { + // If one other player with the name count exists, the returned name count is 2 nameCount = 2; - } else if (count > 1) { + } else { // If more than one player exists with the same name, just increment // the nameCount for the new player nameCount = count + 1; - } else { - // If no other players exist with the same name, set nameCount to 0 - nameCount = 0; } - return nameCount; } @visibleForTesting Future initializeNameCount({required String name}) async { final rowsAffected = - await (update(playerTable)..where((p) => p.name.equals(name))).write( - const PlayerTableCompanion(nameCount: Value(1)), - ); + await (update(playerTable)..where((tbl) => tbl.name.equals(name))) + .write(const PlayerTableCompanion(nameCount: Value(1))); return rowsAffected > 0; } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 48c5653..b48dc23 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -39,18 +39,25 @@ class PlayerGroupDao extends DatabaseAccessor /// Retrieves all players belonging to a specific group by [groupId]. Future> getPlayersOfGroup({required String groupId}) async { - final query = select(playerGroupTable) - ..where((pG) => pG.groupId.equals(groupId)); - final result = await query.get(); + final query = select(playerGroupTable).join([ + innerJoin( + playerTable, + playerTable.id.equalsExp(playerGroupTable.playerId), + ), + ])..where(playerGroupTable.groupId.equals(groupId)); - List groupMembers = List.empty(growable: true); - - for (var entry in result) { - final player = await db.playerDao.getPlayerById(playerId: entry.playerId); - groupMembers.add(player); - } - - return groupMembers; + final result = await query.map((row) => row.readTable(playerTable)).get(); + return result + .map( + (row) => Player( + id: row.id, + createdAt: row.createdAt, + name: row.name, + nameCount: row.nameCount, + description: row.description, + ), + ) + .toList(); } /// Checks if a player with [playerId] is in the group with [groupId]. @@ -60,7 +67,9 @@ class PlayerGroupDao extends DatabaseAccessor required String groupId, }) async { final query = select(playerGroupTable) - ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); + ..where( + (tbl) => tbl.playerId.equals(playerId) & tbl.groupId.equals(groupId), + ); final result = await query.getSingleOrNull(); return result != null; } @@ -81,7 +90,7 @@ class PlayerGroupDao extends DatabaseAccessor await db.transaction(() async { // Remove all existing players from the group final deleteQuery = delete(db.playerGroupTable) - ..where((p) => p.groupId.equals(groupId)); + ..where((tbl) => tbl.groupId.equals(groupId)); await deleteQuery.go(); // Add new players to the player table if they don't exist @@ -121,7 +130,9 @@ class PlayerGroupDao extends DatabaseAccessor required String groupId, }) async { final query = delete(playerGroupTable) - ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); + ..where( + (tbl) => tbl.playerId.equals(playerId) & tbl.groupId.equals(groupId), + ); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index d119468..912cfcc 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -40,7 +40,7 @@ class PlayerMatchDao extends DatabaseAccessor await (selectOnly(playerMatchTable) ..where(playerMatchTable.matchId.equals(matchId)) ..addColumns([playerMatchTable.playerId.count()])) - .map((row) => row.read(playerMatchTable.playerId.count())) + .map((tbl) => tbl.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } @@ -56,7 +56,7 @@ class PlayerMatchDao extends DatabaseAccessor ..where(playerMatchTable.matchId.equals(matchId)) ..where(playerMatchTable.playerId.equals(playerId)) ..addColumns([playerMatchTable.playerId.count()])) - .map((row) => row.read(playerMatchTable.playerId.count())) + .map((tbl) => tbl.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } @@ -66,7 +66,7 @@ class PlayerMatchDao extends DatabaseAccessor Future> getPlayersOfMatch({required String matchId}) async { final result = await (select( playerMatchTable, - )..where((p) => p.matchId.equals(matchId))).get(); + )..where((tbl) => tbl.matchId.equals(matchId))).get(); if (result.isEmpty) return []; @@ -85,8 +85,8 @@ class PlayerMatchDao extends DatabaseAccessor }) async { final result = await (select(playerMatchTable) - ..where((p) => p.matchId.equals(matchId)) - ..where((p) => p.teamId.equals(teamId))) + ..where((tbl) => tbl.matchId.equals(matchId)) + ..where((tbl) => tbl.teamId.equals(teamId))) .get(); if (result.isEmpty) return []; @@ -109,7 +109,8 @@ class PlayerMatchDao extends DatabaseAccessor }) async { final rowsAffected = await (update(playerMatchTable)..where( - (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId), + (tbl) => + tbl.matchId.equals(matchId) & tbl.playerId.equals(playerId), )) .write(PlayerMatchTableCompanion(teamId: Value(teamId))); return rowsAffected > 0; @@ -143,9 +144,9 @@ class PlayerMatchDao extends DatabaseAccessor // Remove old players if (playersToRemove.isNotEmpty) { await (delete(playerMatchTable)..where( - (pg) => - pg.matchId.equals(matchId) & - pg.playerId.isIn(playersToRemove.toList()), + (tbl) => + tbl.matchId.equals(matchId) & + tbl.playerId.isIn(playersToRemove.toList()), )) .go(); } @@ -182,8 +183,8 @@ class PlayerMatchDao extends DatabaseAccessor required String playerId, }) async { final query = delete(playerMatchTable) - ..where((pg) => pg.matchId.equals(matchId)) - ..where((pg) => pg.playerId.equals(playerId)); + ..where((tbl) => tbl.matchId.equals(matchId)) + ..where((tbl) => tbl.playerId.equals(playerId)); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/lib/data/dao/score_entry_dao.dart b/lib/data/dao/score_entry_dao.dart index 830135d..276e8fd 100644 --- a/lib/data/dao/score_entry_dao.dart +++ b/lib/data/dao/score_entry_dao.dart @@ -70,10 +70,10 @@ class ScoreEntryDao extends DatabaseAccessor }) async { final query = select(scoreEntryTable) ..where( - (s) => - s.playerId.equals(playerId) & - s.matchId.equals(matchId) & - s.roundNumber.equals(roundNumber), + (tbl) => + tbl.playerId.equals(playerId) & + tbl.matchId.equals(matchId) & + tbl.roundNumber.equals(roundNumber), ); final result = await query.getSingleOrNull(); @@ -91,7 +91,7 @@ class ScoreEntryDao extends DatabaseAccessor required String matchId, }) async { final query = select(scoreEntryTable) - ..where((s) => s.matchId.equals(matchId)); + ..where((tbl) => tbl.matchId.equals(matchId)); final result = await query.get(); final Map scoresByPlayer = {}; @@ -113,8 +113,10 @@ class ScoreEntryDao extends DatabaseAccessor required String matchId, }) async { final query = select(scoreEntryTable) - ..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId)) - ..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]); + ..where( + (tbl) => tbl.playerId.equals(playerId) & tbl.matchId.equals(matchId), + ) + ..orderBy([(tbl) => OrderingTerm.asc(tbl.roundNumber)]); final result = await query.get(); return result .map( @@ -136,8 +138,8 @@ class ScoreEntryDao extends DatabaseAccessor final query = selectOnly(scoreEntryTable) ..where(scoreEntryTable.matchId.equals(matchId)) ..addColumns([scoreEntryTable.roundNumber.max()]); - final result = await query.getSingle(); - return result.read(scoreEntryTable.roundNumber.max()); + final row = await query.getSingle(); + return row.read(scoreEntryTable.roundNumber.max()); } /// Aggregates the total score for a player in a match by summing all their @@ -166,10 +168,10 @@ class ScoreEntryDao extends DatabaseAccessor }) async { final rowsAffected = await (update(scoreEntryTable)..where( - (s) => - s.playerId.equals(playerId) & - s.matchId.equals(matchId) & - s.roundNumber.equals(entry.roundNumber), + (tbl) => + tbl.playerId.equals(playerId) & + tbl.matchId.equals(matchId) & + tbl.roundNumber.equals(entry.roundNumber), )) .write( ScoreEntryTableCompanion( @@ -190,10 +192,10 @@ class ScoreEntryDao extends DatabaseAccessor }) async { final query = delete(scoreEntryTable) ..where( - (s) => - s.playerId.equals(playerId) & - s.matchId.equals(matchId) & - s.roundNumber.equals(roundNumber), + (tbl) => + tbl.playerId.equals(playerId) & + tbl.matchId.equals(matchId) & + tbl.roundNumber.equals(roundNumber), ); final rowsAffected = await query.go(); return rowsAffected > 0; @@ -201,7 +203,7 @@ class ScoreEntryDao extends DatabaseAccessor Future deleteAllScoresForMatch({required String matchId}) async { final query = delete(scoreEntryTable) - ..where((s) => s.matchId.equals(matchId)); + ..where((tbl) => tbl.matchId.equals(matchId)); final rowsAffected = await query.go(); return rowsAffected > 0; } @@ -211,7 +213,9 @@ class ScoreEntryDao extends DatabaseAccessor required String playerId, }) async { final query = delete(scoreEntryTable) - ..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId)); + ..where( + (tbl) => tbl.playerId.equals(playerId) & tbl.matchId.equals(matchId), + ); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/lib/data/dao/statistic_dao.dart b/lib/data/dao/statistic_dao.dart new file mode 100644 index 0000000..092ceb0 --- /dev/null +++ b/lib/data/dao/statistic_dao.dart @@ -0,0 +1,127 @@ +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/db/tables/statistic_table.dart'; +import 'package:tallee/data/models/statistic.dart'; + +part 'statistic_dao.g.dart'; + +@DriftAccessor(tables: [StatisticTable]) +class StatisticDao extends DatabaseAccessor + with _$StatisticDaoMixin { + StatisticDao(super.db); + + /* Create */ + + Future addStatistic({required Statistic statistic}) async { + await into(statisticTable).insert( + StatisticTableCompanion.insert( + id: statistic.id, + type: statistic.type.name, + timeframe: Value(statistic.timeframe?.name), + displayCount: Value(statistic.displayCount), + ), + mode: InsertMode.insertOrReplace, + ); + + await db.statisticScopeDao.addStatisticScopes( + statisticId: statistic.id, + scopes: statistic.scopes, + ); + + if (statistic.selectedGroups != null) { + await db.statisticGroupDao.addStatisticGroups( + statisticId: statistic.id, + groups: statistic.selectedGroups!, + ); + } + + if (statistic.selectedGames != null) { + await db.statisticGameDao.addStatisticGames( + statisticId: statistic.id, + games: statistic.selectedGames!, + ); + } + + return true; + } + + /* Read */ + + Future getStatisticById(String statisticId) async { + final query = select(statisticTable); + final row = await query.getSingleOrNull(); + if (row != null) { + final groups = await db.statisticGroupDao.getGroupsForStatistic(row.id); + final games = await db.statisticGameDao.getGamesForStatistic(row.id); + final scopes = await db.statisticScopeDao.getScopeForStatistic(row.id); + + return Statistic( + type: StatisticType.values.firstWhere((type) => type.name == row.type), + scopes: scopes, + timeframe: Timeframe.values.firstWhereOrNull( + (t) => t.name == row.timeframe, + ), + selectedGroups: groups, + selectedGames: games, + displayCount: row.displayCount, + id: row.id, + ); + } + return null; + } + + /// Retrieves all statistics from the database, including their associated groups and games. + Future> getAllStatistics() async { + final query = select(statisticTable); + final result = await query.get(); + return Future.wait( + result.map((row) async { + final groups = await db.statisticGroupDao.getGroupsForStatistic(row.id); + final games = await db.statisticGameDao.getGamesForStatistic(row.id); + final scopes = await db.statisticScopeDao.getScopeForStatistic(row.id); + + return Statistic( + type: StatisticType.values.firstWhere( + (type) => type.name == row.type, + ), + scopes: scopes, + timeframe: Timeframe.values.firstWhereOrNull( + (t) => t.name == row.timeframe, + ), + selectedGroups: groups, + selectedGames: games, + displayCount: row.displayCount, + id: row.id, + ); + }), + ); + } + + /* Update */ + + Future updateDisplayCount(String statisticId, int displayCount) async { + final rowsUpdated = + await (update(statisticTable) + ..where((tbl) => tbl.id.equals(statisticId))) + .write(StatisticTableCompanion(displayCount: Value(displayCount))); + + return rowsUpdated > 0; + } + + /* Delete */ + + Future deleteStatistic(String statisticId) async { + final rowsDeleted = await (delete( + statisticTable, + )..where((tbl) => tbl.id.equals(statisticId))).go(); + + return rowsDeleted > 0; + } + + Future deleteAllStatistics() async { + final rowsDeleted = await delete(statisticTable).go(); + return rowsDeleted > 0; + } +} diff --git a/lib/data/dao/statistic_dao.g.dart b/lib/data/dao/statistic_dao.g.dart new file mode 100644 index 0000000..0ce36e1 --- /dev/null +++ b/lib/data/dao/statistic_dao.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'statistic_dao.dart'; + +// ignore_for_file: type=lint +mixin _$StatisticDaoMixin on DatabaseAccessor { + $StatisticTableTable get statisticTable => attachedDatabase.statisticTable; + StatisticDaoManager get managers => StatisticDaoManager(this); +} + +class StatisticDaoManager { + final _$StatisticDaoMixin _db; + StatisticDaoManager(this._db); + $$StatisticTableTableTableManager get statisticTable => + $$StatisticTableTableTableManager( + _db.attachedDatabase, + _db.statisticTable, + ); +} diff --git a/lib/data/dao/statistic_game_dao.dart b/lib/data/dao/statistic_game_dao.dart new file mode 100644 index 0000000..d546ce0 --- /dev/null +++ b/lib/data/dao/statistic_game_dao.dart @@ -0,0 +1,61 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/db/tables/statistic_game_table.dart'; +import 'package:tallee/data/models/game.dart'; + +part 'statistic_game_dao.g.dart'; + +@DriftAccessor(tables: [StatisticGameTable]) +class StatisticGameDao extends DatabaseAccessor + with _$StatisticGameDaoMixin { + StatisticGameDao(super.db); + + /// Retrieves a list of games associated with a specific statistic. + Future?> getGamesForStatistic(String statisticId) async { + final query = select(statisticGameTable).join([ + innerJoin(gameTable, gameTable.id.equalsExp(statisticGameTable.gameId)), + ])..where(statisticGameTable.statisticId.equals(statisticId)); + + final results = await query.map((row) => row.readTable(gameTable)).get(); + if (results.isEmpty) return null; + return results + .map( + (row) => Game( + id: row.id, + name: row.name, + ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset), + description: row.description, + color: AppColor.values.firstWhere((e) => e.name == row.color), + icon: row.icon, + createdAt: row.createdAt, + ), + ) + .toList(); + } + + Future addStatisticGames({ + required String statisticId, + required List games, + }) { + final entries = games + .map( + (game) => StatisticGameTableCompanion.insert( + statisticId: statisticId, + gameId: game.id, + ), + ) + .toList(); + + return batch((batch) { + batch.insertAll( + statisticGameTable, + entries, + mode: InsertMode.insertOrReplace, + ); + }).then((_) => true).catchError((error) { + print('Error adding statistic games: $error'); + return false; + }); + } +} diff --git a/lib/data/dao/statistic_game_dao.g.dart b/lib/data/dao/statistic_game_dao.g.dart new file mode 100644 index 0000000..d6ee984 --- /dev/null +++ b/lib/data/dao/statistic_game_dao.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'statistic_game_dao.dart'; + +// ignore_for_file: type=lint +mixin _$StatisticGameDaoMixin on DatabaseAccessor { + $StatisticTableTable get statisticTable => attachedDatabase.statisticTable; + $GameTableTable get gameTable => attachedDatabase.gameTable; + $StatisticGameTableTable get statisticGameTable => + attachedDatabase.statisticGameTable; + StatisticGameDaoManager get managers => StatisticGameDaoManager(this); +} + +class StatisticGameDaoManager { + final _$StatisticGameDaoMixin _db; + StatisticGameDaoManager(this._db); + $$StatisticTableTableTableManager get statisticTable => + $$StatisticTableTableTableManager( + _db.attachedDatabase, + _db.statisticTable, + ); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable); + $$StatisticGameTableTableTableManager get statisticGameTable => + $$StatisticGameTableTableTableManager( + _db.attachedDatabase, + _db.statisticGameTable, + ); +} diff --git a/lib/data/dao/statistic_group_dao.dart b/lib/data/dao/statistic_group_dao.dart new file mode 100644 index 0000000..449b6a8 --- /dev/null +++ b/lib/data/dao/statistic_group_dao.dart @@ -0,0 +1,67 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/db/tables/group_table.dart'; +import 'package:tallee/data/db/tables/statistic_group_table.dart'; +import 'package:tallee/data/models/group.dart'; + +part 'statistic_group_dao.g.dart'; + +@DriftAccessor(tables: [StatisticGroupTable, GroupTable]) +class StatisticGroupDao extends DatabaseAccessor + with _$StatisticGroupDaoMixin { + StatisticGroupDao(super.db); + + /// Retrieves a list of groups associated with a specific statistic. + Future?> getGroupsForStatistic(String statisticId) async { + final query = select(statisticGroupTable).join([ + innerJoin( + groupTable, + groupTable.id.equalsExp(statisticGroupTable.groupId), + ), + ])..where(statisticGroupTable.statisticId.equals(statisticId)); + + final results = await query.map((row) => row.readTable(groupTable)).get(); + if (results.isEmpty) return null; + final groups = await Future.wait( + results.map((result) async { + final groupMembers = await db.playerGroupDao.getPlayersOfGroup( + groupId: result.id, + ); + return Group( + id: result.id, + createdAt: result.createdAt, + name: result.name, + description: result.description, + members: groupMembers, + ); + }), + ); + + return groups; + } + + Future addStatisticGroups({ + required String statisticId, + required List groups, + }) async { + final entries = groups + .map( + (group) => StatisticGroupTableCompanion.insert( + statisticId: statisticId, + groupId: group.id, + ), + ) + .toList(); + + return batch((batch) { + batch.insertAll( + statisticGroupTable, + entries, + mode: InsertMode.insertOrReplace, + ); + }).then((_) => true).catchError((error) { + print('Error adding statistic groups: $error'); + return false; + }); + } +} diff --git a/lib/data/dao/statistic_group_dao.g.dart b/lib/data/dao/statistic_group_dao.g.dart new file mode 100644 index 0000000..57a83c5 --- /dev/null +++ b/lib/data/dao/statistic_group_dao.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'statistic_group_dao.dart'; + +// ignore_for_file: type=lint +mixin _$StatisticGroupDaoMixin on DatabaseAccessor { + $StatisticTableTable get statisticTable => attachedDatabase.statisticTable; + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $StatisticGroupTableTable get statisticGroupTable => + attachedDatabase.statisticGroupTable; + StatisticGroupDaoManager get managers => StatisticGroupDaoManager(this); +} + +class StatisticGroupDaoManager { + final _$StatisticGroupDaoMixin _db; + StatisticGroupDaoManager(this._db); + $$StatisticTableTableTableManager get statisticTable => + $$StatisticTableTableTableManager( + _db.attachedDatabase, + _db.statisticTable, + ); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable); + $$StatisticGroupTableTableTableManager get statisticGroupTable => + $$StatisticGroupTableTableTableManager( + _db.attachedDatabase, + _db.statisticGroupTable, + ); +} diff --git a/lib/data/dao/statistic_scope_dao.dart b/lib/data/dao/statistic_scope_dao.dart new file mode 100644 index 0000000..eb286af --- /dev/null +++ b/lib/data/dao/statistic_scope_dao.dart @@ -0,0 +1,55 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/db/tables/statistic_scope_table.dart'; + +part 'statistic_scope_dao.g.dart'; + +@DriftAccessor(tables: [StatisticScopeTable]) +class StatisticScopeDao extends DatabaseAccessor + with _$StatisticScopeDaoMixin { + StatisticScopeDao(super.db); + + /// Retrieves a list of statistic scopes associated with a specific statistic ID. + Future> getScopeForStatistic(String statisticId) async { + final query = select(statisticScopeTable) + ..where((tbl) => tbl.statisticId.equals(statisticId)); + + final result = await query.get(); + return result + .map( + (row) => StatisticScope.values.firstWhere( + (e) => e.name == row.scope, + orElse: () => throw Exception( + 'Invalid scope value: ${row.scope} for statistic ID: $statisticId', + ), + ), + ) + .toList(); + } + + Future addStatisticScopes({ + required String statisticId, + required List scopes, + }) async { + final entries = scopes + .map( + (scope) => StatisticScopeTableCompanion.insert( + statisticId: statisticId, + scope: scope.name, + ), + ) + .toList(); + + return batch((batch) { + batch.insertAll( + statisticScopeTable, + entries, + mode: InsertMode.insertOrReplace, + ); + }).then((_) => true).catchError((error) { + print('Error adding statistic scopes: $error'); + return false; + }); + } +} diff --git a/lib/data/dao/statistic_scope_dao.g.dart b/lib/data/dao/statistic_scope_dao.g.dart new file mode 100644 index 0000000..adaa171 --- /dev/null +++ b/lib/data/dao/statistic_scope_dao.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'statistic_scope_dao.dart'; + +// ignore_for_file: type=lint +mixin _$StatisticScopeDaoMixin on DatabaseAccessor { + $StatisticTableTable get statisticTable => attachedDatabase.statisticTable; + $StatisticScopeTableTable get statisticScopeTable => + attachedDatabase.statisticScopeTable; + StatisticScopeDaoManager get managers => StatisticScopeDaoManager(this); +} + +class StatisticScopeDaoManager { + final _$StatisticScopeDaoMixin _db; + StatisticScopeDaoManager(this._db); + $$StatisticTableTableTableManager get statisticTable => + $$StatisticTableTableTableManager( + _db.attachedDatabase, + _db.statisticTable, + ); + $$StatisticScopeTableTableTableManager get statisticScopeTable => + $$StatisticScopeTableTableTableManager( + _db.attachedDatabase, + _db.statisticScopeTable, + ); +} diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart index cba68fb..333db68 100644 --- a/lib/data/dao/team_dao.dart +++ b/lib/data/dao/team_dao.dart @@ -86,7 +86,7 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { Future getTeamCount() async { final count = await (selectOnly(teamTable)..addColumns([teamTable.id.count()])) - .map((row) => row.read(teamTable.id.count())) + .map((tbl) => tbl.read(teamTable.id.count())) .getSingle(); return count ?? 0; } @@ -95,8 +95,8 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { /// Returns `true` if the team exists, `false` otherwise. Future teamExists({required String teamId}) async { final query = select(teamTable)..where((t) => t.id.equals(teamId)); - final result = await query.getSingleOrNull(); - return result != null; + final row = await query.getSingleOrNull(); + return row != null; } /// Retrieves all teams from the database. @@ -119,12 +119,12 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { /// Retrieves a [Team] by its [teamId], including its members. Future getTeamById({required String teamId}) async { final query = select(teamTable)..where((t) => t.id.equals(teamId)); - final result = await query.getSingle(); + final row = await query.getSingle(); final members = await _getTeamMembers(teamId: teamId); return Team( - id: result.id, - name: result.name, - createdAt: result.createdAt, + id: row.id, + name: row.name, + createdAt: row.createdAt, members: members, ); } @@ -133,13 +133,13 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { Future> _getTeamMembers({required String teamId}) async { // Get all player_match entries with this teamId final playerMatchQuery = select(db.playerMatchTable) - ..where((pm) => pm.teamId.equals(teamId)); + ..where((tbl) => tbl.teamId.equals(teamId)); final playerMatches = await playerMatchQuery.get(); if (playerMatches.isEmpty) return []; // Get unique player IDs - final playerIds = playerMatches.map((pm) => pm.playerId).toSet(); + final playerIds = playerMatches.map((tbl) => tbl.playerId).toSet(); // Fetch all players final players = await Future.wait( @@ -156,7 +156,7 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { required String name, }) async { final rowsAffected = - await (update(teamTable)..where((t) => t.id.equals(teamId))).write( + await (update(teamTable)..where((tbl) => tbl.id.equals(teamId))).write( TeamTableCompanion(name: Value(name)), ); return rowsAffected > 0; @@ -175,7 +175,7 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { /// Deletes the team with the given [teamId] from the database. /// Returns `true` if the team was deleted, `false` otherwise. Future deleteTeam({required String teamId}) async { - final query = delete(teamTable)..where((t) => t.id.equals(teamId)); + final query = delete(teamTable)..where((tbl) => tbl.id.equals(teamId)); final rowsAffected = await query.go(); return rowsAffected > 0; } diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index a7e9c1d..792da0e 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -8,6 +8,10 @@ import 'package:tallee/data/dao/player_dao.dart'; import 'package:tallee/data/dao/player_group_dao.dart'; import 'package:tallee/data/dao/player_match_dao.dart'; import 'package:tallee/data/dao/score_entry_dao.dart'; +import 'package:tallee/data/dao/statistic_dao.dart'; +import 'package:tallee/data/dao/statistic_game_dao.dart'; +import 'package:tallee/data/dao/statistic_group_dao.dart'; +import 'package:tallee/data/dao/statistic_scope_dao.dart'; import 'package:tallee/data/dao/team_dao.dart'; import 'package:tallee/data/db/tables/game_table.dart'; import 'package:tallee/data/db/tables/group_table.dart'; @@ -16,6 +20,10 @@ import 'package:tallee/data/db/tables/player_group_table.dart'; import 'package:tallee/data/db/tables/player_match_table.dart'; import 'package:tallee/data/db/tables/player_table.dart'; import 'package:tallee/data/db/tables/score_entry_table.dart'; +import 'package:tallee/data/db/tables/statistic_game_table.dart'; +import 'package:tallee/data/db/tables/statistic_group_table.dart'; +import 'package:tallee/data/db/tables/statistic_scope_table.dart'; +import 'package:tallee/data/db/tables/statistic_table.dart'; import 'package:tallee/data/db/tables/team_table.dart'; part 'database.g.dart'; @@ -30,6 +38,10 @@ part 'database.g.dart'; GameTable, TeamTable, ScoreEntryTable, + StatisticTable, + StatisticScopeTable, + StatisticGameTable, + StatisticGroupTable, ], daos: [ PlayerDao, @@ -40,6 +52,10 @@ part 'database.g.dart'; GameDao, ScoreEntryDao, TeamDao, + StatisticDao, + StatisticScopeDao, + StatisticGameDao, + StatisticGroupDao, ], ) class AppDatabase extends _$AppDatabase { diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index c8d0faa..3a9d277 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -2732,6 +2732,1020 @@ class ScoreEntryTableCompanion extends UpdateCompanion { } } +class $StatisticTableTable extends StatisticTable + with TableInfo<$StatisticTableTable, StatisticTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $StatisticTableTable(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 _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _timeframeMeta = const VerificationMeta( + 'timeframe', + ); + @override + late final GeneratedColumn timeframe = GeneratedColumn( + 'timeframe', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _displayCountMeta = const VerificationMeta( + 'displayCount', + ); + @override + late final GeneratedColumn displayCount = GeneratedColumn( + 'display_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(5), + ); + @override + List get $columns => [id, type, timeframe, displayCount]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'statistic_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('type')) { + context.handle( + _typeMeta, + type.isAcceptableOrUnknown(data['type']!, _typeMeta), + ); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('timeframe')) { + context.handle( + _timeframeMeta, + timeframe.isAcceptableOrUnknown(data['timeframe']!, _timeframeMeta), + ); + } + if (data.containsKey('display_count')) { + context.handle( + _displayCountMeta, + displayCount.isAcceptableOrUnknown( + data['display_count']!, + _displayCountMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + StatisticTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StatisticTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + timeframe: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}timeframe'], + ), + displayCount: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}display_count'], + )!, + ); + } + + @override + $StatisticTableTable createAlias(String alias) { + return $StatisticTableTable(attachedDatabase, alias); + } +} + +class StatisticTableData extends DataClass + implements Insertable { + final String id; + final String type; + final String? timeframe; + final int displayCount; + const StatisticTableData({ + required this.id, + required this.type, + this.timeframe, + required this.displayCount, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['type'] = Variable(type); + if (!nullToAbsent || timeframe != null) { + map['timeframe'] = Variable(timeframe); + } + map['display_count'] = Variable(displayCount); + return map; + } + + StatisticTableCompanion toCompanion(bool nullToAbsent) { + return StatisticTableCompanion( + id: Value(id), + type: Value(type), + timeframe: timeframe == null && nullToAbsent + ? const Value.absent() + : Value(timeframe), + displayCount: Value(displayCount), + ); + } + + factory StatisticTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StatisticTableData( + id: serializer.fromJson(json['id']), + type: serializer.fromJson(json['type']), + timeframe: serializer.fromJson(json['timeframe']), + displayCount: serializer.fromJson(json['displayCount']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'type': serializer.toJson(type), + 'timeframe': serializer.toJson(timeframe), + 'displayCount': serializer.toJson(displayCount), + }; + } + + StatisticTableData copyWith({ + String? id, + String? type, + Value timeframe = const Value.absent(), + int? displayCount, + }) => StatisticTableData( + id: id ?? this.id, + type: type ?? this.type, + timeframe: timeframe.present ? timeframe.value : this.timeframe, + displayCount: displayCount ?? this.displayCount, + ); + StatisticTableData copyWithCompanion(StatisticTableCompanion data) { + return StatisticTableData( + id: data.id.present ? data.id.value : this.id, + type: data.type.present ? data.type.value : this.type, + timeframe: data.timeframe.present ? data.timeframe.value : this.timeframe, + displayCount: data.displayCount.present + ? data.displayCount.value + : this.displayCount, + ); + } + + @override + String toString() { + return (StringBuffer('StatisticTableData(') + ..write('id: $id, ') + ..write('type: $type, ') + ..write('timeframe: $timeframe, ') + ..write('displayCount: $displayCount') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, type, timeframe, displayCount); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StatisticTableData && + other.id == this.id && + other.type == this.type && + other.timeframe == this.timeframe && + other.displayCount == this.displayCount); +} + +class StatisticTableCompanion extends UpdateCompanion { + final Value id; + final Value type; + final Value timeframe; + final Value displayCount; + final Value rowid; + const StatisticTableCompanion({ + this.id = const Value.absent(), + this.type = const Value.absent(), + this.timeframe = const Value.absent(), + this.displayCount = const Value.absent(), + this.rowid = const Value.absent(), + }); + StatisticTableCompanion.insert({ + required String id, + required String type, + this.timeframe = const Value.absent(), + this.displayCount = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + type = Value(type); + static Insertable custom({ + Expression? id, + Expression? type, + Expression? timeframe, + Expression? displayCount, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (type != null) 'type': type, + if (timeframe != null) 'timeframe': timeframe, + if (displayCount != null) 'display_count': displayCount, + if (rowid != null) 'rowid': rowid, + }); + } + + StatisticTableCompanion copyWith({ + Value? id, + Value? type, + Value? timeframe, + Value? displayCount, + Value? rowid, + }) { + return StatisticTableCompanion( + id: id ?? this.id, + type: type ?? this.type, + timeframe: timeframe ?? this.timeframe, + displayCount: displayCount ?? this.displayCount, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (timeframe.present) { + map['timeframe'] = Variable(timeframe.value); + } + if (displayCount.present) { + map['display_count'] = Variable(displayCount.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StatisticTableCompanion(') + ..write('id: $id, ') + ..write('type: $type, ') + ..write('timeframe: $timeframe, ') + ..write('displayCount: $displayCount, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $StatisticScopeTableTable extends StatisticScopeTable + with TableInfo<$StatisticScopeTableTable, StatisticScopeTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $StatisticScopeTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _statisticIdMeta = const VerificationMeta( + 'statisticId', + ); + @override + late final GeneratedColumn statisticId = GeneratedColumn( + 'statistic_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES statistic_table (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _scopeMeta = const VerificationMeta('scope'); + @override + late final GeneratedColumn scope = GeneratedColumn( + 'scope', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [statisticId, scope]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'statistic_scope_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('statistic_id')) { + context.handle( + _statisticIdMeta, + statisticId.isAcceptableOrUnknown( + data['statistic_id']!, + _statisticIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_statisticIdMeta); + } + if (data.containsKey('scope')) { + context.handle( + _scopeMeta, + scope.isAcceptableOrUnknown(data['scope']!, _scopeMeta), + ); + } else if (isInserting) { + context.missing(_scopeMeta); + } + return context; + } + + @override + Set get $primaryKey => {statisticId, scope}; + @override + StatisticScopeTableData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StatisticScopeTableData( + statisticId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}statistic_id'], + )!, + scope: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}scope'], + )!, + ); + } + + @override + $StatisticScopeTableTable createAlias(String alias) { + return $StatisticScopeTableTable(attachedDatabase, alias); + } +} + +class StatisticScopeTableData extends DataClass + implements Insertable { + final String statisticId; + final String scope; + const StatisticScopeTableData({ + required this.statisticId, + required this.scope, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['statistic_id'] = Variable(statisticId); + map['scope'] = Variable(scope); + return map; + } + + StatisticScopeTableCompanion toCompanion(bool nullToAbsent) { + return StatisticScopeTableCompanion( + statisticId: Value(statisticId), + scope: Value(scope), + ); + } + + factory StatisticScopeTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StatisticScopeTableData( + statisticId: serializer.fromJson(json['statisticId']), + scope: serializer.fromJson(json['scope']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'statisticId': serializer.toJson(statisticId), + 'scope': serializer.toJson(scope), + }; + } + + StatisticScopeTableData copyWith({String? statisticId, String? scope}) => + StatisticScopeTableData( + statisticId: statisticId ?? this.statisticId, + scope: scope ?? this.scope, + ); + StatisticScopeTableData copyWithCompanion(StatisticScopeTableCompanion data) { + return StatisticScopeTableData( + statisticId: data.statisticId.present + ? data.statisticId.value + : this.statisticId, + scope: data.scope.present ? data.scope.value : this.scope, + ); + } + + @override + String toString() { + return (StringBuffer('StatisticScopeTableData(') + ..write('statisticId: $statisticId, ') + ..write('scope: $scope') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(statisticId, scope); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StatisticScopeTableData && + other.statisticId == this.statisticId && + other.scope == this.scope); +} + +class StatisticScopeTableCompanion + extends UpdateCompanion { + final Value statisticId; + final Value scope; + final Value rowid; + const StatisticScopeTableCompanion({ + this.statisticId = const Value.absent(), + this.scope = const Value.absent(), + this.rowid = const Value.absent(), + }); + StatisticScopeTableCompanion.insert({ + required String statisticId, + required String scope, + this.rowid = const Value.absent(), + }) : statisticId = Value(statisticId), + scope = Value(scope); + static Insertable custom({ + Expression? statisticId, + Expression? scope, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (statisticId != null) 'statistic_id': statisticId, + if (scope != null) 'scope': scope, + if (rowid != null) 'rowid': rowid, + }); + } + + StatisticScopeTableCompanion copyWith({ + Value? statisticId, + Value? scope, + Value? rowid, + }) { + return StatisticScopeTableCompanion( + statisticId: statisticId ?? this.statisticId, + scope: scope ?? this.scope, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (statisticId.present) { + map['statistic_id'] = Variable(statisticId.value); + } + if (scope.present) { + map['scope'] = Variable(scope.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StatisticScopeTableCompanion(') + ..write('statisticId: $statisticId, ') + ..write('scope: $scope, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $StatisticGameTableTable extends StatisticGameTable + with TableInfo<$StatisticGameTableTable, StatisticGameTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $StatisticGameTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _statisticIdMeta = const VerificationMeta( + 'statisticId', + ); + @override + late final GeneratedColumn statisticId = GeneratedColumn( + 'statistic_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES statistic_table (id) ON DELETE CASCADE', + ), + ); + 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 game_table (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [statisticId, gameId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'statistic_game_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('statistic_id')) { + context.handle( + _statisticIdMeta, + statisticId.isAcceptableOrUnknown( + data['statistic_id']!, + _statisticIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_statisticIdMeta); + } + 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 => {statisticId, gameId}; + @override + StatisticGameTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StatisticGameTableData( + statisticId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}statistic_id'], + )!, + gameId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}game_id'], + )!, + ); + } + + @override + $StatisticGameTableTable createAlias(String alias) { + return $StatisticGameTableTable(attachedDatabase, alias); + } +} + +class StatisticGameTableData extends DataClass + implements Insertable { + final String statisticId; + final String gameId; + const StatisticGameTableData({ + required this.statisticId, + required this.gameId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['statistic_id'] = Variable(statisticId); + map['game_id'] = Variable(gameId); + return map; + } + + StatisticGameTableCompanion toCompanion(bool nullToAbsent) { + return StatisticGameTableCompanion( + statisticId: Value(statisticId), + gameId: Value(gameId), + ); + } + + factory StatisticGameTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StatisticGameTableData( + statisticId: serializer.fromJson(json['statisticId']), + gameId: serializer.fromJson(json['gameId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'statisticId': serializer.toJson(statisticId), + 'gameId': serializer.toJson(gameId), + }; + } + + StatisticGameTableData copyWith({String? statisticId, String? gameId}) => + StatisticGameTableData( + statisticId: statisticId ?? this.statisticId, + gameId: gameId ?? this.gameId, + ); + StatisticGameTableData copyWithCompanion(StatisticGameTableCompanion data) { + return StatisticGameTableData( + statisticId: data.statisticId.present + ? data.statisticId.value + : this.statisticId, + gameId: data.gameId.present ? data.gameId.value : this.gameId, + ); + } + + @override + String toString() { + return (StringBuffer('StatisticGameTableData(') + ..write('statisticId: $statisticId, ') + ..write('gameId: $gameId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(statisticId, gameId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StatisticGameTableData && + other.statisticId == this.statisticId && + other.gameId == this.gameId); +} + +class StatisticGameTableCompanion + extends UpdateCompanion { + final Value statisticId; + final Value gameId; + final Value rowid; + const StatisticGameTableCompanion({ + this.statisticId = const Value.absent(), + this.gameId = const Value.absent(), + this.rowid = const Value.absent(), + }); + StatisticGameTableCompanion.insert({ + required String statisticId, + required String gameId, + this.rowid = const Value.absent(), + }) : statisticId = Value(statisticId), + gameId = Value(gameId); + static Insertable custom({ + Expression? statisticId, + Expression? gameId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (statisticId != null) 'statistic_id': statisticId, + if (gameId != null) 'game_id': gameId, + if (rowid != null) 'rowid': rowid, + }); + } + + StatisticGameTableCompanion copyWith({ + Value? statisticId, + Value? gameId, + Value? rowid, + }) { + return StatisticGameTableCompanion( + statisticId: statisticId ?? this.statisticId, + gameId: gameId ?? this.gameId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (statisticId.present) { + map['statistic_id'] = Variable(statisticId.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('StatisticGameTableCompanion(') + ..write('statisticId: $statisticId, ') + ..write('gameId: $gameId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $StatisticGroupTableTable extends StatisticGroupTable + with TableInfo<$StatisticGroupTableTable, StatisticGroupTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $StatisticGroupTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _statisticIdMeta = const VerificationMeta( + 'statisticId', + ); + @override + late final GeneratedColumn statisticId = GeneratedColumn( + 'statistic_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES statistic_table (id) ON DELETE CASCADE', + ), + ); + 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_table (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [statisticId, groupId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'statistic_group_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('statistic_id')) { + context.handle( + _statisticIdMeta, + statisticId.isAcceptableOrUnknown( + data['statistic_id']!, + _statisticIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_statisticIdMeta); + } + 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 => {statisticId, groupId}; + @override + StatisticGroupTableData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StatisticGroupTableData( + statisticId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}statistic_id'], + )!, + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + )!, + ); + } + + @override + $StatisticGroupTableTable createAlias(String alias) { + return $StatisticGroupTableTable(attachedDatabase, alias); + } +} + +class StatisticGroupTableData extends DataClass + implements Insertable { + final String statisticId; + final String groupId; + const StatisticGroupTableData({ + required this.statisticId, + required this.groupId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['statistic_id'] = Variable(statisticId); + map['group_id'] = Variable(groupId); + return map; + } + + StatisticGroupTableCompanion toCompanion(bool nullToAbsent) { + return StatisticGroupTableCompanion( + statisticId: Value(statisticId), + groupId: Value(groupId), + ); + } + + factory StatisticGroupTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StatisticGroupTableData( + statisticId: serializer.fromJson(json['statisticId']), + groupId: serializer.fromJson(json['groupId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'statisticId': serializer.toJson(statisticId), + 'groupId': serializer.toJson(groupId), + }; + } + + StatisticGroupTableData copyWith({String? statisticId, String? groupId}) => + StatisticGroupTableData( + statisticId: statisticId ?? this.statisticId, + groupId: groupId ?? this.groupId, + ); + StatisticGroupTableData copyWithCompanion(StatisticGroupTableCompanion data) { + return StatisticGroupTableData( + statisticId: data.statisticId.present + ? data.statisticId.value + : this.statisticId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, + ); + } + + @override + String toString() { + return (StringBuffer('StatisticGroupTableData(') + ..write('statisticId: $statisticId, ') + ..write('groupId: $groupId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(statisticId, groupId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StatisticGroupTableData && + other.statisticId == this.statisticId && + other.groupId == this.groupId); +} + +class StatisticGroupTableCompanion + extends UpdateCompanion { + final Value statisticId; + final Value groupId; + final Value rowid; + const StatisticGroupTableCompanion({ + this.statisticId = const Value.absent(), + this.groupId = const Value.absent(), + this.rowid = const Value.absent(), + }); + StatisticGroupTableCompanion.insert({ + required String statisticId, + required String groupId, + this.rowid = const Value.absent(), + }) : statisticId = Value(statisticId), + groupId = Value(groupId); + static Insertable custom({ + Expression? statisticId, + Expression? groupId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (statisticId != null) 'statistic_id': statisticId, + if (groupId != null) 'group_id': groupId, + if (rowid != null) 'rowid': rowid, + }); + } + + StatisticGroupTableCompanion copyWith({ + Value? statisticId, + Value? groupId, + Value? rowid, + }) { + return StatisticGroupTableCompanion( + statisticId: statisticId ?? this.statisticId, + groupId: groupId ?? this.groupId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (statisticId.present) { + map['statistic_id'] = Variable(statisticId.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('StatisticGroupTableCompanion(') + ..write('statisticId: $statisticId, ') + ..write('groupId: $groupId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); @@ -2749,6 +3763,13 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final $ScoreEntryTableTable scoreEntryTable = $ScoreEntryTableTable( this, ); + late final $StatisticTableTable statisticTable = $StatisticTableTable(this); + late final $StatisticScopeTableTable statisticScopeTable = + $StatisticScopeTableTable(this); + late final $StatisticGameTableTable statisticGameTable = + $StatisticGameTableTable(this); + late final $StatisticGroupTableTable statisticGroupTable = + $StatisticGroupTableTable(this); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final MatchDao matchDao = MatchDao(this as AppDatabase); @@ -2761,6 +3782,16 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final GameDao gameDao = GameDao(this as AppDatabase); late final ScoreEntryDao scoreEntryDao = ScoreEntryDao(this as AppDatabase); late final TeamDao teamDao = TeamDao(this as AppDatabase); + late final StatisticDao statisticDao = StatisticDao(this as AppDatabase); + late final StatisticScopeDao statisticScopeDao = StatisticScopeDao( + this as AppDatabase, + ); + late final StatisticGameDao statisticGameDao = StatisticGameDao( + this as AppDatabase, + ); + late final StatisticGroupDao statisticGroupDao = StatisticGroupDao( + this as AppDatabase, + ); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -2774,6 +3805,10 @@ abstract class _$AppDatabase extends GeneratedDatabase { teamTable, playerMatchTable, scoreEntryTable, + statisticTable, + statisticScopeTable, + statisticGameTable, + statisticGroupTable, ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ @@ -2840,6 +3875,41 @@ abstract class _$AppDatabase extends GeneratedDatabase { ), result: [TableUpdate('score_entry_table', kind: UpdateKind.delete)], ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'statistic_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('statistic_scope_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'statistic_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('statistic_game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'game_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('statistic_game_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'statistic_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('statistic_group_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'group_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('statistic_group_table', kind: UpdateKind.delete)], + ), ]); } @@ -3419,6 +4489,33 @@ final class $$GroupTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey< + $StatisticGroupTableTable, + List + > + _statisticGroupTableRefsTable(_$AppDatabase db) => + MultiTypedResultKey.fromTable( + db.statisticGroupTable, + aliasName: $_aliasNameGenerator( + db.groupTable.id, + db.statisticGroupTable.groupId, + ), + ); + + $$StatisticGroupTableTableProcessedTableManager get statisticGroupTableRefs { + final manager = $$StatisticGroupTableTableTableManager( + $_db, + $_db.statisticGroupTable, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _statisticGroupTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$GroupTableTableFilterComposer @@ -3499,6 +4596,31 @@ class $$GroupTableTableFilterComposer ); return f(composer); } + + Expression statisticGroupTableRefs( + Expression Function($$StatisticGroupTableTableFilterComposer f) f, + ) { + final $$StatisticGroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGroupTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGroupTableTableFilterComposer( + $db: $db, + $table: $db.statisticGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableOrderingComposer @@ -3603,6 +4725,32 @@ class $$GroupTableTableAnnotationComposer ); return f(composer); } + + Expression statisticGroupTableRefs( + Expression Function($$StatisticGroupTableTableAnnotationComposer a) f, + ) { + final $$StatisticGroupTableTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGroupTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGroupTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GroupTableTableTableManager @@ -3621,6 +4769,7 @@ class $$GroupTableTableTableManager PrefetchHooks Function({ bool matchTableRefs, bool playerGroupTableRefs, + bool statisticGroupTableRefs, }) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) @@ -3671,12 +4820,17 @@ class $$GroupTableTableTableManager ) .toList(), prefetchHooksCallback: - ({matchTableRefs = false, playerGroupTableRefs = false}) { + ({ + matchTableRefs = false, + playerGroupTableRefs = false, + statisticGroupTableRefs = false, + }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (matchTableRefs) db.matchTable, if (playerGroupTableRefs) db.playerGroupTable, + if (statisticGroupTableRefs) db.statisticGroupTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -3723,6 +4877,27 @@ class $$GroupTableTableTableManager ), typedResults: items, ), + if (statisticGroupTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + StatisticGroupTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._statisticGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).statisticGroupTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.groupId == item.id, + ), + typedResults: items, + ), ]; }, ); @@ -3743,7 +4918,11 @@ typedef $$GroupTableTableProcessedTableManager = $$GroupTableTableUpdateCompanionBuilder, (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function({bool matchTableRefs, bool playerGroupTableRefs}) + PrefetchHooks Function({ + bool matchTableRefs, + bool playerGroupTableRefs, + bool statisticGroupTableRefs, + }) >; typedef $$GameTableTableCreateCompanionBuilder = GameTableCompanion Function({ @@ -3789,6 +4968,33 @@ final class $$GameTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey< + $StatisticGameTableTable, + List + > + _statisticGameTableRefsTable(_$AppDatabase db) => + MultiTypedResultKey.fromTable( + db.statisticGameTable, + aliasName: $_aliasNameGenerator( + db.gameTable.id, + db.statisticGameTable.gameId, + ), + ); + + $$StatisticGameTableTableProcessedTableManager get statisticGameTableRefs { + final manager = $$StatisticGameTableTableTableManager( + $_db, + $_db.statisticGameTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _statisticGameTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$GameTableTableFilterComposer @@ -3859,6 +5065,31 @@ class $$GameTableTableFilterComposer ); return f(composer); } + + Expression statisticGameTableRefs( + Expression Function($$StatisticGameTableTableFilterComposer f) f, + ) { + final $$StatisticGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGameTableTableFilterComposer( + $db: $db, + $table: $db.statisticGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GameTableTableOrderingComposer @@ -3962,6 +5193,32 @@ class $$GameTableTableAnnotationComposer ); return f(composer); } + + Expression statisticGameTableRefs( + Expression Function($$StatisticGameTableTableAnnotationComposer a) f, + ) { + final $$StatisticGameTableTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGameTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGameTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$GameTableTableTableManager @@ -3977,7 +5234,10 @@ class $$GameTableTableTableManager $$GameTableTableUpdateCompanionBuilder, (GameTableData, $$GameTableTableReferences), GameTableData, - PrefetchHooks Function({bool matchTableRefs}) + PrefetchHooks Function({ + bool matchTableRefs, + bool statisticGameTableRefs, + }) > { $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) : super( @@ -4038,36 +5298,63 @@ class $$GameTableTableTableManager ), ) .toList(), - prefetchHooksCallback: ({matchTableRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [if (matchTableRefs) db.matchTable], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (matchTableRefs) - await $_getPrefetchedData< - GameTableData, - $GameTableTable, - MatchTableData - >( - currentTable: table, - referencedTable: $$GameTableTableReferences - ._matchTableRefsTable(db), - managerFromTypedResult: (p0) => - $$GameTableTableReferences( - db, - table, - p0, - ).matchTableRefs, - referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where((e) => e.gameId == item.id), - typedResults: items, - ), - ]; + prefetchHooksCallback: + ({matchTableRefs = false, statisticGameTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (matchTableRefs) db.matchTable, + if (statisticGameTableRefs) db.statisticGameTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (matchTableRefs) + await $_getPrefetchedData< + GameTableData, + $GameTableTable, + MatchTableData + >( + currentTable: table, + referencedTable: $$GameTableTableReferences + ._matchTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GameTableTableReferences( + db, + table, + p0, + ).matchTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), + if (statisticGameTableRefs) + await $_getPrefetchedData< + GameTableData, + $GameTableTable, + StatisticGameTableData + >( + currentTable: table, + referencedTable: $$GameTableTableReferences + ._statisticGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GameTableTableReferences( + db, + table, + p0, + ).statisticGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.gameId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, ), ); } @@ -4084,7 +5371,7 @@ typedef $$GameTableTableProcessedTableManager = $$GameTableTableUpdateCompanionBuilder, (GameTableData, $$GameTableTableReferences), GameTableData, - PrefetchHooks Function({bool matchTableRefs}) + PrefetchHooks Function({bool matchTableRefs, bool statisticGameTableRefs}) >; typedef $$MatchTableTableCreateCompanionBuilder = MatchTableCompanion Function({ @@ -6273,6 +7560,1557 @@ typedef $$ScoreEntryTableTableProcessedTableManager = ScoreEntryTableData, PrefetchHooks Function({bool playerId, bool matchId}) >; +typedef $$StatisticTableTableCreateCompanionBuilder = + StatisticTableCompanion Function({ + required String id, + required String type, + Value timeframe, + Value displayCount, + Value rowid, + }); +typedef $$StatisticTableTableUpdateCompanionBuilder = + StatisticTableCompanion Function({ + Value id, + Value type, + Value timeframe, + Value displayCount, + Value rowid, + }); + +final class $$StatisticTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $StatisticTableTable, + StatisticTableData + > { + $$StatisticTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static MultiTypedResultKey< + $StatisticScopeTableTable, + List + > + _statisticScopeTableRefsTable(_$AppDatabase db) => + MultiTypedResultKey.fromTable( + db.statisticScopeTable, + aliasName: $_aliasNameGenerator( + db.statisticTable.id, + db.statisticScopeTable.statisticId, + ), + ); + + $$StatisticScopeTableTableProcessedTableManager get statisticScopeTableRefs { + final manager = $$StatisticScopeTableTableTableManager( + $_db, + $_db.statisticScopeTable, + ).filter((f) => f.statisticId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _statisticScopeTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey< + $StatisticGameTableTable, + List + > + _statisticGameTableRefsTable(_$AppDatabase db) => + MultiTypedResultKey.fromTable( + db.statisticGameTable, + aliasName: $_aliasNameGenerator( + db.statisticTable.id, + db.statisticGameTable.statisticId, + ), + ); + + $$StatisticGameTableTableProcessedTableManager get statisticGameTableRefs { + final manager = $$StatisticGameTableTableTableManager( + $_db, + $_db.statisticGameTable, + ).filter((f) => f.statisticId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _statisticGameTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + + static MultiTypedResultKey< + $StatisticGroupTableTable, + List + > + _statisticGroupTableRefsTable(_$AppDatabase db) => + MultiTypedResultKey.fromTable( + db.statisticGroupTable, + aliasName: $_aliasNameGenerator( + db.statisticTable.id, + db.statisticGroupTable.statisticId, + ), + ); + + $$StatisticGroupTableTableProcessedTableManager get statisticGroupTableRefs { + final manager = $$StatisticGroupTableTableTableManager( + $_db, + $_db.statisticGroupTable, + ).filter((f) => f.statisticId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _statisticGroupTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$StatisticTableTableFilterComposer + extends Composer<_$AppDatabase, $StatisticTableTable> { + $$StatisticTableTableFilterComposer({ + 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 type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get timeframe => $composableBuilder( + column: $table.timeframe, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get displayCount => $composableBuilder( + column: $table.displayCount, + builder: (column) => ColumnFilters(column), + ); + + Expression statisticScopeTableRefs( + Expression Function($$StatisticScopeTableTableFilterComposer f) f, + ) { + final $$StatisticScopeTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticScopeTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticScopeTableTableFilterComposer( + $db: $db, + $table: $db.statisticScopeTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression statisticGameTableRefs( + Expression Function($$StatisticGameTableTableFilterComposer f) f, + ) { + final $$StatisticGameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGameTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGameTableTableFilterComposer( + $db: $db, + $table: $db.statisticGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression statisticGroupTableRefs( + Expression Function($$StatisticGroupTableTableFilterComposer f) f, + ) { + final $$StatisticGroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGroupTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGroupTableTableFilterComposer( + $db: $db, + $table: $db.statisticGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$StatisticTableTableOrderingComposer + extends Composer<_$AppDatabase, $StatisticTableTable> { + $$StatisticTableTableOrderingComposer({ + 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 type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get timeframe => $composableBuilder( + column: $table.timeframe, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get displayCount => $composableBuilder( + column: $table.displayCount, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$StatisticTableTableAnnotationComposer + extends Composer<_$AppDatabase, $StatisticTableTable> { + $$StatisticTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get timeframe => + $composableBuilder(column: $table.timeframe, builder: (column) => column); + + GeneratedColumn get displayCount => $composableBuilder( + column: $table.displayCount, + builder: (column) => column, + ); + + Expression statisticScopeTableRefs( + Expression Function($$StatisticScopeTableTableAnnotationComposer a) f, + ) { + final $$StatisticScopeTableTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticScopeTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticScopeTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticScopeTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression statisticGameTableRefs( + Expression Function($$StatisticGameTableTableAnnotationComposer a) f, + ) { + final $$StatisticGameTableTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGameTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGameTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticGameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + + Expression statisticGroupTableRefs( + Expression Function($$StatisticGroupTableTableAnnotationComposer a) f, + ) { + final $$StatisticGroupTableTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.statisticGroupTable, + getReferencedColumn: (t) => t.statisticId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticGroupTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticGroupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$StatisticTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $StatisticTableTable, + StatisticTableData, + $$StatisticTableTableFilterComposer, + $$StatisticTableTableOrderingComposer, + $$StatisticTableTableAnnotationComposer, + $$StatisticTableTableCreateCompanionBuilder, + $$StatisticTableTableUpdateCompanionBuilder, + (StatisticTableData, $$StatisticTableTableReferences), + StatisticTableData, + PrefetchHooks Function({ + bool statisticScopeTableRefs, + bool statisticGameTableRefs, + bool statisticGroupTableRefs, + }) + > { + $$StatisticTableTableTableManager( + _$AppDatabase db, + $StatisticTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$StatisticTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$StatisticTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$StatisticTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value timeframe = const Value.absent(), + Value displayCount = const Value.absent(), + Value rowid = const Value.absent(), + }) => StatisticTableCompanion( + id: id, + type: type, + timeframe: timeframe, + displayCount: displayCount, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String type, + Value timeframe = const Value.absent(), + Value displayCount = const Value.absent(), + Value rowid = const Value.absent(), + }) => StatisticTableCompanion.insert( + id: id, + type: type, + timeframe: timeframe, + displayCount: displayCount, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$StatisticTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({ + statisticScopeTableRefs = false, + statisticGameTableRefs = false, + statisticGroupTableRefs = false, + }) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (statisticScopeTableRefs) db.statisticScopeTable, + if (statisticGameTableRefs) db.statisticGameTable, + if (statisticGroupTableRefs) db.statisticGroupTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (statisticScopeTableRefs) + await $_getPrefetchedData< + StatisticTableData, + $StatisticTableTable, + StatisticScopeTableData + >( + currentTable: table, + referencedTable: $$StatisticTableTableReferences + ._statisticScopeTableRefsTable(db), + managerFromTypedResult: (p0) => + $$StatisticTableTableReferences( + db, + table, + p0, + ).statisticScopeTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.statisticId == item.id, + ), + typedResults: items, + ), + if (statisticGameTableRefs) + await $_getPrefetchedData< + StatisticTableData, + $StatisticTableTable, + StatisticGameTableData + >( + currentTable: table, + referencedTable: $$StatisticTableTableReferences + ._statisticGameTableRefsTable(db), + managerFromTypedResult: (p0) => + $$StatisticTableTableReferences( + db, + table, + p0, + ).statisticGameTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.statisticId == item.id, + ), + typedResults: items, + ), + if (statisticGroupTableRefs) + await $_getPrefetchedData< + StatisticTableData, + $StatisticTableTable, + StatisticGroupTableData + >( + currentTable: table, + referencedTable: $$StatisticTableTableReferences + ._statisticGroupTableRefsTable(db), + managerFromTypedResult: (p0) => + $$StatisticTableTableReferences( + db, + table, + p0, + ).statisticGroupTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.statisticId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$StatisticTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $StatisticTableTable, + StatisticTableData, + $$StatisticTableTableFilterComposer, + $$StatisticTableTableOrderingComposer, + $$StatisticTableTableAnnotationComposer, + $$StatisticTableTableCreateCompanionBuilder, + $$StatisticTableTableUpdateCompanionBuilder, + (StatisticTableData, $$StatisticTableTableReferences), + StatisticTableData, + PrefetchHooks Function({ + bool statisticScopeTableRefs, + bool statisticGameTableRefs, + bool statisticGroupTableRefs, + }) + >; +typedef $$StatisticScopeTableTableCreateCompanionBuilder = + StatisticScopeTableCompanion Function({ + required String statisticId, + required String scope, + Value rowid, + }); +typedef $$StatisticScopeTableTableUpdateCompanionBuilder = + StatisticScopeTableCompanion Function({ + Value statisticId, + Value scope, + Value rowid, + }); + +final class $$StatisticScopeTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $StatisticScopeTableTable, + StatisticScopeTableData + > { + $$StatisticScopeTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $StatisticTableTable _statisticIdTable(_$AppDatabase db) => + db.statisticTable.createAlias( + $_aliasNameGenerator( + db.statisticScopeTable.statisticId, + db.statisticTable.id, + ), + ); + + $$StatisticTableTableProcessedTableManager get statisticId { + final $_column = $_itemColumn('statistic_id')!; + + final manager = $$StatisticTableTableTableManager( + $_db, + $_db.statisticTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_statisticIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$StatisticScopeTableTableFilterComposer + extends Composer<_$AppDatabase, $StatisticScopeTableTable> { + $$StatisticScopeTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get scope => $composableBuilder( + column: $table.scope, + builder: (column) => ColumnFilters(column), + ); + + $$StatisticTableTableFilterComposer get statisticId { + final $$StatisticTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableFilterComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StatisticScopeTableTableOrderingComposer + extends Composer<_$AppDatabase, $StatisticScopeTableTable> { + $$StatisticScopeTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get scope => $composableBuilder( + column: $table.scope, + builder: (column) => ColumnOrderings(column), + ); + + $$StatisticTableTableOrderingComposer get statisticId { + final $$StatisticTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableOrderingComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StatisticScopeTableTableAnnotationComposer + extends Composer<_$AppDatabase, $StatisticScopeTableTable> { + $$StatisticScopeTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get scope => + $composableBuilder(column: $table.scope, builder: (column) => column); + + $$StatisticTableTableAnnotationComposer get statisticId { + final $$StatisticTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StatisticScopeTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $StatisticScopeTableTable, + StatisticScopeTableData, + $$StatisticScopeTableTableFilterComposer, + $$StatisticScopeTableTableOrderingComposer, + $$StatisticScopeTableTableAnnotationComposer, + $$StatisticScopeTableTableCreateCompanionBuilder, + $$StatisticScopeTableTableUpdateCompanionBuilder, + (StatisticScopeTableData, $$StatisticScopeTableTableReferences), + StatisticScopeTableData, + PrefetchHooks Function({bool statisticId}) + > { + $$StatisticScopeTableTableTableManager( + _$AppDatabase db, + $StatisticScopeTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$StatisticScopeTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$StatisticScopeTableTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + $$StatisticScopeTableTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + Value statisticId = const Value.absent(), + Value scope = const Value.absent(), + Value rowid = const Value.absent(), + }) => StatisticScopeTableCompanion( + statisticId: statisticId, + scope: scope, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String statisticId, + required String scope, + Value rowid = const Value.absent(), + }) => StatisticScopeTableCompanion.insert( + statisticId: statisticId, + scope: scope, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$StatisticScopeTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({statisticId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (statisticId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.statisticId, + referencedTable: + $$StatisticScopeTableTableReferences + ._statisticIdTable(db), + referencedColumn: + $$StatisticScopeTableTableReferences + ._statisticIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$StatisticScopeTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $StatisticScopeTableTable, + StatisticScopeTableData, + $$StatisticScopeTableTableFilterComposer, + $$StatisticScopeTableTableOrderingComposer, + $$StatisticScopeTableTableAnnotationComposer, + $$StatisticScopeTableTableCreateCompanionBuilder, + $$StatisticScopeTableTableUpdateCompanionBuilder, + (StatisticScopeTableData, $$StatisticScopeTableTableReferences), + StatisticScopeTableData, + PrefetchHooks Function({bool statisticId}) + >; +typedef $$StatisticGameTableTableCreateCompanionBuilder = + StatisticGameTableCompanion Function({ + required String statisticId, + required String gameId, + Value rowid, + }); +typedef $$StatisticGameTableTableUpdateCompanionBuilder = + StatisticGameTableCompanion Function({ + Value statisticId, + Value gameId, + Value rowid, + }); + +final class $$StatisticGameTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $StatisticGameTableTable, + StatisticGameTableData + > { + $$StatisticGameTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $StatisticTableTable _statisticIdTable(_$AppDatabase db) => + db.statisticTable.createAlias( + $_aliasNameGenerator( + db.statisticGameTable.statisticId, + db.statisticTable.id, + ), + ); + + $$StatisticTableTableProcessedTableManager get statisticId { + final $_column = $_itemColumn('statistic_id')!; + + final manager = $$StatisticTableTableTableManager( + $_db, + $_db.statisticTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_statisticIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GameTableTable _gameIdTable(_$AppDatabase db) => + db.gameTable.createAlias( + $_aliasNameGenerator(db.statisticGameTable.gameId, db.gameTable.id), + ); + + $$GameTableTableProcessedTableManager get gameId { + final $_column = $_itemColumn('game_id')!; + + final manager = $$GameTableTableTableManager( + $_db, + $_db.gameTable, + ).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 $$StatisticGameTableTableFilterComposer + extends Composer<_$AppDatabase, $StatisticGameTableTable> { + $$StatisticGameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableFilterComposer get statisticId { + final $$StatisticTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableFilterComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + 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 $$StatisticGameTableTableOrderingComposer + extends Composer<_$AppDatabase, $StatisticGameTableTable> { + $$StatisticGameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableOrderingComposer get statisticId { + final $$StatisticTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableOrderingComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + 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 $$StatisticGameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $StatisticGameTableTable> { + $$StatisticGameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableAnnotationComposer get statisticId { + final $$StatisticTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + 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 $$StatisticGameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $StatisticGameTableTable, + StatisticGameTableData, + $$StatisticGameTableTableFilterComposer, + $$StatisticGameTableTableOrderingComposer, + $$StatisticGameTableTableAnnotationComposer, + $$StatisticGameTableTableCreateCompanionBuilder, + $$StatisticGameTableTableUpdateCompanionBuilder, + (StatisticGameTableData, $$StatisticGameTableTableReferences), + StatisticGameTableData, + PrefetchHooks Function({bool statisticId, bool gameId}) + > { + $$StatisticGameTableTableTableManager( + _$AppDatabase db, + $StatisticGameTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$StatisticGameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$StatisticGameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$StatisticGameTableTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + Value statisticId = const Value.absent(), + Value gameId = const Value.absent(), + Value rowid = const Value.absent(), + }) => StatisticGameTableCompanion( + statisticId: statisticId, + gameId: gameId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String statisticId, + required String gameId, + Value rowid = const Value.absent(), + }) => StatisticGameTableCompanion.insert( + statisticId: statisticId, + gameId: gameId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$StatisticGameTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({statisticId = 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 (statisticId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.statisticId, + referencedTable: + $$StatisticGameTableTableReferences + ._statisticIdTable(db), + referencedColumn: + $$StatisticGameTableTableReferences + ._statisticIdTable(db) + .id, + ) + as T; + } + if (gameId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.gameId, + referencedTable: + $$StatisticGameTableTableReferences + ._gameIdTable(db), + referencedColumn: + $$StatisticGameTableTableReferences + ._gameIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$StatisticGameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $StatisticGameTableTable, + StatisticGameTableData, + $$StatisticGameTableTableFilterComposer, + $$StatisticGameTableTableOrderingComposer, + $$StatisticGameTableTableAnnotationComposer, + $$StatisticGameTableTableCreateCompanionBuilder, + $$StatisticGameTableTableUpdateCompanionBuilder, + (StatisticGameTableData, $$StatisticGameTableTableReferences), + StatisticGameTableData, + PrefetchHooks Function({bool statisticId, bool gameId}) + >; +typedef $$StatisticGroupTableTableCreateCompanionBuilder = + StatisticGroupTableCompanion Function({ + required String statisticId, + required String groupId, + Value rowid, + }); +typedef $$StatisticGroupTableTableUpdateCompanionBuilder = + StatisticGroupTableCompanion Function({ + Value statisticId, + Value groupId, + Value rowid, + }); + +final class $$StatisticGroupTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $StatisticGroupTableTable, + StatisticGroupTableData + > { + $$StatisticGroupTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static $StatisticTableTable _statisticIdTable(_$AppDatabase db) => + db.statisticTable.createAlias( + $_aliasNameGenerator( + db.statisticGroupTable.statisticId, + db.statisticTable.id, + ), + ); + + $$StatisticTableTableProcessedTableManager get statisticId { + final $_column = $_itemColumn('statistic_id')!; + + final manager = $$StatisticTableTableTableManager( + $_db, + $_db.statisticTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_statisticIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTableTable _groupIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.statisticGroupTable.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 $$StatisticGroupTableTableFilterComposer + extends Composer<_$AppDatabase, $StatisticGroupTableTable> { + $$StatisticGroupTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableFilterComposer get statisticId { + final $$StatisticTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableFilterComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$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 $$StatisticGroupTableTableOrderingComposer + extends Composer<_$AppDatabase, $StatisticGroupTableTable> { + $$StatisticGroupTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableOrderingComposer get statisticId { + final $$StatisticTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableOrderingComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$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 $$StatisticGroupTableTableAnnotationComposer + extends Composer<_$AppDatabase, $StatisticGroupTableTable> { + $$StatisticGroupTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + $$StatisticTableTableAnnotationComposer get statisticId { + final $$StatisticTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.statisticId, + referencedTable: $db.statisticTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$StatisticTableTableAnnotationComposer( + $db: $db, + $table: $db.statisticTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$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 $$StatisticGroupTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $StatisticGroupTableTable, + StatisticGroupTableData, + $$StatisticGroupTableTableFilterComposer, + $$StatisticGroupTableTableOrderingComposer, + $$StatisticGroupTableTableAnnotationComposer, + $$StatisticGroupTableTableCreateCompanionBuilder, + $$StatisticGroupTableTableUpdateCompanionBuilder, + (StatisticGroupTableData, $$StatisticGroupTableTableReferences), + StatisticGroupTableData, + PrefetchHooks Function({bool statisticId, bool groupId}) + > { + $$StatisticGroupTableTableTableManager( + _$AppDatabase db, + $StatisticGroupTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$StatisticGroupTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$StatisticGroupTableTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + $$StatisticGroupTableTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + Value statisticId = const Value.absent(), + Value groupId = const Value.absent(), + Value rowid = const Value.absent(), + }) => StatisticGroupTableCompanion( + statisticId: statisticId, + groupId: groupId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String statisticId, + required String groupId, + Value rowid = const Value.absent(), + }) => StatisticGroupTableCompanion.insert( + statisticId: statisticId, + groupId: groupId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$StatisticGroupTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({statisticId = 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 (statisticId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.statisticId, + referencedTable: + $$StatisticGroupTableTableReferences + ._statisticIdTable(db), + referencedColumn: + $$StatisticGroupTableTableReferences + ._statisticIdTable(db) + .id, + ) + as T; + } + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: + $$StatisticGroupTableTableReferences + ._groupIdTable(db), + referencedColumn: + $$StatisticGroupTableTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$StatisticGroupTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $StatisticGroupTableTable, + StatisticGroupTableData, + $$StatisticGroupTableTableFilterComposer, + $$StatisticGroupTableTableOrderingComposer, + $$StatisticGroupTableTableAnnotationComposer, + $$StatisticGroupTableTableCreateCompanionBuilder, + $$StatisticGroupTableTableUpdateCompanionBuilder, + (StatisticGroupTableData, $$StatisticGroupTableTableReferences), + StatisticGroupTableData, + PrefetchHooks Function({bool statisticId, bool groupId}) + >; class $AppDatabaseManager { final _$AppDatabase _db; @@ -6293,4 +9131,12 @@ class $AppDatabaseManager { $$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable); $$ScoreEntryTableTableTableManager get scoreEntryTable => $$ScoreEntryTableTableTableManager(_db, _db.scoreEntryTable); + $$StatisticTableTableTableManager get statisticTable => + $$StatisticTableTableTableManager(_db, _db.statisticTable); + $$StatisticScopeTableTableTableManager get statisticScopeTable => + $$StatisticScopeTableTableTableManager(_db, _db.statisticScopeTable); + $$StatisticGameTableTableTableManager get statisticGameTable => + $$StatisticGameTableTableTableManager(_db, _db.statisticGameTable); + $$StatisticGroupTableTableTableManager get statisticGroupTable => + $$StatisticGroupTableTableTableManager(_db, _db.statisticGroupTable); } diff --git a/lib/data/db/tables/statistic_game_table.dart b/lib/data/db/tables/statistic_game_table.dart new file mode 100644 index 0000000..e1cc7d4 --- /dev/null +++ b/lib/data/db/tables/statistic_game_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/data/db/tables/game_table.dart'; +import 'package:tallee/data/db/tables/statistic_table.dart'; + +class StatisticGameTable extends Table { + TextColumn get statisticId => + text().references(StatisticTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get gameId => + text().references(GameTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {statisticId, gameId}; +} diff --git a/lib/data/db/tables/statistic_group_table.dart b/lib/data/db/tables/statistic_group_table.dart new file mode 100644 index 0000000..cd642ad --- /dev/null +++ b/lib/data/db/tables/statistic_group_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/data/db/tables/group_table.dart'; +import 'package:tallee/data/db/tables/statistic_table.dart'; + +class StatisticGroupTable extends Table { + TextColumn get statisticId => + text().references(StatisticTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get groupId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {statisticId, groupId}; +} diff --git a/lib/data/db/tables/statistic_scope_table.dart b/lib/data/db/tables/statistic_scope_table.dart new file mode 100644 index 0000000..3a9bcdc --- /dev/null +++ b/lib/data/db/tables/statistic_scope_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/data/db/tables/statistic_table.dart'; + +class StatisticScopeTable extends Table { + TextColumn get statisticId => + text().references(StatisticTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get scope => text()(); + + @override + Set> get primaryKey => {statisticId, scope}; +} diff --git a/lib/data/db/tables/statistic_table.dart b/lib/data/db/tables/statistic_table.dart new file mode 100644 index 0000000..ef368a5 --- /dev/null +++ b/lib/data/db/tables/statistic_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; + +class StatisticTable extends Table { + TextColumn get id => text()(); + TextColumn get type => text()(); + TextColumn get timeframe => text().nullable()(); + IntColumn get displayCount => integer().withDefault(const Constant(5))(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/models/game.dart b/lib/data/models/game.dart index 89bbd30..ec69204 100644 --- a/lib/data/models/game.dart +++ b/lib/data/models/game.dart @@ -8,13 +8,13 @@ class Game { final String name; final Ruleset ruleset; final String description; - final GameColor color; + final AppColor color; final String icon; Game({ required this.name, required this.ruleset, - this.color = GameColor.orange, + this.color = AppColor.orange, this.description = '', this.icon = '', String? id, @@ -33,7 +33,7 @@ class Game { String? name, Ruleset? ruleset, String? description, - GameColor? color, + AppColor? color, String? icon, }) { return Game( @@ -73,7 +73,7 @@ class Game { orElse: () => Ruleset.singleWinner, ), description = json['description'], - color = GameColor.values.firstWhere((e) => e.name == json['color']), + color = AppColor.values.firstWhere((e) => e.name == json['color']), icon = json['icon']; Map toJson() => { diff --git a/lib/data/models/group.dart b/lib/data/models/group.dart index 5c1515c..8b1fe92 100644 --- a/lib/data/models/group.dart +++ b/lib/data/models/group.dart @@ -5,17 +5,17 @@ import 'package:uuid/uuid.dart'; class Group { final String id; - final String name; - final String description; final DateTime createdAt; + final String name; final List members; + final String description; Group({ + required this.name, + required this.members, String? id, DateTime? createdAt, - required this.name, String? description, - required this.members, }) : id = id ?? const Uuid().v4(), createdAt = createdAt ?? clock.now(), description = description ?? ''; diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart index 2c43fe3..601a01c 100644 --- a/lib/data/models/match.dart +++ b/lib/data/models/match.dart @@ -107,7 +107,7 @@ class Match { name: '', ruleset: Ruleset.singleWinner, description: '', - color: GameColor.blue, + color: AppColor.blue, icon: '', ), group = null, diff --git a/lib/data/models/statistic.dart b/lib/data/models/statistic.dart new file mode 100644 index 0000000..e995d93 --- /dev/null +++ b/lib/data/models/statistic.dart @@ -0,0 +1,48 @@ +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:uuid/uuid.dart'; + +class Statistic { + final String id; + final StatisticType type; + final List scopes; + final Timeframe? timeframe; + final List? selectedGroups; + final List? selectedGames; + final int displayCount; + + Statistic({ + required this.type, + required this.scopes, + this.timeframe, + this.selectedGroups, + this.selectedGames, + this.displayCount = 5, + String? id, + }) : id = id ?? const Uuid().v4(); + + @override + String toString() { + return 'Statistic(id: $id, type: $type, scopes: $scopes, timeframe: $timeframe, selectedGroups: $selectedGroups, selectedGames: $selectedGames)'; + } + + Statistic copyWith({ + StatisticType? type, + List? scopes, + Timeframe? timeframe, + List? selectedGroups, + List? selectedGames, + int? displayCount, + }) { + return Statistic( + id: id, + type: type ?? this.type, + scopes: scopes ?? this.scopes, + timeframe: timeframe ?? this.timeframe, + selectedGroups: selectedGroups ?? this.selectedGroups, + selectedGames: selectedGames ?? this.selectedGames, + displayCount: displayCount ?? this.displayCount, + ); + } +} diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a1ed4af..7dc32f6 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2,14 +2,18 @@ "@@locale": "de", "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", + "all_time": "Gesamter Zeitraum", "amount_of_matches": "Anzahl der Spiele", "app_name": "Tallee", + "average_score": "Durchschnittliche Punktzahl", "best_player": "Beste:r Spieler:in", + "best_score": "Beste Punktzahl", "cancel": "Abbrechen", "choose_color": "Farbe wählen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", "choose_ruleset": "Regelwerk wählen", + "classifier": "Klassifikator", "color": "Farbe", "color_blue": "Blau", "color_green": "Grün", @@ -19,12 +23,31 @@ "color_red": "Rot", "color_teal": "Türkis", "color_yellow": "Gelb", + "confirm": "Bestätigen", "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "@could_not_add_player": { + "placeholders": { + "playerName": { + "type": "String" + } + } + }, "create_game": "Spielvorlage erstellen", "create_group": "Gruppe erstellen", "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", "create_new_match": "Neues Spiel erstellen", + "create_statistic": "Statistik erstellen", + "create_statistic_classifier_subtitle": "Wähle die anzuzeigende Hauptmetrik aus", + "create_statistic_classifier_title": "Klassifikator", + "create_statistic_games_subtitle": "Wähle die gefilterten Spielvorlagen", + "create_statistic_games_title": "Spielvorlagen", + "create_statistic_groups_subtitle": "Wähle die gefilterten Gruppen", + "create_statistic_groups_title": "Gruppen", + "create_statistic_scope_subtitle": "Wähle den Hauptfilter für deine Statistik. Er bestimmt, welche Daten zur Berechnung des Klassifikators verwendet werden.", + "create_statistic_scope_title": "Bereich", + "create_statistic_timeframe_subtitle": "Wähle einen Zeitraum, nach dem die Daten gefiltert werden. Nur Spiele, die innerhalb des Zeitraums beendet wurden, fließen in die Statistik ein.", + "create_statistic_timeframe_title": "Zeitraum", "created_on": "Erstellt am", "data": "Daten", "data_successfully_deleted": "Daten erfolgreich gelöscht", @@ -44,11 +67,15 @@ }, "delete_group": "Gruppe löschen", "delete_match": "Spiel löschen", + "delete_player": "Spieler:in löschen", "description": "Beschreibung", + "displayed_entries": "Angezeigte Einträge", "drag_to_set_placement": "Ziehen um Platzierung zu setzen", "edit_game": "Spielvorlage bearbeiten", "edit_group": "Gruppe bearbeiten", "edit_match": "Gruppe bearbeiten", + "edit_name": "Name ändern", + "edit_player": "Spieler bearbeiten", "enter_points": "Punkte eingeben", "enter_results": "Ergebnisse eintragen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", @@ -59,30 +86,42 @@ "exit_view": "Ansicht verlassen", "export_canceled": "Export abgebrochen", "export_data": "Daten exportieren", + "filter": "Filter", "format_exception": "Formatfehler (siehe Konsole)", "game": "Spielvorlage", "game_name": "Spielvorlagenname", + "games": "Spielvorlagen", "group": "Gruppe", "group_name": "Gruppenname", "group_profile": "Gruppenprofil", "groups": "Gruppen", + "groups_part_of": "Gruppen Teil von", "highest_score": "Höchste Punkte", "home": "Startseite", "import_canceled": "Import abgebrochen", "import_data": "Daten importieren", "info": "Info", "invalid_schema": "Ungültiges Schema", + "last_180_days": "Letzte 180 Tage", + "last_30_days": "Letzte 30 Tage", + "last_7_days": "Letzte 7 Tage", + "last_90_days": "Letzte 90 Tage", + "last_year": "Letztes Jahr", "least_points": "Niedrigste Punkte", "legal": "Rechtliches", "legal_notice": "Impressum", "licenses": "Lizenzen", "live_edit_mode": "Live-Bearbeitungsmodus", + "loading": "Lädt...", "loser": "Verlierer:in", "lowest_score": "Niedrigste Punkte", "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", "match_profile": "Spielprofil", "matches": "Spiele", + "matches_part_of": "Spiele Teil von", + "matches_played": "Spiele gespielt", + "matches_won": "Spiele gewonnen", "members": "Mitglieder", "most_points": "Höchste Punkte", "multiple_winners": "Mehrere Gewinner:innen", @@ -92,6 +131,7 @@ "no_license_text_available": "Kein Lizenztext verfügbar", "no_licenses_found": "Keine Lizenzen gefunden", "no_matches_created_yet": "Noch keine Spiele erstellt", + "no_matches_played_yet": "Noch kein Spiel gespielt", "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:innen ausgewählt", @@ -99,13 +139,16 @@ "no_results_entered_yet": "Noch keine Ergebnisse eingetragen", "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", + "no_statistics_created_yet": "Noch keine Statistiken erstellt", "none": "Kein", "none_group": "Keine", "not_available": "Nicht verfügbar", + "not_part_of_any_group": "Noch keiner Gruppe hinzugefügt", "place": "Platz", "placement": "Platzierung", "played_matches": "Gespielte Spiele", "player_name": "Spieler:innenname", + "player_profile": "Spieler:in-Profil", "players": "Spieler:innen", "point": "Punkt", "points": "Punkte", @@ -121,15 +164,36 @@ "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.", "save_changes": "Änderungen speichern", + "scope": "Bereich", "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", + "select_a_classifier": "Klassifikator auswählen", + "select_a_game": "Spielvorlage auswählen", + "select_a_group": "Gruppe auswählen", + "select_a_scope": "Bereich auswählen", + "select_a_timeframe": "Zeitraum auswählen", + "select_a_timeframe_for_which_data_will_be_filtered": "Wähle einen Zeitraum, für den die Daten gefiltert werden sollen", "select_loser": "Verlierer:in wählen", + "select_the_filtered_games": "Wähle Spiele, nach denen gefiltert werden soll.", + "select_the_filtered_groups": "Wähle Gruppen, nach denen gefiltert werden soll.", + "select_the_filtered_timeframe": "Wähle einen Zeitraum, nach dem gefiltert werden soll.", "select_winner": "Gewinner:in wählen", "select_winners": "Gewinner:innen wählen", + "selected_games": "Ausgewählte Spielvorlagen", + "selected_groups": "Ausgewählte Gruppen", "selected_players": "Ausgewählte Spieler:innen", + "set_name": "Name setzen", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", + "statistic_type_average_score": "Durchschnittliche Punktzahl", + "statistic_type_best_score": "Beste Punktzahl", + "statistic_type_total_losses": "Niederlagen insgesamt", + "statistic_type_total_matches": "Spiele insgesamt", + "statistic_type_total_score": "Punktzahl insgesamt", + "statistic_type_total_wins": "Siege insgesamt", + "statistic_type_winrate": "Siegquote", + "statistic_type_worst_score": "Schlechteste Punktzahl", "statistics": "Statistiken", "stats": "Statistiken", "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", @@ -137,12 +201,18 @@ "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.", "tie": "Unentschieden", + "timeframe": "Zeitraum", "today_at": "Heute um", + "total_losses": "Niederlagen insgesamt", + "total_matches": "Spiele insgesamt", + "total_score": "Punktzahl insgesamt", + "total_wins": "Siege insgesamt", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", "winner": "Gewinner:in", "winners": "Gewinner:innen", "winrate": "Siegquote", "wins": "Siege", + "worst_score": "Schlechteste Punktzahl", "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 5ae94cd..988433b 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -18,13 +18,30 @@ "color_purple": "Purple", "color_red": "Red", "color_teal": "Teal", + "displayed_entries": "Displayed entries", "color_yellow": "Yellow", - "could_not_add_player": "Could not add player", + "confirm": "Confirm", + "could_not_add_player": "Could not add player {playerName}", + "@could_not_add_player": { + "placeholders": { + "playerName": { + "type": "String" + } + } + }, "create_game": "Create Game", "create_group": "Create Group", "create_match": "Create match", "create_new_group": "Create new group", "create_new_match": "Create new match", + "create_statistic": "Create statistic", + "classifier": "Classifier", + "select_the_filtered_timeframe": "Select the timeframe you want to filter by.", + "select_the_filtered_games": "Select the games you want to filter by.", + "games": "Games", + "select_the_filtered_groups": "Select the groups you want to filter by.", + "scope": "Scope", + "timeframe": "Timeframe", "created_on": "Created on", "data": "Data", "data_successfully_deleted": "Data successfully deleted", @@ -42,13 +59,17 @@ } } }, + "filter": "Filter", "delete_group": "Delete Group", "delete_match": "Delete Match", + "delete_player": "Delete player?", "description": "Description", "drag_to_set_placement": "Drag to set placement", "edit_game": "Edit Game", "edit_group": "Edit Group", "edit_match": "Edit Match", + "edit_name": "Edit name", + "edit_player": "Edit player", "enter_points": "Enter points", "enter_results": "Enter Results", "error_creating_group": "Error while creating group, please try again", @@ -66,6 +87,7 @@ "group_name": "Group name", "group_profile": "Group Profile", "groups": "Groups", + "groups_part_of": "Groups part of", "highest_score": "Highest Score", "home": "Home", "import_canceled": "Import canceled", @@ -77,12 +99,16 @@ "legal_notice": "Legal Notice", "licenses": "Licenses", "live_edit_mode": "Live Edit Mode", + "loading": "Loading...", "loser": "Loser", "lowest_score": "Lowest Score", "match_in_progress": "Match in progress...", "match_name": "Match name", "match_profile": "Match Profile", "matches": "Matches", + "matches_part_of": "Matches part of", + "matches_played": "Matches played", + "matches_won": "Matches won", "members": "Members", "most_points": "Most Points", "multiple_winners": "Multiple Winners", @@ -92,6 +118,7 @@ "no_license_text_available": "No license text available", "no_licenses_found": "No licenses found", "no_matches_created_yet": "No matches created yet", + "no_matches_played_yet": "No games played 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", @@ -99,13 +126,16 @@ "no_results_entered_yet": "No results entered yet", "no_second_match_available": "No second match available", "no_statistics_available": "No statistics available", + "no_statistics_created_yet": "No statistics created yet", "none": "None", "none_group": "None", "not_available": "Not available", + "not_part_of_any_group": "Not part of any group yet", "place": "place", "placement": "Placement", "played_matches": "Played Matches", "player_name": "Player name", + "player_profile": "Player Profile", "players": "Players", "point": "Point", "points": "Points", @@ -126,11 +156,26 @@ "select_winner": "Select Winner", "select_winners": "Select Winners", "selected_players": "Selected players", + "set_name": "Set name", "settings": "Settings", + "select_a_classifier": "Select a classifier", + "select_a_game": "Select a game", + "select_a_group": "Select a group", + "select_a_scope": "Select a scope", + "select_a_timeframe": "Select a timeframe", "single_loser": "Single Loser", "single_winner": "Single Winner", "statistics": "Statistics", "stats": "Stats", + "selected_games": "Selected games", + "selected_groups": "Selected groups", + "average_score": "Average score", + "best_score": "Best score", + "total_losses": "Total losses", + "total_matches": "Total matches", + "total_score": "Total score", + "total_wins": "Total wins", + "worst_score": "Worst score", "successfully_added_player": "Successfully added player {playerName}", "@successfully_added_player": { "description": "Success message when adding a player", @@ -145,6 +190,12 @@ "there_is_no_group_matching_your_search": "There is no group matching your search", "this_cannot_be_undone": "This can't be undone.", "tie": "Tie", + "all_time": "All time", + "last_180_days": "Last 180 days", + "last_30_days": "Last 30 days", + "last_7_days": "Last 7 days", + "last_90_days": "Last 90 days", + "last_year": "Last year", "today_at": "Today at", "undo": "Undo", "unknown_exception": "Unknown Exception (see console)", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index dd538d5..30e1b33 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -206,17 +206,29 @@ abstract class AppLocalizations { /// **'Teal'** String get color_teal; + /// No description provided for @displayed_entries. + /// + /// In en, this message translates to: + /// **'Displayed entries'** + String get displayed_entries; + /// No description provided for @color_yellow. /// /// In en, this message translates to: /// **'Yellow'** String get color_yellow; + /// No description provided for @confirm. + /// + /// In en, this message translates to: + /// **'Confirm'** + String get confirm; + /// No description provided for @could_not_add_player. /// /// In en, this message translates to: - /// **'Could not add player'** - String could_not_add_player(Object playerName); + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); /// No description provided for @create_game. /// @@ -248,6 +260,54 @@ abstract class AppLocalizations { /// **'Create new match'** String get create_new_match; + /// No description provided for @create_statistic. + /// + /// In en, this message translates to: + /// **'Create statistic'** + String get create_statistic; + + /// No description provided for @classifier. + /// + /// In en, this message translates to: + /// **'Classifier'** + String get classifier; + + /// No description provided for @select_the_filtered_timeframe. + /// + /// In en, this message translates to: + /// **'Select the timeframe you want to filter by.'** + String get select_the_filtered_timeframe; + + /// No description provided for @select_the_filtered_games. + /// + /// In en, this message translates to: + /// **'Select the games you want to filter by.'** + String get select_the_filtered_games; + + /// No description provided for @games. + /// + /// In en, this message translates to: + /// **'Games'** + String get games; + + /// No description provided for @select_the_filtered_groups. + /// + /// In en, this message translates to: + /// **'Select the groups you want to filter by.'** + String get select_the_filtered_groups; + + /// No description provided for @scope. + /// + /// In en, this message translates to: + /// **'Scope'** + String get scope; + + /// No description provided for @timeframe. + /// + /// In en, this message translates to: + /// **'Timeframe'** + String get timeframe; + /// No description provided for @created_on. /// /// In en, this message translates to: @@ -308,6 +368,12 @@ abstract class AppLocalizations { /// **'If you delete this game template, {count, plural, =1{1 match} other{{count} matches}} using this game template will also be deleted.'** String delete_game_with_matches_warning(int count); + /// No description provided for @filter. + /// + /// In en, this message translates to: + /// **'Filter'** + String get filter; + /// No description provided for @delete_group. /// /// In en, this message translates to: @@ -320,6 +386,12 @@ abstract class AppLocalizations { /// **'Delete Match'** String get delete_match; + /// No description provided for @delete_player. + /// + /// In en, this message translates to: + /// **'Delete player?'** + String get delete_player; + /// No description provided for @description. /// /// In en, this message translates to: @@ -350,6 +422,18 @@ abstract class AppLocalizations { /// **'Edit Match'** String get edit_match; + /// No description provided for @edit_name. + /// + /// In en, this message translates to: + /// **'Edit name'** + String get edit_name; + + /// No description provided for @edit_player. + /// + /// In en, this message translates to: + /// **'Edit player'** + String get edit_player; + /// No description provided for @enter_points. /// /// In en, this message translates to: @@ -452,6 +536,12 @@ abstract class AppLocalizations { /// **'Groups'** String get groups; + /// No description provided for @groups_part_of. + /// + /// In en, this message translates to: + /// **'Groups part of'** + String get groups_part_of; + /// No description provided for @highest_score. /// /// In en, this message translates to: @@ -518,6 +608,12 @@ abstract class AppLocalizations { /// **'Live Edit Mode'** String get live_edit_mode; + /// No description provided for @loading. + /// + /// In en, this message translates to: + /// **'Loading...'** + String get loading; + /// No description provided for @loser. /// /// In en, this message translates to: @@ -554,6 +650,24 @@ abstract class AppLocalizations { /// **'Matches'** String get matches; + /// No description provided for @matches_part_of. + /// + /// In en, this message translates to: + /// **'Matches part of'** + String get matches_part_of; + + /// No description provided for @matches_played. + /// + /// In en, this message translates to: + /// **'Matches played'** + String get matches_played; + + /// No description provided for @matches_won. + /// + /// In en, this message translates to: + /// **'Matches won'** + String get matches_won; + /// No description provided for @members. /// /// In en, this message translates to: @@ -608,6 +722,12 @@ abstract class AppLocalizations { /// **'No matches created yet'** String get no_matches_created_yet; + /// No description provided for @no_matches_played_yet. + /// + /// In en, this message translates to: + /// **'No games played yet'** + String get no_matches_played_yet; + /// No description provided for @no_players_created_yet. /// /// In en, this message translates to: @@ -650,6 +770,12 @@ abstract class AppLocalizations { /// **'No statistics available'** String get no_statistics_available; + /// No description provided for @no_statistics_created_yet. + /// + /// In en, this message translates to: + /// **'No statistics created yet'** + String get no_statistics_created_yet; + /// No description provided for @none. /// /// In en, this message translates to: @@ -668,6 +794,12 @@ abstract class AppLocalizations { /// **'Not available'** String get not_available; + /// No description provided for @not_part_of_any_group. + /// + /// In en, this message translates to: + /// **'Not part of any group yet'** + String get not_part_of_any_group; + /// No description provided for @place. /// /// In en, this message translates to: @@ -692,6 +824,12 @@ abstract class AppLocalizations { /// **'Player name'** String get player_name; + /// No description provided for @player_profile. + /// + /// In en, this message translates to: + /// **'Player Profile'** + String get player_profile; + /// No description provided for @players. /// /// In en, this message translates to: @@ -812,12 +950,48 @@ abstract class AppLocalizations { /// **'Selected players'** String get selected_players; + /// No description provided for @set_name. + /// + /// In en, this message translates to: + /// **'Set name'** + String get set_name; + /// No description provided for @settings. /// /// In en, this message translates to: /// **'Settings'** String get settings; + /// No description provided for @select_a_classifier. + /// + /// In en, this message translates to: + /// **'Select a classifier'** + String get select_a_classifier; + + /// No description provided for @select_a_game. + /// + /// In en, this message translates to: + /// **'Select a game'** + String get select_a_game; + + /// No description provided for @select_a_group. + /// + /// In en, this message translates to: + /// **'Select a group'** + String get select_a_group; + + /// No description provided for @select_a_scope. + /// + /// In en, this message translates to: + /// **'Select a scope'** + String get select_a_scope; + + /// No description provided for @select_a_timeframe. + /// + /// In en, this message translates to: + /// **'Select a timeframe'** + String get select_a_timeframe; + /// No description provided for @single_loser. /// /// In en, this message translates to: @@ -842,6 +1016,60 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; + /// No description provided for @selected_games. + /// + /// In en, this message translates to: + /// **'Selected games'** + String get selected_games; + + /// No description provided for @selected_groups. + /// + /// In en, this message translates to: + /// **'Selected groups'** + String get selected_groups; + + /// No description provided for @average_score. + /// + /// In en, this message translates to: + /// **'Average score'** + String get average_score; + + /// No description provided for @best_score. + /// + /// In en, this message translates to: + /// **'Best score'** + String get best_score; + + /// No description provided for @total_losses. + /// + /// In en, this message translates to: + /// **'Total losses'** + String get total_losses; + + /// No description provided for @total_matches. + /// + /// In en, this message translates to: + /// **'Total matches'** + String get total_matches; + + /// No description provided for @total_score. + /// + /// In en, this message translates to: + /// **'Total score'** + String get total_score; + + /// No description provided for @total_wins. + /// + /// In en, this message translates to: + /// **'Total wins'** + String get total_wins; + + /// No description provided for @worst_score. + /// + /// In en, this message translates to: + /// **'Worst score'** + String get worst_score; + /// Success message when adding a player /// /// In en, this message translates to: @@ -872,6 +1100,42 @@ abstract class AppLocalizations { /// **'Tie'** String get tie; + /// No description provided for @all_time. + /// + /// In en, this message translates to: + /// **'All time'** + String get all_time; + + /// No description provided for @last_180_days. + /// + /// In en, this message translates to: + /// **'Last 180 days'** + String get last_180_days; + + /// No description provided for @last_30_days. + /// + /// In en, this message translates to: + /// **'Last 30 days'** + String get last_30_days; + + /// No description provided for @last_7_days. + /// + /// In en, this message translates to: + /// **'Last 7 days'** + String get last_7_days; + + /// No description provided for @last_90_days. + /// + /// In en, this message translates to: + /// **'Last 90 days'** + String get last_90_days; + + /// No description provided for @last_year. + /// + /// In en, this message translates to: + /// **'Last year'** + String get last_year; + /// No description provided for @today_at. /// /// 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 7c5177a..1417bb7 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -62,11 +62,17 @@ class AppLocalizationsDe extends AppLocalizations { @override String get color_teal => 'Türkis'; + @override + String get displayed_entries => 'Angezeigte Einträge'; + @override String get color_yellow => 'Gelb'; @override - String could_not_add_player(Object playerName) { + String get confirm => 'Bestätigen'; + + @override + String could_not_add_player(String playerName) { return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @@ -85,6 +91,33 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_new_match => 'Neues Spiel erstellen'; + @override + String get create_statistic => 'Statistik erstellen'; + + @override + String get classifier => 'Klassifikator'; + + @override + String get select_the_filtered_timeframe => + 'Wähle einen Zeitraum, nach dem gefiltert werden soll.'; + + @override + String get select_the_filtered_games => + 'Wähle Spiele, nach denen gefiltert werden soll.'; + + @override + String get games => 'Spielvorlagen'; + + @override + String get select_the_filtered_groups => + 'Wähle Gruppen, nach denen gefiltert werden soll.'; + + @override + String get scope => 'Bereich'; + + @override + String get timeframe => 'Zeitraum'; + @override String get created_on => 'Erstellt am'; @@ -125,12 +158,18 @@ class AppLocalizationsDe extends AppLocalizations { return 'Wenn du diese Spielvorlage löschst, $_temp0 mit dieser Spielvorlage ebenfalls gelöscht.'; } + @override + String get filter => 'Filter'; + @override String get delete_group => 'Gruppe löschen'; @override String get delete_match => 'Spiel löschen'; + @override + String get delete_player => 'Spieler:in löschen'; + @override String get description => 'Beschreibung'; @@ -146,6 +185,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get edit_match => 'Gruppe bearbeiten'; + @override + String get edit_name => 'Name ändern'; + + @override + String get edit_player => 'Spieler bearbeiten'; + @override String get enter_points => 'Punkte eingeben'; @@ -201,6 +246,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get groups => 'Gruppen'; + @override + String get groups_part_of => 'Gruppen Teil von'; + @override String get highest_score => 'Höchste Punkte'; @@ -234,6 +282,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get live_edit_mode => 'Live-Bearbeitungsmodus'; + @override + String get loading => 'Lädt...'; + @override String get loser => 'Verlierer:in'; @@ -252,6 +303,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get matches => 'Spiele'; + @override + String get matches_part_of => 'Spiele Teil von'; + + @override + String get matches_played => 'Spiele gespielt'; + + @override + String get matches_won => 'Spiele gewonnen'; + @override String get members => 'Mitglieder'; @@ -279,6 +339,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_matches_created_yet => 'Noch keine Spiele erstellt'; + @override + String get no_matches_played_yet => 'Noch kein Spiel gespielt'; + @override String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @@ -301,6 +364,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_statistics_available => 'Keine Statistiken verfügbar'; + @override + String get no_statistics_created_yet => 'Noch keine Statistiken erstellt'; + @override String get none => 'Kein'; @@ -310,6 +376,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get not_available => 'Nicht verfügbar'; + @override + String get not_part_of_any_group => 'Noch keiner Gruppe hinzugefügt'; + @override String get place => 'Platz'; @@ -322,6 +391,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get player_name => 'Spieler:innenname'; + @override + String get player_profile => 'Spieler:in-Profil'; + @override String get players => 'Spieler:innen'; @@ -387,9 +459,27 @@ class AppLocalizationsDe extends AppLocalizations { @override String get selected_players => 'Ausgewählte Spieler:innen'; + @override + String get set_name => 'Name setzen'; + @override String get settings => 'Einstellungen'; + @override + String get select_a_classifier => 'Klassifikator auswählen'; + + @override + String get select_a_game => 'Spielvorlage auswählen'; + + @override + String get select_a_group => 'Gruppe auswählen'; + + @override + String get select_a_scope => 'Bereich auswählen'; + + @override + String get select_a_timeframe => 'Zeitraum auswählen'; + @override String get single_loser => 'Ein:e Verlierer:in'; @@ -402,6 +492,33 @@ class AppLocalizationsDe extends AppLocalizations { @override String get stats => 'Statistiken'; + @override + String get selected_games => 'Ausgewählte Spielvorlagen'; + + @override + String get selected_groups => 'Ausgewählte Gruppen'; + + @override + String get average_score => 'Durchschnittliche Punktzahl'; + + @override + String get best_score => 'Beste Punktzahl'; + + @override + String get total_losses => 'Niederlagen insgesamt'; + + @override + String get total_matches => 'Spiele insgesamt'; + + @override + String get total_score => 'Punktzahl insgesamt'; + + @override + String get total_wins => 'Siege insgesamt'; + + @override + String get worst_score => 'Schlechteste Punktzahl'; + @override String successfully_added_player(String playerName) { return 'Spieler:in $playerName erfolgreich hinzugefügt'; @@ -422,6 +539,24 @@ class AppLocalizationsDe extends AppLocalizations { @override String get tie => 'Unentschieden'; + @override + String get all_time => 'Gesamter Zeitraum'; + + @override + String get last_180_days => 'Letzte 180 Tage'; + + @override + String get last_30_days => 'Letzte 30 Tage'; + + @override + String get last_7_days => 'Letzte 7 Tage'; + + @override + String get last_90_days => 'Letzte 90 Tage'; + + @override + String get last_year => 'Letztes Jahr'; + @override String get today_at => 'Heute um'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index bc083e5..2275b97 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -62,12 +62,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String get color_teal => 'Teal'; + @override + String get displayed_entries => 'Displayed entries'; + @override String get color_yellow => 'Yellow'; @override - String could_not_add_player(Object playerName) { - return 'Could not add player'; + String get confirm => 'Confirm'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; } @override @@ -85,6 +91,33 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_new_match => 'Create new match'; + @override + String get create_statistic => 'Create statistic'; + + @override + String get classifier => 'Classifier'; + + @override + String get select_the_filtered_timeframe => + 'Select the timeframe you want to filter by.'; + + @override + String get select_the_filtered_games => + 'Select the games you want to filter by.'; + + @override + String get games => 'Games'; + + @override + String get select_the_filtered_groups => + 'Select the groups you want to filter by.'; + + @override + String get scope => 'Scope'; + + @override + String get timeframe => 'Timeframe'; + @override String get created_on => 'Created on'; @@ -125,12 +158,18 @@ class AppLocalizationsEn extends AppLocalizations { return 'If you delete this game template, $_temp0 using this game template will also be deleted.'; } + @override + String get filter => 'Filter'; + @override String get delete_group => 'Delete Group'; @override String get delete_match => 'Delete Match'; + @override + String get delete_player => 'Delete player?'; + @override String get description => 'Description'; @@ -146,6 +185,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get edit_match => 'Edit Match'; + @override + String get edit_name => 'Edit name'; + + @override + String get edit_player => 'Edit player'; + @override String get enter_points => 'Enter points'; @@ -201,6 +246,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get groups => 'Groups'; + @override + String get groups_part_of => 'Groups part of'; + @override String get highest_score => 'Highest Score'; @@ -234,6 +282,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get live_edit_mode => 'Live Edit Mode'; + @override + String get loading => 'Loading...'; + @override String get loser => 'Loser'; @@ -252,6 +303,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get matches => 'Matches'; + @override + String get matches_part_of => 'Matches part of'; + + @override + String get matches_played => 'Matches played'; + + @override + String get matches_won => 'Matches won'; + @override String get members => 'Members'; @@ -279,6 +339,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get no_matches_created_yet => 'No matches created yet'; + @override + String get no_matches_played_yet => 'No games played yet'; + @override String get no_players_created_yet => 'No players created yet'; @@ -301,6 +364,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get no_statistics_available => 'No statistics available'; + @override + String get no_statistics_created_yet => 'No statistics created yet'; + @override String get none => 'None'; @@ -310,6 +376,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get not_available => 'Not available'; + @override + String get not_part_of_any_group => 'Not part of any group yet'; + @override String get place => 'place'; @@ -322,6 +391,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get player_name => 'Player name'; + @override + String get player_profile => 'Player Profile'; + @override String get players => 'Players'; @@ -387,9 +459,27 @@ class AppLocalizationsEn extends AppLocalizations { @override String get selected_players => 'Selected players'; + @override + String get set_name => 'Set name'; + @override String get settings => 'Settings'; + @override + String get select_a_classifier => 'Select a classifier'; + + @override + String get select_a_game => 'Select a game'; + + @override + String get select_a_group => 'Select a group'; + + @override + String get select_a_scope => 'Select a scope'; + + @override + String get select_a_timeframe => 'Select a timeframe'; + @override String get single_loser => 'Single Loser'; @@ -402,6 +492,33 @@ class AppLocalizationsEn extends AppLocalizations { @override String get stats => 'Stats'; + @override + String get selected_games => 'Selected games'; + + @override + String get selected_groups => 'Selected groups'; + + @override + String get average_score => 'Average score'; + + @override + String get best_score => 'Best score'; + + @override + String get total_losses => 'Total losses'; + + @override + String get total_matches => 'Total matches'; + + @override + String get total_score => 'Total score'; + + @override + String get total_wins => 'Total wins'; + + @override + String get worst_score => 'Worst score'; + @override String successfully_added_player(String playerName) { return 'Successfully added player $playerName'; @@ -421,6 +538,24 @@ class AppLocalizationsEn extends AppLocalizations { @override String get tie => 'Tie'; + @override + String get all_time => 'All time'; + + @override + String get last_180_days => 'Last 180 days'; + + @override + String get last_30_days => 'Last 30 days'; + + @override + String get last_7_days => 'Last 7 days'; + + @override + String get last_90_days => 'Last 90 days'; + + @override + String get last_year => 'Last year'; + @override String get today_at => 'Today at'; diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 7e5434b..07d66b4 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -6,7 +6,7 @@ import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart'; import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart'; -import 'package:tallee/presentation/views/main_menu/statistics_view.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/statistics_view.dart'; import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/navbar_item.dart'; 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 84efbe1..72e4c69 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 @@ -89,6 +89,7 @@ class _CreateGroupViewState extends State { Expanded( child: PlayerSelection( initialSelectedPlayers: initialSelectedPlayers, + onPlayerCreated: () => widget.onMembersChanged?.call(), onChanged: (value) { setState(() { selectedPlayers = [...value]; @@ -134,6 +135,7 @@ class _CreateGroupViewState extends State { if (!mounted) return; if (success) { + widget.onMembersChanged?.call(); await HapticFeedback.successNotification(); if (mounted) { Navigator.pop(context, updatedGroup); @@ -157,7 +159,6 @@ class _CreateGroupViewState extends State { final success = await db.groupDao.addGroup( group: Group(name: groupName, members: selectedPlayers), ); - return success; } diff --git a/lib/presentation/views/main_menu/group_view/group_view.dart b/lib/presentation/views/main_menu/group_view/group_view.dart index c8a9398..e53b661 100644 --- a/lib/presentation/views/main_menu/group_view/group_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_view.dart @@ -77,6 +77,7 @@ class _GroupViewState extends State { ); } return GroupTile( + onPlayerChanged: loadGroups, group: groups[index], onTap: () async { await Navigator.push( @@ -106,13 +107,10 @@ class _GroupViewState extends State { context, adaptivePageRoute( builder: (context) { - return const CreateGroupView(); + return CreateGroupView(onMembersChanged: loadGroups); }, ), ); - setState(() { - loadGroups(); - }); }, ), ), 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 3c51cab..4d085d7 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 @@ -164,7 +164,7 @@ class _ChooseGameViewState extends State { game.ruleset, context, ), - badgeColor: getColorFromGameColor(game.color), + badgeColor: getColorFromAppColor(game.color), isHighlighted: selectedGameId == game.id, onTap: () async { setState(() { diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index 998f4e1..0671055 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -49,10 +49,10 @@ class _CreateGameViewState extends State { late final AppDatabase db; late List<(Ruleset, String)> _rulesets; - late List<(GameColor, String)> _colors; + late List<(AppColor, String)> _colors; Ruleset? selectedRuleset = Ruleset.singleWinner; - GameColor? selectedColor = GameColor.orange; + AppColor? selectedColor = AppColor.orange; /// Controller for the game name input field. final _gameNameController = TextEditingController(); @@ -87,10 +87,10 @@ class _CreateGameViewState extends State { ), ); _colors = List.generate( - GameColor.values.length, + AppColor.values.length, (index) => ( - GameColor.values[index], - translateGameColorToString(GameColor.values[index], context), + AppColor.values[index], + translateAppColorToString(AppColor.values[index], context), ), ); @@ -117,7 +117,6 @@ class _CreateGameViewState extends State { return ScaffoldMessenger( child: Scaffold( - backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( title: Text(isEditing ? loc.edit_game : loc.create_game), actions: [ @@ -468,7 +467,7 @@ class _CreateGameViewState extends State { height: 16, margin: const EdgeInsets.only(left: 12), decoration: BoxDecoration( - color: getColorFromGameColor( + color: getColorFromAppColor( _colors[index].$1, ), shape: BoxShape.circle, @@ -502,13 +501,13 @@ class _CreateGameViewState extends State { width: 16, height: 16, decoration: BoxDecoration( - color: getColorFromGameColor(selectedColor!), + color: getColorFromAppColor(selectedColor!), shape: BoxShape.circle, ), ), Padding( padding: const EdgeInsets.only(right: 5), - child: Text(translateGameColorToString(selectedColor!, context)), + child: Text(translateAppColorToString(selectedColor!, context)), ), Transform.rotate( angle: pi / 2, 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 85bb936..c8790be 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 @@ -196,6 +196,7 @@ class _CreateMatchViewState extends State { child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), initialSelectedPlayers: selectedPlayers, + onPlayerCreated: () => widget.onMatchesUpdated?.call(), onChanged: (value) { setState(() { selectedPlayers = value; 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 a7f60c6..1d30afb 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -39,7 +39,7 @@ class _MatchViewState extends State { game: Game( name: 'Game name', ruleset: Ruleset.singleWinner, - color: GameColor.blue, + color: AppColor.blue, icon: '', ), group: Group( @@ -79,7 +79,7 @@ class _MatchViewState extends State { visible: matches.isNotEmpty, replacement: Center( child: TopCenteredMessage( - icon: Icons.report, + icon: Icons.info, title: loc.info, message: loc.no_matches_created_yet, ), @@ -97,6 +97,7 @@ class _MatchViewState extends State { child: Padding( padding: const EdgeInsets.only(bottom: 12.0), child: MatchTile( + onPlayerEdited: loadMatches, width: MediaQuery.sizeOf(context).width * 0.95, onTap: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/player_detail_view.dart b/lib/presentation/views/main_menu/player_detail_view.dart new file mode 100644 index 0000000..e26c327 --- /dev/null +++ b/lib/presentation/views/main_menu/player_detail_view.dart @@ -0,0 +1,394 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:tallee/core/common.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/colored_icon_container.dart'; +import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart'; +import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart'; +import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; +import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; + +class PlayerDetailView extends StatefulWidget { + const PlayerDetailView({ + super.key, + required this.player, + required this.callback, + }); + + /// The player to display + final Player player; + + final VoidCallback callback; + + @override + State createState() => _PlayerDetailViewState(); +} + +class _PlayerDetailViewState extends State { + late final AppDatabase db; + late Player _player; + late String playerNameCount; + bool isLoading = true; + + /// Total matches played by this player + int totalMatches = 0; + + /// Total matches won by this player + int matchesWon = 0; + + /// Total groups this player belongs to + int totalGroups = 0; + + /// Full list of groups this player belongs to + List playerGroups = List.filled( + 4, + Group(name: 'Skeleton group', members: []), + ); + + /// Full list of matches this player played in + List playerMatches = List.filled( + 4, + Match( + name: 'Skeleton match', + game: Game(name: 'Game name', ruleset: Ruleset.singleWinner), + players: [], + ), + ); + + TextEditingController nameController = TextEditingController(); + + @override + void initState() { + super.initState(); + _player = widget.player; + db = Provider.of(context, listen: false); + playerNameCount = getNameCountText(_player); + _loadData(); + } + + @override + void dispose() { + nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + appBar: AppBar( + title: Text(loc.player_profile), + actions: [ + HapticIconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + showDialog( + context: context, + builder: (context) => CustomAlertDialog( + title: loc.delete_player, + content: Text(loc.this_cannot_be_undone), + actions: [ + CustomDialogAction( + onPressed: () => Navigator.of(context).pop(true), + text: loc.delete, + ), + CustomDialogAction( + onPressed: () => Navigator.of(context).pop(false), + buttonType: ButtonType.secondary, + text: loc.cancel, + ), + ], + ), + ).then((confirmed) async { + if (confirmed! && context.mounted) { + //TODO: implement player deletion in db + if (!context.mounted) return; + Navigator.pop(context); + widget.callback(); + } + }); + }, + ), + ], + ), + body: SafeArea( + child: Stack( + alignment: Alignment.center, + children: [ + ListView( + padding: const EdgeInsets.only( + left: 12, + right: 12, + top: 20, + bottom: 100, + ), + children: [ + const Center( + child: ColoredIconContainer( + icon: Icons.person, + containerSize: 55, + iconSize: 38, + ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + _player.name, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + Text( + playerNameCount, + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: CustomTheme.textColor.withAlpha(120), + ), + textAlign: TextAlign.center, + ), + ], + ), + const SizedBox(height: 5), + Text( + '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(_player.createdAt)}', + style: const TextStyle( + fontSize: 12, + color: CustomTheme.textColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + InfoTile( + title: '${loc.matches_part_of} ($totalMatches)', + icon: Icons.sports_esports, + horizontalAlignment: CrossAxisAlignment.start, + content: AppSkeleton( + enabled: isLoading, + fixLayoutBuilder: true, + alignment: Alignment.topLeft, + child: playerMatches.isNotEmpty + ? Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 12, + runSpacing: 8, + children: playerMatches.map((match) { + return TextIconTile( + text: match.name, + iconEnabled: false, + ); + }).toList(), + ) + : Text( + loc.no_matches_played_yet, + style: const TextStyle( + fontSize: 14, + color: CustomTheme.textColor, + ), + ), + ), + ), + const SizedBox(height: 15), + InfoTile( + title: '${loc.groups_part_of} ($totalGroups)', + icon: Icons.people, + horizontalAlignment: CrossAxisAlignment.start, + content: AppSkeleton( + enabled: isLoading, + fixLayoutBuilder: true, + alignment: Alignment.topLeft, + child: playerGroups.isNotEmpty + ? Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 12, + runSpacing: 8, + children: playerGroups.map((group) { + return TextIconTile( + text: group.name, + iconEnabled: false, + ); + }).toList(), + ) + : Text( + loc.not_part_of_any_group, + style: const TextStyle( + fontSize: 14, + color: CustomTheme.textColor, + ), + ), + ), + ), + const SizedBox(height: 15), + InfoTile( + title: loc.statistics, + icon: Icons.bar_chart, + content: AppSkeleton( + enabled: isLoading, + fixLayoutBuilder: true, + child: Column( + children: [ + _buildStatRow( + loc.matches_played, + totalMatches.toString(), + ), + _buildStatRow(loc.matches_won, matchesWon.toString()), + _buildStatRow( + loc.winrate, + '${totalMatches == 0 ? 0 : ((matchesWon / totalMatches) * 100).round()}%', + ), + ], + ), + ), + ), + ], + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: MainMenuButton( + text: loc.edit_player, + icon: Icons.edit, + onPressed: () async { + nameController.text = _player.name; + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setDialogState) { + return CustomAlertDialog( + title: loc.edit_name, + content: TextInputField( + controller: nameController, + hintText: loc.set_name, + onChanged: (_) => setDialogState(() {}), + ), + actions: [ + CustomDialogAction( + onPressed: isConfirmButtonEnabled() + ? () => Navigator.of(context).pop(true) + : null, + text: loc.confirm, + ), + CustomDialogAction( + onPressed: () => Navigator.of(context).pop(false), + buttonType: ButtonType.secondary, + text: loc.cancel, + ), + ], + ); + }, + ), + ).then((confirmed) async { + if (confirmed! && context.mounted) { + final newName = nameController.text.trim(); + + if (newName != _player.name) { + final fetchedPlayerNameCount = await db.playerDao + .getNameCount(name: newName); + await db.playerDao.updatePlayerName( + playerId: _player.id, + name: newName, + ); + widget.callback.call(); + setState(() { + _player = Player( + name: newName, + createdAt: _player.createdAt, + id: _player.id, + nameCount: _player.nameCount, + description: _player.description, + ); + + // If there is already a player with the same name, + // the count of that player is 0, so we start counting from 2 to get the correct count for this player. If there are no players with the same name, we just show the name without a count. + playerNameCount = fetchedPlayerNameCount == 0 + ? '' + : ' #${fetchedPlayerNameCount + 1}'; + }); + } + } + }); + }, + ), + ), + ], + ), + ), + ); + } + + /// Loads statistics for this player + Future _loadData() async { + isLoading = true; + final fetchedMatches = await db.matchDao.getMatchesByPlayer( + playerId: _player.id, + ); + final fetchedGroups = await db.groupDao.getGroupsByPlayer( + playerId: _player.id, + ); + + if (!mounted) return; + + setState(() { + playerMatches = fetchedMatches; + totalMatches = fetchedMatches.length; + matchesWon = fetchedMatches + .where((match) => match.mvp.any((mvp) => mvp.id == _player.id)) + .length; + playerGroups = fetchedGroups; + totalGroups = fetchedGroups.length; + isLoading = false; + }); + } + + /// Builds a single statistic row with a label and value + /// - [label]: The label of the statistic + /// - [value]: The value of the statistic + Widget _buildStatRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + label, + style: const TextStyle( + fontSize: 16, + color: CustomTheme.textColor, + ), + ), + ], + ), + Text( + value, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } + + bool isConfirmButtonEnabled() { + return nameController.text.trim().isNotEmpty; + } +} diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart deleted file mode 100644 index 8659a2e..0000000 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tallee/core/constants.dart'; -import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/models/match.dart'; -import 'package:tallee/data/models/player.dart'; -import 'package:tallee/l10n/generated/app_localizations.dart'; -import 'package:tallee/presentation/widgets/app_skeleton.dart'; -import 'package:tallee/presentation/widgets/tiles/quick_info_tile.dart'; -import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart'; -import 'package:tallee/presentation/widgets/top_centered_message.dart'; - -class StatisticsView extends StatefulWidget { - /// A view that displays player statistics - const StatisticsView({super.key}); - - @override - State createState() => _StatisticsViewState(); -} - -class _StatisticsViewState extends State { - int matchCount = 0; - int groupCount = 0; - - List<(Player, int)> winCounts = List.filled(6, ( - Player(name: 'Skeleton Player'), - 1, - )); - List<(Player, int)> matchCounts = List.filled(6, ( - Player(name: 'Skeleton Player'), - 1, - )); - List<(Player, double)> winRates = List.filled(6, ( - Player(name: 'Skeleton Player'), - 1, - )); - bool isLoading = true; - - @override - void initState() { - super.initState(); - loadStatisticData(); - } - - @override - Widget build(BuildContext context) { - final loc = AppLocalizations.of(context); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return SingleChildScrollView( - child: AppSkeleton( - enabled: isLoading, - fixLayoutBuilder: true, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: constraints.maxWidth), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.13, - title: loc.matches, - icon: Icons.groups_rounded, - value: matchCount, - ), - SizedBox(width: constraints.maxWidth * 0.05), - QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.13, - title: loc.groups, - icon: Icons.groups_rounded, - value: groupCount, - ), - ], - ), - SizedBox(height: constraints.maxHeight * 0.02), - Visibility( - visible: - winCounts.isEmpty && - matchCounts.isEmpty && - winRates.isEmpty, - replacement: Column( - children: [ - StatisticsTile( - icon: Icons.sports_score, - title: loc.wins, - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.green, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: loc.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: loc.amount_of_matches, - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.blue, - ), - ], - ), - child: TopCenteredMessage( - icon: Icons.info, - title: loc.info, - message: AppLocalizations.of( - context, - ).no_statistics_available, - ), - ), - SizedBox(height: MediaQuery.paddingOf(context).bottom), - ], - ), - ), - ), - ); - }, - ); - } - - /// 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(), - db.matchDao.getMatchCount(), - db.groupDao.getGroupCount(), - Future.delayed(Constants.MINIMUM_SKELETON_DURATION), - ]).then((results) async { - if (!mounted) return; - - final matches = results[0] as List; - final players = results[1] as List; - matchCount = results[2] as int; - groupCount = results[3] as int; - - winCounts = _calculateWinsForAllPlayers( - matches: matches, - players: players, - context: context, - ); - matchCounts = _calculateMatchAmountsForAllPlayers( - matches: matches, - players: players, - context: context, - ); - winRates = computeWinRatePercent( - winCounts: winCounts, - matchCounts: matchCounts, - ); - - setState(() { - isLoading = false; - }); - }); - } - - /// Calculates the number of wins for each player - /// and returns a sorted list of tuples (playerName, winCount) - List<(Player, int)> _calculateWinsForAllPlayers({ - required List matches, - required List players, - required BuildContext context, - }) { - List<(Player, int)> winCounts = []; - final loc = AppLocalizations.of(context); - - // Getting the winners - for (var match in matches) { - final mvps = match.mvp; - for (var winner in mvps) { - final index = winCounts.indexWhere((entry) => entry.$1.id == winner.id); - // -1 means winner not found in winCounts - 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.id == player.id); - // -1 means player not found in winCounts - if (index == -1) { - winCounts.add((player, 0)); - } - } - - // Replace player IDs with names - for (int i = 0; i < winCounts.length; i++) { - final playerId = winCounts[i].$1.id; - final player = players.firstWhere( - (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available), - ); - winCounts[i] = (player, winCounts[i].$2); - } - - winCounts.sort((a, b) => b.$2.compareTo(a.$2)); - - return winCounts; - } - - /// Calculates the number of matches played for each player - /// and returns a sorted list of tuples (playerName, matchCount) - List<(Player, int)> _calculateMatchAmountsForAllPlayers({ - required List matches, - required List players, - required BuildContext context, - }) { - List<(Player, int)> matchCounts = []; - final loc = AppLocalizations.of(context); - - // Counting matches for each player - for (var match in matches) { - for (Player player in match.players) { - // Check if the player is already in matchCounts - final index = matchCounts.indexWhere( - (entry) => entry.$1.id == player.id, - ); - - // -1 -> not found - if (index == -1) { - // Add new entry - matchCounts.add((player, 1)); - } else { - // Update existing entry - final currentMatchAmount = matchCounts[index].$2; - matchCounts[index] = (player, currentMatchAmount + 1); - } - } - } - - // Adding all players with zero matches - for (var player in players) { - final index = matchCounts.indexWhere((entry) => entry.$1.id == player.id); - // -1 means player not found in matchCounts - if (index == -1) { - matchCounts.add((player, 0)); - } - } - - // Replace player IDs with names - for (int i = 0; i < matchCounts.length; i++) { - final playerId = matchCounts[i].$1.id; - final player = players.firstWhere( - (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available), - ); - matchCounts[i] = (player, matchCounts[i].$2); - } - - matchCounts.sort((a, b) => b.$2.compareTo(a.$2)); - - return matchCounts; - } - - List<(Player, double)> computeWinRatePercent({ - required List<(Player, int)> winCounts, - required List<(Player, int)> matchCounts, - }) { - final Map winsMap = {for (var e in winCounts) e.$1: e.$2}; - final Map matchesMap = {for (var e in matchCounts) e.$1: e.$2}; - - // Get all unique player names - final player = {...matchesMap.keys}; - - // Calculate win rates - final result = player.map((name) { - final int w = winsMap[name] ?? 0; - final int m = matchesMap[name] ?? 0; - // Calculate percentage and round to 2 decimal places - // Avoid division by zero - final double percent = (m > 0) - ? double.parse(((w / m)).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; - } -} diff --git a/lib/presentation/views/main_menu/statistics_view/create_statistic_view.dart b/lib/presentation/views/main_menu/statistics_view/create_statistic_view.dart new file mode 100644 index 0000000..a88432a --- /dev/null +++ b/lib/presentation/views/main_menu/statistics_view/create_statistic_view.dart @@ -0,0 +1,635 @@ +import 'package:animated_custom_dropdown/custom_dropdown.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tallee/core/common.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/statistic.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart'; + +class CreateStatisticView extends StatefulWidget { + const CreateStatisticView({super.key, required this.onStatisticCreated}); + + final void Function() onStatisticCreated; + + @override + State createState() => _CreateStatisticViewState(); +} + +class _CreateStatisticViewState extends State { + bool isLoading = false; + + /* Data loaded from the database */ + List players = []; + List games = []; + List groups = []; + + /* User selections */ + StatisticType? selectedType; + List selectedScope = []; + List selectedGames = []; + List selectedPlayers = []; + List selectedGroups = []; + Timeframe? selectedTimeframe; + + @override + void initState() { + loadAllData(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var loc = AppLocalizations.of(context); + + return ScaffoldMessenger( + child: Scaffold( + appBar: AppBar(title: Text(loc.create_statistic)), + body: Stack( + alignment: AlignmentDirectional.center, + children: [ + SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 80, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Classifier title + Padding( + padding: const EdgeInsetsGeometry.only(left: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.classifier, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Text( + 'description', + textAlign: TextAlign.start, + style: TextStyle( + color: CustomTheme.textColor, + fontSize: 12, + ), + softWrap: true, + ), + ], + ), + ), + + // Classifier selection + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: CustomDropdown( + closedHeaderPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + listItemBuilder: + (context, item, isSelected, onItemSelect) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + translateStatisticTypeToString(item, context), + style: itemStyle, + ), + if (isSelected) + const Icon( + Icons.check, + color: CustomTheme.textColor, + ), + ], + ), + headerBuilder: (context, selectedType, enabled) => Text( + translateStatisticTypeToString(selectedType, context), + style: headerStyle, + ), + hintText: loc.select_a_classifier, + items: StatisticType.values, + decoration: decoration, + onChanged: (value) { + setState(() { + selectedType = value; + }); + }, + ), + ), + + const SizedBox(height: 10), + + // Scope title + Padding( + padding: const EdgeInsetsGeometry.only(left: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.scope, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const Text( + 'description', + textAlign: TextAlign.start, + style: TextStyle( + color: CustomTheme.textColor, + fontSize: 12, + ), + ), + ], + ), + ), + + // Scope selection + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: CustomDropdown.multiSelect( + closedHeaderPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + hintText: loc.select_a_scope, + items: StatisticScope.values, + decoration: decoration, + listItemBuilder: + (context, scope, isSelected, onItemSelect) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + translateScopeToString(scope, context), + style: itemStyle, + ), + if (isSelected) + const Icon( + Icons.check, + color: CustomTheme.textColor, + ), + ], + ), + headerListBuilder: (context, selectedItems, enabled) => + Text( + selectedItems + .map((s) => translateScopeToString(s, context)) + .join(', '), + style: headerStyle, + overflow: TextOverflow.ellipsis, + ), + onListChanged: (List values) { + setState(() { + selectedScope = values; + }); + }, + ), + ), + + if (selectedScope.contains(StatisticScope.selectedGames)) ...[ + const SizedBox(height: 10), + + // games title + Padding( + padding: const EdgeInsetsGeometry.only(left: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.games, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + loc.select_the_filtered_games, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 12, + ), + ), + ], + ), + ), + + // game selection + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: CustomDropdown.multiSelect( + enabled: !isLoading, + disabledDecoration: disabledDecoration, + closedHeaderPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + hintText: isLoading ? loc.loading : loc.select_a_game, + items: games, + decoration: decoration, + listItemBuilder: + (context, item, isSelected, onItemSelect) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Name + Text(item.name, style: itemStyle), + const SizedBox(width: 12), + + // Ruleset + Text( + translateRulesetToString( + item.ruleset, + context, + ), + style: hintStyle.copyWith(fontSize: 12), + ), + ], + ), + + // Check icon + if (isSelected) + const Icon( + Icons.check, + color: CustomTheme.textColor, + ), + ], + ), + headerListBuilder: (context, selectedItems, enabled) => + Text( + selectedItems.map((g) => g.name).join(', '), + style: const TextStyle( + color: CustomTheme.textColor, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + onListChanged: (List values) { + setState(() { + selectedGames = values; + }); + }, + ), + ), + ], + + if (selectedScope.contains( + StatisticScope.selectedGroups, + )) ...[ + const SizedBox(height: 10), + + // groups title + Padding( + padding: const EdgeInsetsGeometry.only(left: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.groups, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + loc.select_the_filtered_groups, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 12, + ), + ), + ], + ), + ), + + // groups selection + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: CustomDropdown.multiSelect( + enabled: !isLoading, + disabledDecoration: disabledDecoration, + closedHeaderPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + hintText: isLoading ? loc.loading : loc.select_a_group, + items: groups, + decoration: decoration, + listItemBuilder: + (context, item, isSelected, onItemSelect) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Name + Text(item.name, style: itemStyle), + const SizedBox(width: 12), + + // Ruleset + Text( + ' ${item.members.length.toString()} ${loc.members}', + style: hintStyle.copyWith(fontSize: 12), + ), + ], + ), + if (isSelected) + const Icon( + Icons.check, + color: CustomTheme.textColor, + ), + ], + ), + headerListBuilder: (context, selectedItems, enabled) => + Text( + selectedItems.map((g) => g.name).join(', '), + style: headerStyle, + overflow: TextOverflow.ellipsis, + ), + onListChanged: (List groups) { + setState(() { + selectedGroups = groups; + }); + }, + ), + ), + ], + + if (selectedScope.contains(StatisticScope.timeframe)) ...[ + const SizedBox(height: 10), + + // timeframe title + Padding( + padding: const EdgeInsetsGeometry.only(left: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + loc.timeframe, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + loc.select_the_filtered_timeframe, + textAlign: TextAlign.start, + style: const TextStyle( + color: CustomTheme.textColor, + fontSize: 12, + ), + ), + ], + ), + ), + + // groups selection + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: CustomDropdown( + enabled: !isLoading, + excludeSelected: false, + disabledDecoration: disabledDecoration, + closedHeaderPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + hintText: isLoading + ? loc.loading + : loc.select_a_timeframe, + items: Timeframe.values, + decoration: decoration, + listItemBuilder: + (context, timeframe, isSelected, onItemSelect) => + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + translateTimeframeToString( + timeframe, + context, + ), + style: itemStyle, + ), + if (isSelected) + const Icon( + Icons.check, + color: CustomTheme.textColor, + ), + ], + ), + headerBuilder: (context, selectedTimeframe, enabled) => + Text( + translateTimeframeToString( + selectedTimeframe, + context, + ), + style: headerStyle, + overflow: TextOverflow.ellipsis, + ), + onChanged: (Timeframe? timeframe) { + setState(() { + selectedTimeframe = timeframe; + }); + }, + ), + ), + ], + ], + ), + ), + + // Create statistic button + Positioned( + bottom: MediaQuery.of(context).padding.bottom, + child: AnimatedDialogButton( + buttonConstraints: const BoxConstraints(minWidth: 350), + buttonText: loc.create_statistic, + onPressed: selectedType != null && selectedScope.isNotEmpty + ? () => submitStatistic() + : null, + ), + ), + ], + ), + ), + ); + } + + CustomDropdownDecoration get decoration => CustomDropdownDecoration( + listItemDecoration: const ListItemDecoration( + selectedIconBorder: BorderSide(color: CustomTheme.primaryColor, width: 1), + selectedIconColor: CustomTheme.primaryColor, + highlightColor: CustomTheme.secondaryColor, + splashColor: Colors.transparent, + selectedColor: CustomTheme.onBoxColor, + ), + listItemStyle: itemStyle, + headerStyle: headerStyle, + hintStyle: hintStyle, + closedFillColor: CustomTheme.boxColor, + closedBorder: Border.all(color: CustomTheme.boxBorderColor, width: 1), + expandedFillColor: CustomTheme.boxColor, + expandedBorder: Border.all(color: CustomTheme.boxBorderColor, width: 1), + ); + + CustomDropdownDisabledDecoration get disabledDecoration => + CustomDropdownDisabledDecoration( + fillColor: CustomTheme.boxColor.withAlpha(125), + border: Border.all( + color: CustomTheme.boxBorderColor.withAlpha(125), + width: 1, + ), + headerStyle: disabledHeaderStyle, + hintStyle: disabledHintStyle, + ); + + TextStyle get headerStyle => const TextStyle( + color: CustomTheme.textColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ); + + TextStyle get itemStyle => + const TextStyle(color: CustomTheme.textColor, fontSize: 14); + + TextStyle get hintStyle => + const TextStyle(color: CustomTheme.hintColor, fontSize: 14); + + TextStyle get disabledHeaderStyle => const TextStyle( + color: CustomTheme.hintColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ); + + TextStyle get disabledHintStyle => + const TextStyle(color: CustomTheme.hintColor, fontSize: 14); + + Future loadAllData() async { + isLoading = true; + final db = Provider.of(context, listen: false); + + Future.wait([ + db.playerDao.getAllPlayers(), + db.groupDao.getAllGroups(), + db.gameDao.getAllGames(), + Future.delayed(Constants.MINIMUM_SKELETON_DURATION), + ]) + .then((results) async { + players = results[0]; + groups = results[1]; + games = results[2]; + isLoading = false; + }) + .catchError((error) { + print('Error loading data: $error'); + }); + } + + void submitStatistic() { + final newStatistic = Statistic( + type: selectedType!, + scopes: selectedScope, + timeframe: selectedTimeframe, + selectedGroups: selectedGroups, + selectedGames: selectedGames, + ); + final db = Provider.of(context, listen: false); + db.statisticDao.addStatistic(statistic: newStatistic); + Navigator.of(context).pop(newStatistic); + } +} + +String translateTimeframeToString(Timeframe timeframe, BuildContext context) { + final loc = AppLocalizations.of(context); + switch (timeframe) { + case Timeframe.last7Days: + return loc.last_7_days; + case Timeframe.last30Days: + return loc.last_30_days; + case Timeframe.last90Days: + return loc.last_90_days; + case Timeframe.last180Days: + return loc.last_180_days; + case Timeframe.lastYear: + return loc.last_year; + case Timeframe.allTime: + return loc.all_time; + } +} + +String translateScopeToString(StatisticScope scope, BuildContext context) { + final loc = AppLocalizations.of(context); + switch (scope) { + case StatisticScope.allPlayers: + return loc.all_players; + case StatisticScope.selectedGroups: + return loc.selected_groups; + case StatisticScope.selectedGames: + return loc.selected_games; + case StatisticScope.timeframe: + return loc.timeframe; + } +} + +String translateStatisticTypeToString( + StatisticType type, + BuildContext context, +) { + final loc = AppLocalizations.of(context); + switch (type) { + case StatisticType.totalMatches: + return loc.total_matches; + case StatisticType.totalWins: + return loc.total_wins; + case StatisticType.totalScore: + return loc.total_score; + case StatisticType.totalLosses: + return loc.total_losses; + case StatisticType.averageScore: + return loc.average_score; + case StatisticType.bestScore: + return loc.best_score; + case StatisticType.worstScore: + return loc.worst_score; + case StatisticType.winrate: + return loc.winrate; + } +} diff --git a/lib/presentation/views/main_menu/statistics_view/statistic_detail_view.dart b/lib/presentation/views/main_menu/statistics_view/statistic_detail_view.dart new file mode 100644 index 0000000..f1bc209 --- /dev/null +++ b/lib/presentation/views/main_menu/statistics_view/statistic_detail_view.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/statistic.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/create_statistic_view.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; +import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart'; + +class StatisticDetailView extends StatefulWidget { + const StatisticDetailView({ + super.key, + required this.statistic, + required this.values, + required this.icon, + required this.barColor, + }); + + final Statistic statistic; + final List<(Player, num)> values; + final IconData icon; + final Color barColor; + + @override + State createState() => _StatisticDetailViewState(); +} + +class _StatisticDetailViewState extends State { + late int displayCount; + + @override + void initState() { + super.initState(); + displayCount = widget.statistic.displayCount; + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final title = translateStatisticTypeToString( + widget.statistic.type, + context, + ); + const style = TextStyle(fontWeight: FontWeight.bold); + + return Scaffold( + appBar: AppBar( + title: Text(title), + leading: HapticIconButton( + icon: const Icon(Icons.arrow_back_ios_new), + onPressed: () => handleBack(context), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + StatisticsTile( + icon: widget.icon, + title: title, + width: MediaQuery.sizeOf(context).width * 0.95, + values: widget.values, + barColor: widget.barColor, + selectedGroups: widget.statistic.selectedGroups, + selectedGames: widget.statistic.selectedGames, + displayCount: displayCount, + showAllValues: true, + ), + const SizedBox(height: 12), + + InfoTile( + icon: Icons.filter_alt, + title: loc.filter, + content: Column( + spacing: 12, + + children: [ + // Scopes + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(loc.scope, style: style), + Text( + widget.statistic.scopes + .map( + (scope) => translateScopeToString(scope, context), + ) + .join('\n'), + textAlign: TextAlign.end, + ), + ], + ), + + // Timeframe + if (widget.statistic.timeframe != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(loc.timeframe, style: style), + Text( + translateTimeframeToString( + widget.statistic.timeframe!, + context, + ), + textAlign: TextAlign.end, + ), + ], + ), + + // Groups + if (widget.statistic.selectedGroups != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(loc.groups, style: style), + Text( + widget.statistic.selectedGroups! + .map((group) => group.name) + .join('\n'), + textAlign: TextAlign.end, + ), + ], + ), + + // Games + if (widget.statistic.selectedGames != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(loc.games, style: style), + Text( + widget.statistic.selectedGames! + .map((game) => game.name) + .join('\n'), + textAlign: TextAlign.end, + ), + ], + ), + + if (widget.values.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(loc.displayed_entries, style: style), + Row( + children: [ + HapticIconButton( + icon: const Icon(Icons.remove), + onPressed: displayCount <= 1 + ? null + : () => setState(() => displayCount -= 1), + ), + SizedBox( + width: 30, + child: Text( + '$displayCount', + textAlign: TextAlign.center, + ), + ), + HapticIconButton( + icon: const Icon(Icons.add), + onPressed: displayCount >= widget.values.length + ? null + : () => setState(() => displayCount += 1), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + // Handles saving the display count and giving it to statistics view + Future handleBack(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.statisticDao.updateDisplayCount(widget.statistic.id, displayCount); + if (context.mounted) Navigator.of(context).pop(displayCount); + } +} diff --git a/lib/presentation/views/main_menu/statistics_view/statistic_tile_factory.dart b/lib/presentation/views/main_menu/statistics_view/statistic_tile_factory.dart new file mode 100644 index 0000000..807358e --- /dev/null +++ b/lib/presentation/views/main_menu/statistics_view/statistic_tile_factory.dart @@ -0,0 +1,322 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:tallee/core/common.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/statistic.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/create_statistic_view.dart' + show translateStatisticTypeToString; +import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart'; + +List _colorPalette = AppColor.values + .map((c) => getColorFromAppColor(c)) + .toList(); + +/// Returns the icon for the given statistic type. +IconData getStatisticIconForType(StatisticType type) => + _getStatisticIcon(type: type); + +/// Returns a color from the palette based on the statistic's ID. +Color getStatisticColorForStatistic(Statistic stat) => _getStatisticColor(stat); + +/// Computes the statistic values for a given [Statistic]. +List<(Player, num)> computeStatisticValues({ + required Statistic statistic, + required List matches, + required List players, +}) { + final filteredMatches = _getFilterMatches(statistic, matches); + final filteredPlayers = _getFilteredPlayers( + statistic, + players, + filteredMatches, + ); + + return _computeValuesForType( + type: statistic.type, + matches: filteredMatches, + players: filteredPlayers, + ); +} + +/// Build the [StatisticsTile] for a given [Statistic]. +Widget buildStatisticTile({ + required Statistic statistic, + required List matches, + required List players, + required BuildContext context, + double? width, +}) { + final values = computeStatisticValues( + statistic: statistic, + matches: matches, + players: players, + ); + + return StatisticsTile( + icon: _getStatisticIcon(type: statistic.type), + title: translateStatisticTypeToString(statistic.type, context), + width: width ?? MediaQuery.sizeOf(context).width * 0.95, + values: values, + barColor: _getStatisticColor(statistic), + displayCount: statistic.displayCount, + selectedGroups: statistic.selectedGroups, + selectedGames: statistic.selectedGames, + ); +} + +List _getFilterMatches(Statistic statistic, List matches) { + List filteredMatches = matches; + + // Filter timeframe + if (statistic.scopes.contains(StatisticScope.timeframe) && + statistic.timeframe != null) { + final minDate = _getMinimumDate(timeframe: statistic.timeframe!); + print( + 'Filtering matches by timeframe: ${statistic.timeframe}, minDate: $minDate', + ); + if (minDate != null) { + filteredMatches = matches + .where((m) => m.endedAt != null && m.endedAt!.isAfter(minDate)) + .toList(); + } + } + + // Filter games + if (statistic.scopes.contains(StatisticScope.selectedGames) && + (statistic.selectedGames?.isNotEmpty ?? false)) { + final gameIds = statistic.selectedGames!.map((g) => g.id).toSet(); + filteredMatches = filteredMatches + .where((match) => gameIds.contains(match.game.id)) + .toList(); + } + + // Filter groups + if (statistic.scopes.contains(StatisticScope.selectedGroups) && + (statistic.selectedGroups?.isNotEmpty ?? false)) { + final groupIds = statistic.selectedGroups!.map((g) => g.id).toSet(); + filteredMatches = filteredMatches + .where((m) => m.group != null && groupIds.contains(m.group!.id)) + .toList(); + } + + return filteredMatches; +} + +/// Returns a [Player] List with the selected players depending on +List _getFilteredPlayers( + Statistic statistic, + List allPlayers, + List filteredMatches, +) { + // allPlayers + if (statistic.scopes.contains(StatisticScope.allPlayers)) { + return allPlayers; + } + + // selectedGroups -> only members + if (statistic.scopes.contains(StatisticScope.selectedGroups) && + (statistic.selectedGroups?.isNotEmpty ?? false)) { + final Set ids = { + for (final g in statistic.selectedGroups!) + for (final p in g.members) p.id, + }; + return allPlayers.where((p) => ids.contains(p.id)).toList(); + } + + // Else -> all players from filtered matches + final Set ids = { + for (final m in filteredMatches) + for (final p in m.players) p.id, + }; + return allPlayers.where((p) => ids.contains(p.id)).toList(); +} + +/// Returns a [DateTime] with the minimum time and date the [timeframe] allows +DateTime? _getMinimumDate({required Timeframe timeframe}) { + final now = DateTime.now(); + switch (timeframe) { + case Timeframe.last7Days: + return now.subtract(const Duration(days: 7)); + case Timeframe.last30Days: + return now.subtract(const Duration(days: 30)); + case Timeframe.last90Days: + return now.subtract(const Duration(days: 90)); + case Timeframe.last180Days: + return now.subtract(const Duration(days: 180)); + case Timeframe.lastYear: + return now.subtract(const Duration(days: 365)); + case Timeframe.allTime: + return null; + } +} + +/// Computes the statistic values for each player based on the statistic type +/// and returns a list of (Player, value) tuples sorted descending by value. +List<(Player, num)> _computeValuesForType({ + required StatisticType type, + required List matches, + required List players, +}) { + switch (type) { + case StatisticType.totalMatches: + return _sortDesc( + players.map((p) => (p, _matchesPlayed(p, matches) as num)).toList(), + ); + + case StatisticType.totalWins: + return _sortDesc( + players.map((p) => (p, _wins(p, matches) as num)).toList(), + ); + + case StatisticType.totalLosses: + return _sortDesc( + players + .map( + (p) => + (p, (_matchesPlayed(p, matches) - _wins(p, matches)) as num), + ) + .toList(), + ); + + case StatisticType.totalScore: + return _sortDesc( + players.map((p) => (p, _totalScore(p, matches) as num)).toList(), + ); + + case StatisticType.averageScore: + return _sortDesc( + players.map((p) { + final scores = _scoresOf(p, matches); + final avg = scores.isEmpty + ? 0.0 + : double.parse( + (scores.reduce((a, b) => a + b) / scores.length) + .toStringAsFixed(2), + ); + return (p, avg as num); + }).toList(), + ); + + case StatisticType.bestScore: + return _sortDesc( + players.map((p) { + final scores = _scoresOf(p, matches); + final best = scores.isEmpty ? 0 : scores.reduce(max); + return (p, best as num); + }).toList(), + ); + + case StatisticType.worstScore: + // Ascending here is more meaningful for "worst", but keep the + // existing tile semantics (largest bar = top entry) by sorting + // descending on the inverse — i.e. show smallest score on top. + final entries = players.map((p) { + final scores = _scoresOf(p, matches); + final worst = scores.isEmpty ? 0 : scores.reduce(min); + return (p, worst as num); + }).toList(); + entries.sort((a, b) => a.$2.compareTo(b.$2)); + return entries; + + case StatisticType.winrate: + return _sortDesc( + players.map((p) { + final played = _matchesPlayed(p, matches); + final wins = _wins(p, matches); + final rate = played == 0 + ? 0.0 + : double.parse((wins / played).toStringAsFixed(2)); + return (p, rate as num); + }).toList(), + ); + } +} + +/* Helper functions for different statistic types */ + +/// Detemerines how many matches the player has played in the given list of matches. +int _matchesPlayed(Player p, List matches) => + matches.where((m) => m.players.any((mp) => mp.id == p.id)).length; + +/// Determines how many matches the player has won in the given list of matches. +int _wins(Player p, List matches) => + matches.where((m) => m.mvp.any((mp) => mp.id == p.id)).length; + +/// Determines the total score of the player in the given list of matches. +int _totalScore(Player p, List matches) { + var total = 0; + for (final m in matches) { + final s = m.scores[p.id]; + if (s != null) total += s.score; + } + return total; +} + +/// Returns a list of all scores the player has achieved in the given list of matches. +List _scoresOf(Player p, List matches) => [ + for (final m in matches) + if (m.scores[p.id] != null) m.scores[p.id]!.score, +]; + +/// Returns the list of entries sorted descending by the statistic value. +List<(Player, num)> _sortDesc(List<(Player, num)> entries) { + entries.sort((a, b) => b.$2.compareTo(a.$2)); + return entries; +} + +/* Icon and color */ + +/// Returns the icon for the given statistic type. +IconData _getStatisticIcon({required StatisticType type}) { + switch (type) { + case StatisticType.totalMatches: + return Icons.casino; + case StatisticType.totalWins: + return Icons.emoji_events; + case StatisticType.totalLosses: + return Icons.sentiment_dissatisfied; + case StatisticType.totalScore: + return Icons.scoreboard; + case StatisticType.averageScore: + return Icons.show_chart; + case StatisticType.bestScore: + return Icons.trending_up; + case StatisticType.worstScore: + return Icons.trending_down; + case StatisticType.winrate: + return Icons.percent; + } +} + +/// Returns a color from the palette based on the statistic's ID as random seed. +Color _getStatisticColor(Statistic stat) { + final seed = stat.id.hashCode; + return _colorPalette[seed.abs() % _colorPalette.length]; +} + +/* Skeleton data */ + +/// A placeholder tile with mock data for the loading state. +Widget buildSkeletonStatisticTile({required BuildContext context}) { + final count = 4 + Random().nextInt(5); // 4..8 + final values = <(Player, num)>[ + for (var i = 0; i < count; i++) + (Player(name: 'Player ${i + 1}'), count - i), + ]; + + return StatisticsTile( + icon: Icons.bar_chart, + title: 'Skeleton title', + width: MediaQuery.sizeOf(context).width * 0.95, + values: values, + barColor: _colorPalette[Random().nextInt(_colorPalette.length)], + selectedGames: [Game(name: 'Game 1', ruleset: Ruleset.highestScore)], + selectedGroups: [Group(name: 'Group 1', members: [])], + displayCount: 5, + ); +} diff --git a/lib/presentation/views/main_menu/statistics_view/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view/statistics_view.dart new file mode 100644 index 0000000..3381267 --- /dev/null +++ b/lib/presentation/views/main_menu/statistics_view/statistics_view.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/statistic.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/create_statistic_view.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/statistic_detail_view.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view/statistic_tile_factory.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; + +class StatisticsView extends StatefulWidget { + /// A view that displays player statistics + const StatisticsView({super.key}); + + @override + State createState() => _StatisticsViewState(); +} + +class _StatisticsViewState extends State { + bool isLoading = true; + List _allMatches = const []; + List _allPlayers = const []; + List _statistics = const []; + List statisticTiles = []; + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + loadStatistics(context); + }); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Stack( + alignment: AlignmentDirectional.bottomCenter, + fit: StackFit.expand, + children: [ + Visibility( + visible: statisticTiles.isNotEmpty, + replacement: Center( + child: TopCenteredMessage( + icon: Icons.info, + title: loc.info, + message: loc.no_statistics_created_yet, + ), + ), + child: SingleChildScrollView( + child: AppSkeleton( + enabled: isLoading, + fixLayoutBuilder: true, + child: Column( + spacing: 12, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ...statisticTiles, + SizedBox( + height: MediaQuery.paddingOf(context).bottom + 80, + ), + ], + ), + ), + ), + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom + 20, + child: MainMenuButton( + text: loc.create_statistic, + icon: Icons.bar_chart, + onPressed: () async { + Statistic newStatistic = await Navigator.push( + context, + adaptivePageRoute( + builder: (context) => CreateStatisticView( + onStatisticCreated: () => loadStatistics(context), + ), + ), + ); + if (!context.mounted) return; + setState(() { + _statistics = [..._statistics, newStatistic]; + statisticTiles = _statistics + .map((stat) => _buildStatisticTile(context, stat)) + .toList(); + }); + }, + ), + ), + ], + ); + }, + ); + } + + Future loadStatistics(BuildContext context) async { + setState(() { + isLoading = true; + statisticTiles = List.generate( + 4, + (index) => Column( + children: [ + buildSkeletonStatisticTile(context: context), + const SizedBox(height: 12), + ], + ), + ); + }); + + final db = Provider.of(context, listen: false); + + final results = await Future.wait([ + db.statisticDao.getAllStatistics(), + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(Constants.MINIMUM_SKELETON_DURATION), + ]); + + if (!mounted) return; + + final statistics = results[0] as List; + _allMatches = results[1] as List; + _allPlayers = results[2] as List; + _statistics = statistics; + + setState(() { + statisticTiles = _statistics + .map((stat) => _buildStatisticTile(context, stat)) + .toList(); + isLoading = false; + }); + } + + Widget _buildStatisticTile(BuildContext context, Statistic statistic) { + final values = computeStatisticValues( + statistic: statistic, + matches: _allMatches, + players: _allPlayers, + ); + + return GestureDetector( + onTap: () async { + final newDisplayCount = await Navigator.push( + context, + adaptivePageRoute( + builder: (context) => StatisticDetailView( + statistic: statistic, + values: values, + icon: getStatisticIconForType(statistic.type), + barColor: getStatisticColorForStatistic(statistic), + ), + ), + ); + if (newDisplayCount != null && + newDisplayCount != statistic.displayCount) { + setState(() { + _statistics = _statistics + .map( + (stat) => stat.id == statistic.id + ? stat.copyWith(displayCount: newDisplayCount) + : stat, + ) + .toList(); + statisticTiles = _statistics + .map((stat) => _buildStatisticTile(context, stat)) + .toList(); + }); + } + }, + child: buildStatisticTile( + statistic: statistic, + matches: _allMatches, + players: _allPlayers, + context: context, + ), + ); + } +} diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart index 8a21320..abdfb8d 100644 --- a/lib/presentation/widgets/app_skeleton.dart +++ b/lib/presentation/widgets/app_skeleton.dart @@ -6,11 +6,13 @@ class AppSkeleton extends StatefulWidget { /// - [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. + /// - [alignment]: The alignment used for the custom layout builder and optional Align wrapper. Defaults to [Alignment.center]. const AppSkeleton({ super.key, required this.child, this.enabled = true, this.fixLayoutBuilder = false, + this.alignment = Alignment.center, }); /// The widget tree to apply the skeleton effect to. @@ -22,6 +24,9 @@ class AppSkeleton extends StatefulWidget { /// A boolean to fix the layout builder for AnimatedSwitcher. final bool fixLayoutBuilder; + /// The alignment used for the custom layout builder and optional Align wrapper + final Alignment alignment; + @override State createState() => _AppSkeletonState(); } @@ -45,13 +50,14 @@ class _AppSkeletonState extends State { layoutBuilder: !widget.fixLayoutBuilder ? AnimatedSwitcher.defaultLayoutBuilder : (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [...previousChildren, ?currentChild], - ); + final children = [...previousChildren]; + if (currentChild != null) children.add(currentChild); + return Stack(alignment: widget.alignment, children: children); }, ), - child: widget.child, + child: widget.fixLayoutBuilder + ? Align(alignment: widget.alignment, child: widget.child) + : widget.child, ); } } diff --git a/lib/presentation/widgets/buttons/animated_dialog_button.dart b/lib/presentation/widgets/buttons/animated_dialog_button.dart index 8c8765e..5062b23 100644 --- a/lib/presentation/widgets/buttons/animated_dialog_button.dart +++ b/lib/presentation/widgets/buttons/animated_dialog_button.dart @@ -11,7 +11,7 @@ class AnimatedDialogButton extends StatefulWidget { const AnimatedDialogButton({ super.key, required this.buttonText, - required this.onPressed, + this.onPressed, this.buttonConstraints, this.buttonType = ButtonType.primary, this.isDescructive = false, @@ -19,7 +19,7 @@ class AnimatedDialogButton extends StatefulWidget { final String buttonText; - final VoidCallback onPressed; + final VoidCallback? onPressed; final BoxConstraints? buttonConstraints; @@ -38,28 +38,38 @@ class _AnimatedDialogButtonState extends State { Widget build(BuildContext context) { final textStyling = _getTextStyling(); final buttonDecoration = _getButtonDecoration(); + bool isDisabled = widget.onPressed == null; - return GestureDetector( - onTapDown: (_) => setState(() => _isPressed = true), - onTapUp: (_) => setState(() => _isPressed = false), - onTapCancel: () => setState(() => _isPressed = false), - onTap: widget.onPressed, - child: AnimatedScale( - scale: _isPressed ? 0.95 : 1.0, - duration: const Duration(milliseconds: 100), - child: AnimatedOpacity( - opacity: _isPressed ? 0.6 : 1.0, - duration: const Duration(milliseconds: 100), - child: Center( - child: Container( - constraints: widget.buttonConstraints, - decoration: buttonDecoration, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - margin: const EdgeInsets.symmetric(vertical: 8), - child: Text( - widget.buttonText, - style: textStyling, - textAlign: TextAlign.center, + return IgnorePointer( + ignoring: isDisabled, + child: Opacity( + opacity: isDisabled ? 0.5 : 1.0, + child: GestureDetector( + onTapDown: (_) => setState(() => _isPressed = true), + onTapUp: (_) => setState(() => _isPressed = false), + onTapCancel: () => setState(() => _isPressed = false), + onTap: widget.onPressed, + child: AnimatedScale( + scale: _isPressed ? 0.95 : 1.0, + duration: const Duration(milliseconds: 100), + child: AnimatedOpacity( + opacity: _isPressed ? 0.6 : 1.0, + duration: const Duration(milliseconds: 100), + child: Center( + child: Container( + constraints: widget.buttonConstraints, + decoration: buttonDecoration, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + margin: const EdgeInsets.symmetric(vertical: 8), + child: Text( + widget.buttonText, + style: textStyling, + textAlign: TextAlign.center, + ), + ), ), ), ), diff --git a/lib/presentation/widgets/dialog/custom_alert_dialog.dart b/lib/presentation/widgets/dialog/custom_alert_dialog.dart index 606fc49..7dfba98 100644 --- a/lib/presentation/widgets/dialog/custom_alert_dialog.dart +++ b/lib/presentation/widgets/dialog/custom_alert_dialog.dart @@ -19,7 +19,6 @@ class CustomAlertDialog extends StatelessWidget { final String title; final Widget content; final List actions; - @override Widget build(BuildContext context) { return AlertDialog( diff --git a/lib/presentation/widgets/dialog/custom_dialog_action.dart b/lib/presentation/widgets/dialog/custom_dialog_action.dart index 26dc40d..0c0b2e0 100644 --- a/lib/presentation/widgets/dialog/custom_dialog_action.dart +++ b/lib/presentation/widgets/dialog/custom_dialog_action.dart @@ -10,7 +10,7 @@ class CustomDialogAction extends StatelessWidget { /// - [onPressed]: Callback function that is triggered when the button is pressed. const CustomDialogAction({ super.key, - required this.onPressed, + this.onPressed, required this.text, this.buttonType = ButtonType.primary, this.isDestructive = false, @@ -20,17 +20,18 @@ class CustomDialogAction extends StatelessWidget { final ButtonType buttonType; - final VoidCallback onPressed; + final VoidCallback? onPressed; final bool isDestructive; - @override Widget build(BuildContext context) { return AnimatedDialogButton( - onPressed: () async { - await HapticFeedback.selectionClick(); - onPressed.call(); - }, + onPressed: onPressed != null + ? () async { + await HapticFeedback.selectionClick(); + onPressed?.call(); + } + : null, buttonText: text, buttonType: buttonType, isDescructive: isDestructive, diff --git a/lib/presentation/widgets/game_label.dart b/lib/presentation/widgets/game_label.dart index 553e637..2e6bf74 100644 --- a/lib/presentation/widgets/game_label.dart +++ b/lib/presentation/widgets/game_label.dart @@ -12,41 +12,44 @@ class GameLabel extends StatelessWidget { final String title; final String description; - final GameColor color; + final AppColor color; @override Widget build(BuildContext context) { - final backgroundColor = getColorFromGameColor(color); + final backgroundColor = getColorFromAppColor(color); final fontColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; - return IntrinsicHeight( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Title - Container( - decoration: BoxDecoration( - color: backgroundColor.withAlpha(230), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), - ), - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Text( - title, - style: TextStyle( - fontSize: 12, - color: fontColor, - fontWeight: FontWeight.bold, - ), + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Title + Container( + decoration: BoxDecoration( + color: backgroundColor.withAlpha(230), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), ), ), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: TextStyle( + fontSize: 12, + color: fontColor, + fontWeight: FontWeight.bold, + ), + ), + ), - // Description - Container( + // Description + Flexible( + child: Container( decoration: BoxDecoration( color: backgroundColor.withAlpha(140), borderRadius: const BorderRadius.only( @@ -57,6 +60,9 @@ class GameLabel extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: Text( description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, style: TextStyle( fontSize: 12, color: fontColor, @@ -64,8 +70,8 @@ class GameLabel extends StatelessWidget { ), ), ), - ], - ), + ), + ], ); } } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 00d6c11..6a62c95 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -26,6 +26,7 @@ class PlayerSelection extends StatefulWidget { this.availablePlayers, this.initialSelectedPlayers, required this.onChanged, + this.onPlayerCreated, }); /// An optional list of players to choose from. If null, all players from the database are used. @@ -37,6 +38,9 @@ class PlayerSelection extends StatefulWidget { /// A callback function that is invoked whenever the selection changes, final Function(List value) onChanged; + /// A callback function that is invoked when a player was created in this widget + final VoidCallback? onPlayerCreated; + @override State createState() => _PlayerSelectionState(); } @@ -323,6 +327,7 @@ class _PlayerSelectionState extends State { /// Updates the state after successfully adding a new player. void _handleSuccessfulPlayerCreation(Player player) { + widget.onPlayerCreated?.call(); selectedPlayers.insert(0, player); widget.onChanged([...selectedPlayers]); allPlayers.add(player); diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart index ee5acf0..4fb12d1 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/game_tile.dart @@ -51,7 +51,7 @@ class GameTile extends StatelessWidget { ? (badgeColor!.computeLuminance() > 0.5 ? Colors.black : Colors.white) : Colors.white; - final gameColor = badgeColor ?? getColorFromGameColor(GameColor.orange); + final gameColor = badgeColor ?? getColorFromAppColor(AppColor.orange); return GestureDetector( onTap: () async { diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index f6c406e..c01dcbe 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/models/group.dart'; +import 'package:tallee/presentation/views/main_menu/player_detail_view.dart'; import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatefulWidget { @@ -15,6 +17,7 @@ class GroupTile extends StatefulWidget { required this.group, this.isHighlighted = false, this.onTap, + this.onPlayerChanged, }); /// The group data to be displayed. @@ -26,6 +29,9 @@ class GroupTile extends StatefulWidget { /// Callback function to be executed when the tile is tapped. final VoidCallback? onTap; + /// Callback function to be executed when the players in the group are changed. + final VoidCallback? onPlayerChanged; + @override State createState() => _GroupTileState(); } @@ -92,6 +98,19 @@ class _GroupTileState extends State { text: member.name, suffixText: getNameCountText(member), iconEnabled: false, + onTileTap: () { + Navigator.push( + context, + adaptivePageRoute( + builder: (context) => PlayerDetailView( + player: member, + callback: () { + widget.onPlayerChanged?.call(); + }, + ), + ), + ); + }, ), ], ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 6a81dc3..c3d0242 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -3,11 +3,13 @@ import 'dart:core' hide Match; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/models/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/player_detail_view.dart'; import 'package:tallee/presentation/widgets/game_label.dart'; import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; @@ -24,6 +26,7 @@ class MatchTile extends StatefulWidget { required this.onTap, this.width, this.compact = false, + this.onPlayerEdited, }); /// The match data to be displayed. @@ -32,6 +35,9 @@ class MatchTile extends StatefulWidget { /// The callback invoked when the tile is tapped. final VoidCallback onTap; + /// The callback invoked when the players are edited + final VoidCallback? onPlayerEdited; + /// Optional width for the tile. final double? width; @@ -224,6 +230,19 @@ class _MatchTileState extends State { text: player.name, suffixText: getNameCountText(player), iconEnabled: false, + onTileTap: () { + Navigator.push( + context, + adaptivePageRoute( + builder: (context) => PlayerDetailView( + player: player, + callback: () { + widget.onPlayerEdited?.call(); + }, + ), + ), + ); + }, ); }).toList(), ), diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index ea9cb49..02cdb75 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -1,8 +1,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:fluttericon/rpg_awesome_icons.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; @@ -21,8 +25,11 @@ class StatisticsTile extends StatelessWidget { required this.title, required this.width, required this.values, - required this.itemCount, required this.barColor, + required this.displayCount, + this.selectedGroups, + this.selectedGames, + this.showAllValues = false, }); /// The icon displayed next to the title. @@ -37,12 +44,16 @@ class StatisticsTile extends StatelessWidget { /// A list of tuples containing labels and their corresponding numeric values. final List<(Player, num)> values; - /// The maximum number of items to display. - final int itemCount; - /// The color of the bars representing the values. final Color barColor; + // statistic data + final int displayCount; + final List? selectedGroups; + final List? selectedGames; + + final bool showAllValues; + @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -52,91 +63,202 @@ class StatisticsTile extends StatelessWidget { title: title, icon: icon, content: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Visibility( visible: values.isNotEmpty, + + // No data avaiable message replacement: Center( heightFactor: 4, child: Text(loc.no_data_available), ), + + // Bar chart child: LayoutBuilder( builder: (context, constraints) { - final maxBarWidth = constraints.maxWidth * 0.65; + final maxBarWidth = constraints.maxWidth * 0.8; + + // If displayCount wasnt provided, take all values + final valuesShown = showAllValues + ? values.length + : min(values.length, displayCount); + final displayValues = values.take(valuesShown).toList(); + final maxVal = displayValues.isNotEmpty + ? displayValues.fold( + 0, + (currentMax, entry) => + entry.$2 > currentMax ? entry.$2 : currentMax, + ) + : 0; + return Column( - children: List.generate(min(values.length, itemCount), (index) { - /// The maximum wins among all players - final maxMatches = values.isNotEmpty ? values[0].$2 : 0; + children: [ + // Bars + ...List.generate(valuesShown, (index) { + /// Fraction of wins + final double fraction = (maxVal > 0) + ? (displayValues[index].$2 / maxVal) + : 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).clamp( + 0.0, + maxBarWidth, + ); - /// Calculated width for current the bar - final double barWidth = maxBarWidth * fraction; + final barClr = index >= displayCount + ? barColor.withAlpha(150) + : barColor; - 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: RichText( - overflow: TextOverflow.ellipsis, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: values[index].$1.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: getNameCountText(values[index].$1), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: CustomTheme.textColor.withAlpha( - 150, - ), - ), - ), - ], + var textClr = barColor.computeLuminance() > 0.5 + ? const Color(0xFF101010) + : CustomTheme.textColor; + textClr = textClr.withAlpha( + index >= displayCount ? 220 : 255, + ); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: maxBarWidth, + child: Stack( + clipBehavior: Clip.hardEdge, + children: [ + // Bar + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barClr, + ), ), - ), - ), - ], - ), - 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, + + // Player + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: RichText( + maxLines: 1, + softWrap: false, + overflow: TextOverflow.ellipsis, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: displayValues[index].$1.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: textClr, + ), + ), + TextSpan( + text: getNameCountText( + displayValues[index].$1, + ), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: + (barColor == + getColorFromAppColor( + AppColor.yellow, + ) + ? const Color( + 0xFF101010, + ) + : CustomTheme.textColor) + .withAlpha(150), + ), + ), + ], + ), + ), + ), + ], ), ), - ), - ], + const Spacer(), + + // Value + Center( + child: Text( + displayValues[index].$2 <= 1 && + displayValues[index].$2 is double + ? displayValues[index].$2.toStringAsFixed(2) + : displayValues[index].$2.toString(), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + }), + + // Group & Game info + if (hasGame || hasGroup) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 4, + children: [ + // Game + if (hasGroup) + Row( + spacing: 8, + children: [ + const Icon( + RpgAwesome.clovers_card, + color: CustomTheme.hintColor, + size: 20, + ), + Text( + getGameText(selectedGames!), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: CustomTheme.hintColor, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + if (hasGroup && hasGame) const SizedBox(width: 20), + + // Group + if (hasGroup) + Row( + spacing: 8, + children: [ + const Icon( + Icons.groups, + color: CustomTheme.hintColor, + ), + Text( + getGroupText(selectedGroups!), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: CustomTheme.hintColor, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ], + ), ), - ); - }), + ], ); }, ), @@ -144,4 +266,24 @@ class StatisticsTile extends StatelessWidget { ), ); } + + String getGroupText(List groups) { + var text = groups[0].name; + if (groups.length > 1) { + return '$text + ${groups.length - 1}'; + } + return text; + } + + String getGameText(List games) { + var text = games[0].name; + if (games.length > 1) { + return '$text + ${games.length - 1}'; + } + return text; + } + + bool get hasGroup => selectedGroups != null && selectedGroups!.isNotEmpty; + + bool get hasGame => selectedGames != null && selectedGames!.isNotEmpty; } diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 541b6ae..a5ef399 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -12,6 +12,7 @@ class TextIconTile extends StatelessWidget { this.suffixText = '', this.iconEnabled = true, this.onIconTap, + this.onTileTap, }); /// The text to display in the tile. @@ -25,52 +26,58 @@ class TextIconTile extends StatelessWidget { /// The callback to be invoked when the icon is tapped. final VoidCallback? onIconTap; + /// The callback to be invoked when the tile is tapped. + final VoidCallback? onTileTap; + @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 (iconEnabled) const SizedBox(width: 3), - Flexible( - child: RichText( - overflow: TextOverflow.ellipsis, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: text, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, + return GestureDetector( + onTap: onTileTap, + child: 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 (iconEnabled) const SizedBox(width: 3), + Flexible( + child: RichText( + overflow: TextOverflow.ellipsis, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: text, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), - ), - TextSpan( - text: suffixText, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: CustomTheme.textColor.withAlpha(120), + TextSpan( + text: suffixText, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: CustomTheme.textColor.withAlpha(120), + ), ), - ), - ], + ], + ), ), ), - ), - if (iconEnabled) ...[ - const SizedBox(width: 3), - GestureDetector( - onTap: onIconTap, - child: const Icon(Icons.close, size: 20), - ), + if (iconEnabled) ...[ + const SizedBox(width: 3), + GestureDetector( + onTap: onIconTap, + child: const Icon(Icons.close, size: 20), + ), + ], ], - ], + ), ), ); } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 29199f8..9dbf955 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -20,6 +20,7 @@ class DataTransferService { static Future deleteAllData(BuildContext context) async { final db = Provider.of(context, listen: false); + await db.statisticDao.deleteAllStatistics(); await db.matchDao.deleteAllMatches(); await db.teamDao.deleteAllTeams(); await db.groupDao.deleteAllGroups(); @@ -278,7 +279,7 @@ class DataTransferService { name: 'Unknown', ruleset: Ruleset.singleWinner, description: '', - color: GameColor.blue, + color: AppColor.blue, icon: '', ); } diff --git a/pubspec.lock b/pubspec.lock index 2ec2197..fc30e57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.0.1" + animated_custom_dropdown: + dependency: "direct main" + description: + name: animated_custom_dropdown + sha256: "5a72dc209041bb53f6c7164bc2e366552d5197cdb032b1c9b2c36e3013024486" + url: "https://pub.dev" + source: hosted + version: "3.1.1" arb_utils: dependency: "direct dev" description: @@ -85,10 +93,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" url: "https://pub.dev" source: hosted - version: "2.15.0" + version: "2.13.1" built_collection: dependency: transitive description: @@ -177,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" collection: dependency: "direct main" description: @@ -325,26 +341,34 @@ packages: dependency: "direct main" description: name: drift - sha256: "8033500116b24398fba0cca0369cc31678cd627c01e41753a61186911cea743e" + sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e" url: "https://pub.dev" source: hosted - version: "2.33.0" + version: "2.31.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: b3dd5b75e30522a91da8abda9f5bb17230cb038097f6d15fa75d42bb563428aa + sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587" url: "https://pub.dev" source: hosted - version: "2.33.0" + version: "2.31.0" drift_flutter: dependency: "direct main" description: name: drift_flutter - sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166" + sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.2.8" + dropdown_flutter: + dependency: "direct main" + description: + name: dropdown_flutter + sha256: "5ae3d05d768d0bb6030ff735e6b4b93f7b29be3cf3bec7c86cd4f444c8f067ff" + url: "https://pub.dev" + source: hosted + version: "1.0.3" equatable: dependency: transitive description: @@ -389,10 +413,10 @@ packages: dependency: "direct main" description: name: file_saver - sha256: "68c9a085d9bb4546e0a31d1e583a48d7c17a6987d538788ea064f0043b1fc02d" + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.3.1" fixnum: dependency: transitive description: @@ -1114,38 +1138,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" - sqlcipher_flutter_libs: - dependency: transitive - description: - name: sqlcipher_flutter_libs - sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929" - url: "https://pub.dev" - source: hosted - version: "0.7.0+eol" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5" + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "2.9.4" sqlite3_flutter_libs: dependency: transitive description: name: sqlite3_flutter_libs - sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454" + sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad url: "https://pub.dev" source: hosted - version: "0.6.0+eol" + version: "0.5.42" sqlparser: dependency: transitive description: name: sqlparser - sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978 + sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19" url: "https://pub.dev" source: hosted - version: "0.44.4" + version: "0.43.1" stack_trace: dependency: transitive description: @@ -1427,5 +1443,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.12.0 <4.0.0" - flutter: ">=3.41.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 7906d03..183335c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,16 @@ name: tallee description: "Tracking App for Card Games" publish_to: 'none' -version: 0.0.35+277 +version: 0.0.33+281 environment: sdk: ^3.12.0 dependencies: + animated_custom_dropdown: ^3.1.1 clock: ^1.1.2 collection: ^1.19.1 + dropdown_flutter: ^1.0.3 cupertino_icons: ^1.0.9 drift: ^2.33.0 drift_flutter: ^0.3.0 diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index 1498523..3ea625f 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -194,6 +194,31 @@ void main() { expect(allGroups, isEmpty); }); + test('getGroupsByPlayer() works correctly', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup2], + ); + + final groups = await database.groupDao.getGroupsByPlayer( + playerId: testPlayer2.id, + ); + + expect(groups, hasLength(2)); + expect(groups.any((group) => group.id == testGroup1.id), isTrue); + expect(groups.any((group) => group.id == testGroup2.id), isTrue); + }); + + test( + 'getGroupsByPlayer() returns empty list for non-existent player', + () async { + final groups = await database.groupDao.getGroupsByPlayer( + playerId: 'non-existent-player-id', + ); + + expect(groups, isEmpty); + }, + ); + test('addGroupsAsList() with duplicate groups only adds once', () async { await database.groupDao.addGroupsAsList( groups: [testGroup1, testGroup1, testGroup1], diff --git a/test/db_tests/aggregates/match_test.dart b/test/db_tests/aggregates/match_test.dart index 37c1cd0..1144a73 100644 --- a/test/db_tests/aggregates/match_test.dart +++ b/test/db_tests/aggregates/match_test.dart @@ -56,7 +56,7 @@ void main() { name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', - color: GameColor.blue, + color: AppColor.blue, icon: '', ); testMatch1 = Match( @@ -260,6 +260,34 @@ void main() { expect(match.group!.id, testGroup1.id); }); + test('getMatchesByPlayer() works correctly', () async { + await database.matchDao.addMatchesAsList( + matches: [testMatch1, testMatch2], + ); + + final matches = await database.matchDao.getMatchesByPlayer( + playerId: testPlayer1.id, + ); + + expect(matches, hasLength(1)); + expect(matches.first.id, testMatch2.id); + expect( + matches.first.players.any((p) => p.id == testPlayer1.id), + isTrue, + ); + }); + + test( + 'getMatchesByPlayer() returns empty list for non-existent player', + () async { + final matches = await database.matchDao.getMatchesByPlayer( + playerId: 'non-existing-player-id', + ); + + expect(matches, isEmpty); + }, + ); + test('getMatchCount() works correctly', () async { var count = await database.matchDao.getMatchCount(); expect(count, 0); diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart index fefdcc5..381d22b 100644 --- a/test/db_tests/aggregates/team_test.dart +++ b/test/db_tests/aggregates/team_test.dart @@ -49,7 +49,7 @@ void main() { testGame = Game( name: 'Test Game', ruleset: Ruleset.highestScore, - color: GameColor.blue, + color: AppColor.blue, icon: '', ); testMatch1 = Match( diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index 778d43b..f7e7dcd 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -28,7 +28,7 @@ void main() { name: 'Chess', ruleset: Ruleset.singleWinner, description: 'A classic strategy game', - color: GameColor.blue, + color: AppColor.blue, icon: 'chess_icon', ); testGame2 = Game( @@ -36,7 +36,7 @@ void main() { name: 'Poker', ruleset: Ruleset.multipleWinners, description: 'Card game with multiple winners', - color: GameColor.red, + color: AppColor.red, icon: 'poker_icon', ); testGame3 = Game( @@ -44,7 +44,7 @@ void main() { name: 'Monopoly', ruleset: Ruleset.highestScore, description: 'A board game about real estate', - color: GameColor.orange, + color: AppColor.orange, icon: '', ); }); @@ -124,7 +124,7 @@ void main() { name: 'Game\'s & "Special" ', ruleset: Ruleset.multipleWinners, description: 'Description with émojis 🎮🎲', - color: GameColor.purple, + color: AppColor.purple, icon: '', ); await database.gameDao.addGame(game: specialGame); @@ -280,19 +280,19 @@ void main() { await database.gameDao.updateGameColor( gameId: testGame1.id, - color: GameColor.green, + color: AppColor.green, ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); - expect(updatedGame.color, GameColor.green); + expect(updatedGame.color, AppColor.green); }); test('updateGameColor() does nothing for non-existent game', () async { final updated = await database.gameDao.updateGameColor( gameId: 'non-existent-id', - color: GameColor.green, + color: AppColor.green, ); expect(updated, isFalse); @@ -336,7 +336,7 @@ void main() { name: newName, ); - const newGameColor = GameColor.teal; + const newGameColor = AppColor.teal; await database.gameDao.updateGameColor( gameId: testGame1.id, color: newGameColor, diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart index bfcced4..4105df1 100644 --- a/test/db_tests/entities/player_test.dart +++ b/test/db_tests/entities/player_test.dart @@ -233,6 +233,95 @@ void main() { expect(allPlayers, isEmpty); }); + test('updatePlayerName() sets correct nameCount with 2 player', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); + + final newName = testPlayer1.name; + await database.playerDao.updatePlayerName( + playerId: testPlayer2.id, + name: newName, + ); + + var player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.nameCount, 1); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer2.id, + ); + expect(player.nameCount, 2); + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + name: 'different name', + ); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.nameCount, 0); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer2.id, + ); + expect(player.nameCount, 0); + }); + + test('updatePlayerName() sets correct nameCount with 3 player', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3], + ); + + // Changing both names to player 1's name + final newName = testPlayer1.name; + await database.playerDao.updatePlayerName( + playerId: testPlayer2.id, + name: newName, + ); + await database.playerDao.updatePlayerName( + playerId: testPlayer3.id, + name: newName, + ); + + var player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.nameCount, 1); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer2.id, + ); + expect(player.nameCount, 2); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer3.id, + ); + expect(player.nameCount, 3); + + // Changing the middle players name + await database.playerDao.updatePlayerName( + playerId: testPlayer2.id, + name: 'different name', + ); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.nameCount, 1); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer2.id, + ); + expect(player.nameCount, 0); + + player = await database.playerDao.getPlayerById( + playerId: testPlayer3.id, + ); + expect(player.nameCount, 2); + }); + test('updatePlayerDescription() works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); @@ -372,14 +461,22 @@ void main() { final player1 = Player(name: testPlayer1.name, description: ''); await database.playerDao.addPlayer(player: player1); + final player2 = Player(name: testPlayer1.name, description: ''); + await database.playerDao.addPlayer(player: player2); + var players = await database.playerDao.getAllPlayers(); - expect(players.length, 2); + expect(players.length, 3); players.sort((a, b) => a.nameCount.compareTo(b.nameCount)); for (int i = 0; i < players.length - 1; i++) { expect(players[i].nameCount, i + 1); } + + // ids are correct in the right order + expect(players[0].id, testPlayer1.id); + expect(players[1].id, player1.id); + expect(players[2].id, player2.id); }, ); @@ -404,24 +501,62 @@ void main() { for (int i = 0; i < players.length - 1; i++) { expect(players[i].nameCount, i + 1); } + + // ids are correct in the right order + expect(players[0].id, testPlayer1.id); + expect(players[1].id, player1.id); + expect(players[2].id, player2.id); + expect(players[3].id, player3.id); }, ); - test('getNameCount works correctly', () async { + test('getNameCount works correctly', () async { + final player1 = Player(name: testPlayer1.name); final player2 = Player(name: testPlayer1.name); - final player3 = Player(name: testPlayer1.name); - await database.playerDao.addPlayersAsList( - players: [testPlayer1, player2, player3], + await database.playerDao.addPlayer(player: testPlayer1); + + var nameCount = await database.playerDao.getNameCount( + name: testPlayer1.name, ); - final nameCount = await database.playerDao.getNameCount( + expect(nameCount, 1); + + await database.playerDao.addPlayersAsList(players: [player1, player2]); + + nameCount = await database.playerDao.getNameCount( name: testPlayer1.name, ); expect(nameCount, 3); }); + test('calculateNameCount works correctly', () async { + final player1 = Player(name: testPlayer1.name); + final player2 = Player(name: testPlayer1.name); + + // Case 1: No existing players with the name + var nameCount = await database.playerDao.calculateNameCount( + name: testPlayer1.name, + ); + expect(nameCount, 0); + + // Case 2: One existing player with the name. Should return 2 for + // the new player + await database.playerDao.addPlayer(player: testPlayer1); + nameCount = await database.playerDao.calculateNameCount( + name: testPlayer1.name, + ); + expect(nameCount, 2); + + // Case 3: Multiple existing players with the name. Should return count + 1 + await database.playerDao.addPlayersAsList(players: [player1, player2]); + nameCount = await database.playerDao.calculateNameCount( + name: testPlayer1.name, + ); + expect(nameCount, 4); + }); + test('updateNameCount works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); @@ -441,14 +576,24 @@ void main() { final player2 = Player(name: testPlayer1.name, description: ''); final player3 = Player(name: testPlayer1.name, description: ''); - await database.playerDao.addPlayersAsList( - players: [testPlayer1, player2, player3], - ); - - final player = await database.playerDao.getPlayerWithHighestNameCount( + await database.playerDao.addPlayer(player: testPlayer1); + var player = await database.playerDao.getPlayerWithHighestNameCount( name: testPlayer1.name, ); + expect(player, isNotNull); + expect(player!.nameCount, 0); + await database.playerDao.addPlayer(player: player2); + player = await database.playerDao.getPlayerWithHighestNameCount( + name: testPlayer1.name, + ); + expect(player, isNotNull); + expect(player!.nameCount, 2); + + await database.playerDao.addPlayer(player: player3); + player = await database.playerDao.getPlayerWithHighestNameCount( + name: testPlayer1.name, + ); expect(player, isNotNull); expect(player!.nameCount, 3); }); @@ -460,32 +605,6 @@ void main() { expect(player, isNull); }); - test('calculateNameCount works correctly', () async { - // Case 1: No existing players with the name - var count = await database.playerDao.calculateNameCount( - name: testPlayer1.name, - ); - expect(count, 0); - - // Case 2: One existing player with the name. Should update that - // player's nameCount to 1 and return 2 for the new player - await database.playerDao.addPlayer(player: testPlayer1); - - count = await database.playerDao.calculateNameCount( - name: testPlayer1.name, - ); - expect(count, 2); - - // Case 3: Multiple existing players with the name. - final player2 = Player(name: testPlayer1.name, nameCount: count); - await database.playerDao.addPlayer(player: player2); - - count = await database.playerDao.calculateNameCount( - name: testPlayer1.name, - ); - expect(count, 3); - }); - test('getPlayerWithHighestNameCount with non existing player', () async { await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.initializeNameCount(name: testPlayer1.name); diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart index 6d879c3..fa7ec21 100644 --- a/test/db_tests/relationships/player_match_test.dart +++ b/test/db_tests/relationships/player_match_test.dart @@ -42,7 +42,7 @@ void main() { name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', - color: GameColor.blue, + color: AppColor.blue, icon: '', ); testMatch1 = Match( diff --git a/test/db_tests/statistics/statistic_test.dart b/test/db_tests/statistics/statistic_test.dart new file mode 100644 index 0000000..bd590c7 --- /dev/null +++ b/test/db_tests/statistics/statistic_test.dart @@ -0,0 +1,122 @@ +import 'dart:core'; + +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNotNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/statistic.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Group testGroup1; + late Group testGroup2; + late Game testGame; + /*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); + + setUp(() async { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + 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 1', + description: '', + members: [testPlayer1, testPlayer2, testPlayer3], + ); + testGroup2 = Group( + name: 'Test Group 2', + description: '', + members: [testPlayer4, testPlayer5], + ); + testGame = Game( + name: 'Test Game', + ruleset: Ruleset.singleWinner, + description: 'A test game', + color: AppColor.blue, + icon: '', + ); + /*testMatch1 = Match( + name: 'First Test Match', + game: testGame, + group: testGroup1, + players: [testPlayer4, testPlayer5], + scores: {testPlayer4.id: ScoreEntry(score: 1)}, + ); + testMatch2 = Match( + name: 'Second Test Match', + game: testGame, + group: testGroup2, + players: [testPlayer1, testPlayer2, testPlayer3], + ); + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', + game: testGame, + players: [testPlayer1, testPlayer2, testPlayer3], + ); + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + game: testGame, + group: testGroup2, + players: testGroup2.members, + );*/ + }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + ], + ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); + await database.gameDao.addGame(game: testGame); + }); + tearDown(() async { + await database.close(); + }); + + test('Adding/fetching statistic works correclty', () async { + final statistic = Statistic( + type: StatisticType.averageScore, + scopes: [StatisticScope.allPlayers, StatisticScope.selectedGames], + timeframe: Timeframe.allTime, + selectedGames: [testGame], + selectedGroups: [testGroup1], + ); + + final added = await database.statisticDao.addStatistic( + statistic: statistic, + ); + expect(added, isTrue); + + final fetched = await database.statisticDao.getStatisticById(statistic.id); + expect(fetched, isNotNull); + expect(fetched!.type, statistic.type); + }); +} diff --git a/test/db_tests/values/score_entry_test.dart b/test/db_tests/values/score_entry_test.dart index f6cc292..593d194 100644 --- a/test/db_tests/values/score_entry_test.dart +++ b/test/db_tests/values/score_entry_test.dart @@ -40,7 +40,7 @@ void main() { name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', - color: GameColor.blue, + color: AppColor.blue, icon: '', ); testMatch1 = Match( diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index 586138a..94ed977 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -45,7 +45,7 @@ void main() { name: 'Chess', ruleset: Ruleset.singleWinner, description: 'Strategic board game', - color: GameColor.blue, + color: AppColor.blue, icon: 'chess_icon', ); @@ -448,19 +448,19 @@ void main() { Game( name: 'Red Game', ruleset: Ruleset.singleWinner, - color: GameColor.red, + color: AppColor.red, icon: 'icon', ), Game( name: 'Blue Game', ruleset: Ruleset.singleWinner, - color: GameColor.blue, + color: AppColor.blue, icon: 'icon', ), Game( name: 'Green Game', ruleset: Ruleset.singleWinner, - color: GameColor.green, + color: AppColor.green, icon: 'icon', ), ]; @@ -484,19 +484,19 @@ void main() { Game( name: 'Highest Score Game', ruleset: Ruleset.highestScore, - color: GameColor.blue, + color: AppColor.blue, icon: 'icon', ), Game( name: 'Lowest Score Game', ruleset: Ruleset.lowestScore, - color: GameColor.blue, + color: AppColor.blue, icon: 'icon', ), Game( name: 'Single Winner', ruleset: Ruleset.singleWinner, - color: GameColor.blue, + color: AppColor.blue, icon: 'icon', ), ];