Compare commits
55 Commits
developmen
...
feature/16
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c3c3c257 | |||
| 6d17539af2 | |||
| 3c9d115d08 | |||
| a1b2a1d722 | |||
| 25afe998f5 | |||
| 4b30673125 | |||
| 047c4e88d3 | |||
| cf7dbe60f4 | |||
| 47829a6955 | |||
| 96037e6062 | |||
| a0897d4966 | |||
| 6fb4a8996c | |||
| df64ef4b93 | |||
| a6deba4238 | |||
| a7d36787ce | |||
| 14eb77e241 | |||
| 46134a4f5c | |||
| 12b7bcdc6c | |||
| a803dc36d7 | |||
| a497ae872b | |||
| df8e060707 | |||
| 021a546479 | |||
| 32a8a6090a | |||
| f3f7d44994 | |||
| 60d746ede2 | |||
| 32d6eb1d18 | |||
| 042f44e8ef | |||
| e761fb1474 | |||
| 1f9ba96401 | |||
| 63f050b34f | |||
| aaeb4bf18c | |||
| 8b1a447bd9 | |||
| 0c40357847 | |||
| e61af14827 | |||
| 3493a74c6f | |||
| ec1182b560 | |||
| 4c5ce1aba0 | |||
| 369cabe996 | |||
| f069f62e26 | |||
| 44c474ae77 | |||
| 9c5e72e6ed | |||
| 9a8b93510e | |||
| 0812f18d77 | |||
| 0f621cd799 | |||
| 1356cbc92a | |||
| 9bce03d780 | |||
| 0a1e14a32d | |||
| 092dd5ec0a | |||
| 418a6307a0 | |||
| 07703037f2 | |||
| d9e0cdf506 | |||
| 2de8cef18d | |||
| 2f72b71fda | |||
| a957408c7e | |||
| badf5ea311 |
@@ -31,17 +31,21 @@ jobs:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: ghcr.io/cirruslabs/flutter:stable
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y nodejs npm
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory '*'
|
git config --global --add safe.directory '*'
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: ghcr.io/cirruslabs/flutter:stable
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y nodejs npm
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory '*'
|
git config --global --add safe.directory '*'
|
||||||
@@ -292,8 +295,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v3
|
uses: android-actions/setup-android@v3
|
||||||
with:
|
|
||||||
packages: "platform-tools platforms;android-34 build-tools;34.0.0"
|
|
||||||
|
|
||||||
# Required for Flutter action
|
# Required for Flutter action
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
|
|||||||
@@ -166,6 +166,9 @@
|
|||||||
"notes": {
|
"notes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"isTeamMatch": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"type": ["array", "null"]
|
"type": ["array", "null"]
|
||||||
}
|
}
|
||||||
@@ -177,7 +180,8 @@
|
|||||||
"createdAt",
|
"createdAt",
|
||||||
"gameId",
|
"gameId",
|
||||||
"playerIds",
|
"playerIds",
|
||||||
"notes"
|
"notes",
|
||||||
|
"isTeamMatch"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "csqlite",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/simolus3/CSQLite.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "1ee46d19a4f451a7aa64ffc64fc99b4748131e62"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "dkcamera",
|
"identity" : "dkcamera",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -24,47 +24,62 @@ String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates a [GameColor] enum value to its corresponding localized string.
|
// Returns a AppColor enum value based on the provided team [index].
|
||||||
String translateGameColorToString(GameColor color, BuildContext context) {
|
AppColor getTeamColor(int index) {
|
||||||
|
final colors = [
|
||||||
|
AppColor.red,
|
||||||
|
AppColor.blue,
|
||||||
|
AppColor.green,
|
||||||
|
AppColor.yellow,
|
||||||
|
AppColor.purple,
|
||||||
|
AppColor.orange,
|
||||||
|
AppColor.pink,
|
||||||
|
AppColor.teal,
|
||||||
|
];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translates a [AppColor] enum value to its corresponding localized string.
|
||||||
|
String translateGameColorToString(AppColor color, BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
switch (color) {
|
switch (color) {
|
||||||
case GameColor.red:
|
case AppColor.red:
|
||||||
return loc.color_red;
|
return loc.color_red;
|
||||||
case GameColor.blue:
|
case AppColor.blue:
|
||||||
return loc.color_blue;
|
return loc.color_blue;
|
||||||
case GameColor.green:
|
case AppColor.green:
|
||||||
return loc.color_green;
|
return loc.color_green;
|
||||||
case GameColor.yellow:
|
case AppColor.yellow:
|
||||||
return loc.color_yellow;
|
return loc.color_yellow;
|
||||||
case GameColor.purple:
|
case AppColor.purple:
|
||||||
return loc.color_purple;
|
return loc.color_purple;
|
||||||
case GameColor.orange:
|
case AppColor.orange:
|
||||||
return loc.color_orange;
|
return loc.color_orange;
|
||||||
case GameColor.pink:
|
case AppColor.pink:
|
||||||
return loc.color_pink;
|
return loc.color_pink;
|
||||||
case GameColor.teal:
|
case AppColor.teal:
|
||||||
return loc.color_teal;
|
return loc.color_teal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [Color] object corresponding to a [GameColor] enum value.
|
/// Returns the [Color] object corresponding to a [AppColor] enum value.
|
||||||
Color getColorFromGameColor(GameColor color) {
|
Color getColorFromGameColor(AppColor color) {
|
||||||
switch (color) {
|
switch (color) {
|
||||||
case GameColor.red:
|
case AppColor.red:
|
||||||
return Colors.red;
|
return Colors.red;
|
||||||
case GameColor.blue:
|
case AppColor.blue:
|
||||||
return Colors.blue;
|
return Colors.blue;
|
||||||
case GameColor.green:
|
case AppColor.green:
|
||||||
return Colors.green;
|
return Colors.green;
|
||||||
case GameColor.yellow:
|
case AppColor.yellow:
|
||||||
return const Color(0xFFF7CA28);
|
return const Color(0xFFF7CA28);
|
||||||
case GameColor.purple:
|
case AppColor.purple:
|
||||||
return Colors.purple;
|
return Colors.purple;
|
||||||
case GameColor.orange:
|
case AppColor.orange:
|
||||||
return const Color(0xFFef681f);
|
return const Color(0xFFef681f);
|
||||||
case GameColor.pink:
|
case AppColor.pink:
|
||||||
return Colors.pink;
|
return Colors.pink;
|
||||||
case GameColor.teal:
|
case AppColor.teal:
|
||||||
return Colors.teal;
|
return Colors.teal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,11 +92,10 @@ IconData getRulesetIcon(Ruleset ruleset) {
|
|||||||
case Ruleset.lowestScore:
|
case Ruleset.lowestScore:
|
||||||
return Icons.arrow_downward;
|
return Icons.arrow_downward;
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
return Icons.emoji_events;
|
return Icons.emoji_events;
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return Icons.sentiment_dissatisfied;
|
return Icons.sentiment_dissatisfied;
|
||||||
case Ruleset.multipleWinners:
|
|
||||||
return Icons.group;
|
|
||||||
case Ruleset.placement:
|
case Ruleset.placement:
|
||||||
return RpgAwesome.podium;
|
return RpgAwesome.podium;
|
||||||
}
|
}
|
||||||
@@ -113,6 +127,7 @@ String getExtraPlayerCount(Match match) {
|
|||||||
return ' + ${count.toString()}';
|
return ' + ${count.toString()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the player name count if greater 0 in the format " #2", otherwise an empty string
|
||||||
String getNameCountText(Player player) {
|
String getNameCountText(Player player) {
|
||||||
if (player.nameCount >= 1) {
|
if (player.nameCount >= 1) {
|
||||||
return ' #${player.nameCount}';
|
return ' #${player.nameCount}';
|
||||||
@@ -120,6 +135,7 @@ String getNameCountText(Player player) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the correct singular or plural form of "point(s)" based on the [points] value.
|
||||||
String getPointLabel(AppLocalizations loc, int points) {
|
String getPointLabel(AppLocalizations loc, int points) {
|
||||||
if (points == 1) {
|
if (points == 1) {
|
||||||
return '$points ${loc.point}';
|
return '$points ${loc.point}';
|
||||||
|
|||||||
@@ -65,7 +65,11 @@ class CustomTheme {
|
|||||||
|
|
||||||
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
||||||
color: boxColor,
|
color: boxColor,
|
||||||
border: Border.all(color: textColor, width: 2),
|
border: Border.all(
|
||||||
|
color: textColor,
|
||||||
|
width: 2,
|
||||||
|
strokeAlign: BorderSide.strokeAlignCenter,
|
||||||
|
),
|
||||||
borderRadius: standardBorderRadiusAll,
|
borderRadius: standardBorderRadiusAll,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -42,5 +42,5 @@ enum Ruleset {
|
|||||||
singleLoser,
|
singleLoser,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different colors for highlighting games
|
/// Different colors for highlighting content
|
||||||
enum GameColor { red, orange, yellow, green, teal, blue, purple, pink }
|
enum AppColor { red, orange, yellow, green, teal, blue, purple, pink }
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
|
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
|
||||||
description: row.description,
|
description: row.description,
|
||||||
color: GameColor.values.firstWhere((e) => e.name == row.color),
|
color: AppColor.values.firstWhere((e) => e.name == row.color),
|
||||||
icon: row.icon,
|
icon: row.icon,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
),
|
),
|
||||||
@@ -109,7 +109,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|||||||
name: result.name,
|
name: result.name,
|
||||||
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
|
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
|
||||||
description: result.description,
|
description: result.description,
|
||||||
color: GameColor.values.firstWhere((e) => e.name == result.color),
|
color: AppColor.values.firstWhere((e) => e.name == result.color),
|
||||||
icon: result.icon,
|
icon: result.icon,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
);
|
);
|
||||||
@@ -156,7 +156,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|||||||
/// Updates the color of the game with the given [gameId].
|
/// Updates the color of the game with the given [gameId].
|
||||||
Future<bool> updateGameColor({
|
Future<bool> updateGameColor({
|
||||||
required String gameId,
|
required String gameId,
|
||||||
required GameColor color,
|
required AppColor color,
|
||||||
}) async {
|
}) async {
|
||||||
final rowsAffected =
|
final rowsAffected =
|
||||||
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
gameId: match.game.id,
|
gameId: match.game.id,
|
||||||
groupId: Value(match.group?.id),
|
groupId: Value(match.group?.id),
|
||||||
name: match.name,
|
name: match.name,
|
||||||
|
isTeamMatch: Value(match.isTeamMatch),
|
||||||
notes: match.notes,
|
notes: match.notes,
|
||||||
createdAt: match.createdAt,
|
createdAt: match.createdAt,
|
||||||
endedAt: Value(match.endedAt),
|
endedAt: Value(match.endedAt),
|
||||||
@@ -142,6 +143,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
gameId: match.game.id,
|
gameId: match.game.id,
|
||||||
groupId: Value(match.group?.id),
|
groupId: Value(match.group?.id),
|
||||||
name: match.name,
|
name: match.name,
|
||||||
|
isTeamMatch: Value(match.isTeamMatch),
|
||||||
notes: match.notes,
|
notes: match.notes,
|
||||||
createdAt: match.createdAt,
|
createdAt: match.createdAt,
|
||||||
endedAt: Value(match.endedAt),
|
endedAt: Value(match.endedAt),
|
||||||
@@ -300,6 +302,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
group: group,
|
group: group,
|
||||||
players: players,
|
players: players,
|
||||||
teams: teams.isEmpty ? null : teams,
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: row.isTeamMatch,
|
||||||
notes: row.notes,
|
notes: row.notes,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
endedAt: row.endedAt,
|
endedAt: row.endedAt,
|
||||||
@@ -334,6 +337,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
group: group,
|
group: group,
|
||||||
players: players,
|
players: players,
|
||||||
teams: teams.isEmpty ? null : teams,
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: result.isTeamMatch,
|
||||||
notes: result.notes,
|
notes: result.notes,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
endedAt: result.endedAt,
|
endedAt: result.endedAt,
|
||||||
@@ -373,6 +377,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
group: group,
|
group: group,
|
||||||
players: players,
|
players: players,
|
||||||
teams: teams.isEmpty ? null : teams,
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: row.isTeamMatch,
|
||||||
notes: row.notes,
|
notes: row.notes,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
endedAt: row.endedAt,
|
endedAt: row.endedAt,
|
||||||
@@ -401,7 +406,8 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)),
|
teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return teams;
|
return teams
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update */
|
/* Update */
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||||
);
|
);
|
||||||
final players = await Future.wait(futures);
|
final players = await Future.wait(futures);
|
||||||
return players;
|
return players
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a list of [Player]s associated with a specific team in a match.
|
/// Retrieves a list of [Player]s associated with a specific team in a match.
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
/* Create */
|
/* Create */
|
||||||
|
|
||||||
/// Adds a score entry to the database.
|
/// Adds a score entry to the database.
|
||||||
Future<void> addScore({
|
Future<bool> addScore({
|
||||||
required String playerId,
|
required String playerId,
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required ScoreEntry entry,
|
required ScoreEntry entry,
|
||||||
}) async {
|
}) async {
|
||||||
await into(scoreEntryTable).insert(
|
final rowsAffected = await into(scoreEntryTable).insert(
|
||||||
ScoreEntryTableCompanion.insert(
|
ScoreEntryTableCompanion.insert(
|
||||||
playerId: playerId,
|
playerId: playerId,
|
||||||
matchId: matchId,
|
matchId: matchId,
|
||||||
@@ -31,6 +31,8 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addScoresAsList({
|
Future<void> addScoresAsList({
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/player_match_table.dart';
|
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||||
import 'package:tallee/data/db/tables/team_table.dart';
|
import 'package:tallee/data/db/tables/team_table.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
import 'package:tallee/data/models/team.dart';
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
|
||||||
part 'team_dao.g.dart';
|
part 'team_dao.g.dart';
|
||||||
@@ -22,6 +24,8 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
id: team.id,
|
id: team.id,
|
||||||
name: team.name,
|
name: team.name,
|
||||||
createdAt: team.createdAt,
|
createdAt: team.createdAt,
|
||||||
|
color: Value(team.color.name),
|
||||||
|
score: Value(team.score),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
@@ -56,6 +60,8 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
id: team.id,
|
id: team.id,
|
||||||
name: team.name,
|
name: team.name,
|
||||||
createdAt: team.createdAt,
|
createdAt: team.createdAt,
|
||||||
|
color: Value(team.color.name),
|
||||||
|
score: Value(team.score),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -110,12 +116,32 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
|
color: AppColor.values.byName(row.color),
|
||||||
|
score: row.score,
|
||||||
members: members,
|
members: members,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Team>> getTeamsByMatchId({required String matchId}) async {
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.matchId.equals(matchId));
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return [];
|
||||||
|
|
||||||
|
final teamIds = playerMatches
|
||||||
|
.map((pm) => pm.teamId)
|
||||||
|
.whereType<String>()
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
final teams = await Future.wait(
|
||||||
|
teamIds.map((id) => getTeamById(teamId: id)),
|
||||||
|
);
|
||||||
|
return teams;
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves a [Team] by its [teamId], including its members.
|
/// Retrieves a [Team] by its [teamId], including its members.
|
||||||
Future<Team> getTeamById({required String teamId}) async {
|
Future<Team> getTeamById({required String teamId}) async {
|
||||||
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
||||||
@@ -125,6 +151,8 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
id: result.id,
|
id: result.id,
|
||||||
name: result.name,
|
name: result.name,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
|
color: AppColor.values.byName(result.color),
|
||||||
|
score: result.score,
|
||||||
members: members,
|
members: members,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -145,7 +173,8 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
final players = await Future.wait(
|
final players = await Future.wait(
|
||||||
playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)),
|
playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)),
|
||||||
);
|
);
|
||||||
return players;
|
return players
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update */
|
/* Update */
|
||||||
@@ -162,6 +191,81 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the color of the team with the given [teamId].
|
||||||
|
Future<bool> updateTeamColor({
|
||||||
|
required String teamId,
|
||||||
|
required AppColor color,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
TeamTableCompanion(color: Value(color.name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the score of the team with the given [teamId].
|
||||||
|
/// Updates the member scores correspondingly
|
||||||
|
Future<bool> updateTeamScore({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
required int score,
|
||||||
|
}) async {
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
const TeamTableCompanion(score: Value(null)),
|
||||||
|
);
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId);
|
||||||
|
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
TeamTableCompanion(score: Value(score)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final members = await _getTeamMembers(teamId: teamId);
|
||||||
|
for (final member in members) {
|
||||||
|
await db.scoreEntryDao.addScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: matchId,
|
||||||
|
entry: ScoreEntry(score: score),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> removeScoreForTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
const TeamTableCompanion(score: Value(null)),
|
||||||
|
);
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the scores for all teams in the match with the given [matchId] by setting their scores to null.
|
||||||
|
Future<bool> removeAllTeamScores({required String matchId}) async {
|
||||||
|
// collect all teamIds for the given matchId from playerMatchTable
|
||||||
|
final teamIds =
|
||||||
|
await (selectOnly(playerMatchTable)
|
||||||
|
..addColumns([playerMatchTable.teamId])
|
||||||
|
..where(playerMatchTable.matchId.equals(matchId)))
|
||||||
|
.map((row) => row.read(playerMatchTable.teamId))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
// filter null or duplicates
|
||||||
|
final filteredTeamIds = teamIds.whereType<String>().toSet().toList();
|
||||||
|
|
||||||
|
var rowsAffected = 0;
|
||||||
|
if (filteredTeamIds.isNotEmpty) {
|
||||||
|
rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.isIn(filteredTeamIds)))
|
||||||
|
.write(const TeamTableCompanion(score: Value(null)));
|
||||||
|
}
|
||||||
|
await db.scoreEntryDao.deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Delete */
|
/* Delete */
|
||||||
|
|
||||||
/// Deletes all teams from the database.
|
/// Deletes all teams from the database.
|
||||||
@@ -179,4 +283,92 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
final rowsAffected = await query.go();
|
final rowsAffected = await query.go();
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Score handling */
|
||||||
|
|
||||||
|
/// Sets the team with the given [teamId] as the winner of the match with the given [matchId] by assigning a score of 1.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setWinnerTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets multiple teams as winners of the match with the given [matchId] by assigning a score of 1 to each team.
|
||||||
|
/// Returns `true` if all scores were updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setWinnerTeams({
|
||||||
|
required List<Team> winners,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
// Reset all team scores .
|
||||||
|
await removeAllTeamScores(matchId: matchId);
|
||||||
|
// Reset all score entries
|
||||||
|
for (final team in winners) {
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: team.id, matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final team in winners) {
|
||||||
|
await updateTeamScore(teamId: team.id, matchId: matchId, score: 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the winner status from all Teams with the given [matchId] by setting its score to null.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> removeWinnerTeam({required String matchId}) async {
|
||||||
|
return await removeAllTeamScores(matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the team with the given [teamId] as the loser of the match with the given [matchId] by assigning a score of 0.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setLoserTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the loser from the match with the given [matchId] by setting its score to null.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> removeLoserTeam({required String matchId}) async {
|
||||||
|
return await removeAllTeamScores(matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the placements for the teams in the match with the given [matchId] by assigning scores based on their order in the [teams] list.
|
||||||
|
/// Returns `true` if all scores were updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setTeamPlacements({
|
||||||
|
required String matchId,
|
||||||
|
required List<Team> teams,
|
||||||
|
}) async {
|
||||||
|
List<bool?> success = List.generate(teams.length, (index) => null);
|
||||||
|
for (int i = 0; i < teams.length; i++) {
|
||||||
|
success[i] = await updateTeamScore(
|
||||||
|
matchId: matchId,
|
||||||
|
teamId: teams[i].id,
|
||||||
|
score: teams.length - i,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return success.every((result) => result == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to delete all scores for members of a team in a specific match.
|
||||||
|
Future<bool> _deleteAllScoresForMembersOfTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.teamId.equals(teamId) & pm.matchId.equals(matchId));
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return false;
|
||||||
|
|
||||||
|
for (final pm in playerMatches) {
|
||||||
|
await db.scoreEntryDao.deleteAllScoresForPlayerInMatch(
|
||||||
|
playerId: pm.playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1185,6 +1185,21 @@ class $MatchTableTable extends MatchTable
|
|||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _isTeamMatchMeta = const VerificationMeta(
|
||||||
|
'isTeamMatch',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<bool> isTeamMatch = GeneratedColumn<bool>(
|
||||||
|
'is_team_match',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_team_match" IN (0, 1))',
|
||||||
|
),
|
||||||
|
defaultValue: const Constant(false),
|
||||||
|
);
|
||||||
static const VerificationMeta _notesMeta = const VerificationMeta('notes');
|
static const VerificationMeta _notesMeta = const VerificationMeta('notes');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<String> notes = GeneratedColumn<String>(
|
late final GeneratedColumn<String> notes = GeneratedColumn<String>(
|
||||||
@@ -1222,6 +1237,7 @@ class $MatchTableTable extends MatchTable
|
|||||||
gameId,
|
gameId,
|
||||||
groupId,
|
groupId,
|
||||||
name,
|
name,
|
||||||
|
isTeamMatch,
|
||||||
notes,
|
notes,
|
||||||
createdAt,
|
createdAt,
|
||||||
endedAt,
|
endedAt,
|
||||||
@@ -1265,6 +1281,15 @@ class $MatchTableTable extends MatchTable
|
|||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_nameMeta);
|
context.missing(_nameMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('is_team_match')) {
|
||||||
|
context.handle(
|
||||||
|
_isTeamMatchMeta,
|
||||||
|
isTeamMatch.isAcceptableOrUnknown(
|
||||||
|
data['is_team_match']!,
|
||||||
|
_isTeamMatchMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data.containsKey('notes')) {
|
if (data.containsKey('notes')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_notesMeta,
|
_notesMeta,
|
||||||
@@ -1312,6 +1337,10 @@ class $MatchTableTable extends MatchTable
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}name'],
|
data['${effectivePrefix}name'],
|
||||||
)!,
|
)!,
|
||||||
|
isTeamMatch: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.bool,
|
||||||
|
data['${effectivePrefix}is_team_match'],
|
||||||
|
)!,
|
||||||
notes: attachedDatabase.typeMapping.read(
|
notes: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}notes'],
|
data['${effectivePrefix}notes'],
|
||||||
@@ -1338,6 +1367,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
final String gameId;
|
final String gameId;
|
||||||
final String? groupId;
|
final String? groupId;
|
||||||
final String name;
|
final String name;
|
||||||
|
final bool isTeamMatch;
|
||||||
final String notes;
|
final String notes;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime? endedAt;
|
final DateTime? endedAt;
|
||||||
@@ -1346,6 +1376,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
required this.gameId,
|
required this.gameId,
|
||||||
this.groupId,
|
this.groupId,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
required this.isTeamMatch,
|
||||||
required this.notes,
|
required this.notes,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
this.endedAt,
|
this.endedAt,
|
||||||
@@ -1359,6 +1390,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
map['group_id'] = Variable<String>(groupId);
|
map['group_id'] = Variable<String>(groupId);
|
||||||
}
|
}
|
||||||
map['name'] = Variable<String>(name);
|
map['name'] = Variable<String>(name);
|
||||||
|
map['is_team_match'] = Variable<bool>(isTeamMatch);
|
||||||
map['notes'] = Variable<String>(notes);
|
map['notes'] = Variable<String>(notes);
|
||||||
map['created_at'] = Variable<DateTime>(createdAt);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
if (!nullToAbsent || endedAt != null) {
|
if (!nullToAbsent || endedAt != null) {
|
||||||
@@ -1375,6 +1407,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(groupId),
|
: Value(groupId),
|
||||||
name: Value(name),
|
name: Value(name),
|
||||||
|
isTeamMatch: Value(isTeamMatch),
|
||||||
notes: Value(notes),
|
notes: Value(notes),
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
endedAt: endedAt == null && nullToAbsent
|
endedAt: endedAt == null && nullToAbsent
|
||||||
@@ -1393,6 +1426,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
gameId: serializer.fromJson<String>(json['gameId']),
|
gameId: serializer.fromJson<String>(json['gameId']),
|
||||||
groupId: serializer.fromJson<String?>(json['groupId']),
|
groupId: serializer.fromJson<String?>(json['groupId']),
|
||||||
name: serializer.fromJson<String>(json['name']),
|
name: serializer.fromJson<String>(json['name']),
|
||||||
|
isTeamMatch: serializer.fromJson<bool>(json['isTeamMatch']),
|
||||||
notes: serializer.fromJson<String>(json['notes']),
|
notes: serializer.fromJson<String>(json['notes']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
endedAt: serializer.fromJson<DateTime?>(json['endedAt']),
|
endedAt: serializer.fromJson<DateTime?>(json['endedAt']),
|
||||||
@@ -1406,6 +1440,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
'gameId': serializer.toJson<String>(gameId),
|
'gameId': serializer.toJson<String>(gameId),
|
||||||
'groupId': serializer.toJson<String?>(groupId),
|
'groupId': serializer.toJson<String?>(groupId),
|
||||||
'name': serializer.toJson<String>(name),
|
'name': serializer.toJson<String>(name),
|
||||||
|
'isTeamMatch': serializer.toJson<bool>(isTeamMatch),
|
||||||
'notes': serializer.toJson<String>(notes),
|
'notes': serializer.toJson<String>(notes),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
'endedAt': serializer.toJson<DateTime?>(endedAt),
|
'endedAt': serializer.toJson<DateTime?>(endedAt),
|
||||||
@@ -1417,6 +1452,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
String? gameId,
|
String? gameId,
|
||||||
Value<String?> groupId = const Value.absent(),
|
Value<String?> groupId = const Value.absent(),
|
||||||
String? name,
|
String? name,
|
||||||
|
bool? isTeamMatch,
|
||||||
String? notes,
|
String? notes,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
Value<DateTime?> endedAt = const Value.absent(),
|
Value<DateTime?> endedAt = const Value.absent(),
|
||||||
@@ -1425,6 +1461,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
gameId: gameId ?? this.gameId,
|
gameId: gameId ?? this.gameId,
|
||||||
groupId: groupId.present ? groupId.value : this.groupId,
|
groupId: groupId.present ? groupId.value : this.groupId,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
|
isTeamMatch: isTeamMatch ?? this.isTeamMatch,
|
||||||
notes: notes ?? this.notes,
|
notes: notes ?? this.notes,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
endedAt: endedAt.present ? endedAt.value : this.endedAt,
|
endedAt: endedAt.present ? endedAt.value : this.endedAt,
|
||||||
@@ -1435,6 +1472,9 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
gameId: data.gameId.present ? data.gameId.value : this.gameId,
|
gameId: data.gameId.present ? data.gameId.value : this.gameId,
|
||||||
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
||||||
name: data.name.present ? data.name.value : this.name,
|
name: data.name.present ? data.name.value : this.name,
|
||||||
|
isTeamMatch: data.isTeamMatch.present
|
||||||
|
? data.isTeamMatch.value
|
||||||
|
: this.isTeamMatch,
|
||||||
notes: data.notes.present ? data.notes.value : this.notes,
|
notes: data.notes.present ? data.notes.value : this.notes,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
endedAt: data.endedAt.present ? data.endedAt.value : this.endedAt,
|
endedAt: data.endedAt.present ? data.endedAt.value : this.endedAt,
|
||||||
@@ -1448,6 +1488,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
..write('gameId: $gameId, ')
|
..write('gameId: $gameId, ')
|
||||||
..write('groupId: $groupId, ')
|
..write('groupId: $groupId, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
|
..write('isTeamMatch: $isTeamMatch, ')
|
||||||
..write('notes: $notes, ')
|
..write('notes: $notes, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('endedAt: $endedAt')
|
..write('endedAt: $endedAt')
|
||||||
@@ -1456,8 +1497,16 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(
|
||||||
Object.hash(id, gameId, groupId, name, notes, createdAt, endedAt);
|
id,
|
||||||
|
gameId,
|
||||||
|
groupId,
|
||||||
|
name,
|
||||||
|
isTeamMatch,
|
||||||
|
notes,
|
||||||
|
createdAt,
|
||||||
|
endedAt,
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@@ -1466,6 +1515,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
|
|||||||
other.gameId == this.gameId &&
|
other.gameId == this.gameId &&
|
||||||
other.groupId == this.groupId &&
|
other.groupId == this.groupId &&
|
||||||
other.name == this.name &&
|
other.name == this.name &&
|
||||||
|
other.isTeamMatch == this.isTeamMatch &&
|
||||||
other.notes == this.notes &&
|
other.notes == this.notes &&
|
||||||
other.createdAt == this.createdAt &&
|
other.createdAt == this.createdAt &&
|
||||||
other.endedAt == this.endedAt);
|
other.endedAt == this.endedAt);
|
||||||
@@ -1476,6 +1526,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
final Value<String> gameId;
|
final Value<String> gameId;
|
||||||
final Value<String?> groupId;
|
final Value<String?> groupId;
|
||||||
final Value<String> name;
|
final Value<String> name;
|
||||||
|
final Value<bool> isTeamMatch;
|
||||||
final Value<String> notes;
|
final Value<String> notes;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
final Value<DateTime?> endedAt;
|
final Value<DateTime?> endedAt;
|
||||||
@@ -1485,6 +1536,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
this.gameId = const Value.absent(),
|
this.gameId = const Value.absent(),
|
||||||
this.groupId = const Value.absent(),
|
this.groupId = const Value.absent(),
|
||||||
this.name = const Value.absent(),
|
this.name = const Value.absent(),
|
||||||
|
this.isTeamMatch = const Value.absent(),
|
||||||
this.notes = const Value.absent(),
|
this.notes = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.endedAt = const Value.absent(),
|
this.endedAt = const Value.absent(),
|
||||||
@@ -1495,6 +1547,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
required String gameId,
|
required String gameId,
|
||||||
this.groupId = const Value.absent(),
|
this.groupId = const Value.absent(),
|
||||||
required String name,
|
required String name,
|
||||||
|
this.isTeamMatch = const Value.absent(),
|
||||||
required String notes,
|
required String notes,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
this.endedAt = const Value.absent(),
|
this.endedAt = const Value.absent(),
|
||||||
@@ -1509,6 +1562,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
Expression<String>? gameId,
|
Expression<String>? gameId,
|
||||||
Expression<String>? groupId,
|
Expression<String>? groupId,
|
||||||
Expression<String>? name,
|
Expression<String>? name,
|
||||||
|
Expression<bool>? isTeamMatch,
|
||||||
Expression<String>? notes,
|
Expression<String>? notes,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
Expression<DateTime>? endedAt,
|
Expression<DateTime>? endedAt,
|
||||||
@@ -1519,6 +1573,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
if (gameId != null) 'game_id': gameId,
|
if (gameId != null) 'game_id': gameId,
|
||||||
if (groupId != null) 'group_id': groupId,
|
if (groupId != null) 'group_id': groupId,
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
|
if (isTeamMatch != null) 'is_team_match': isTeamMatch,
|
||||||
if (notes != null) 'notes': notes,
|
if (notes != null) 'notes': notes,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
if (endedAt != null) 'ended_at': endedAt,
|
if (endedAt != null) 'ended_at': endedAt,
|
||||||
@@ -1531,6 +1586,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
Value<String>? gameId,
|
Value<String>? gameId,
|
||||||
Value<String?>? groupId,
|
Value<String?>? groupId,
|
||||||
Value<String>? name,
|
Value<String>? name,
|
||||||
|
Value<bool>? isTeamMatch,
|
||||||
Value<String>? notes,
|
Value<String>? notes,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
Value<DateTime?>? endedAt,
|
Value<DateTime?>? endedAt,
|
||||||
@@ -1541,6 +1597,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
gameId: gameId ?? this.gameId,
|
gameId: gameId ?? this.gameId,
|
||||||
groupId: groupId ?? this.groupId,
|
groupId: groupId ?? this.groupId,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
|
isTeamMatch: isTeamMatch ?? this.isTeamMatch,
|
||||||
notes: notes ?? this.notes,
|
notes: notes ?? this.notes,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
endedAt: endedAt ?? this.endedAt,
|
endedAt: endedAt ?? this.endedAt,
|
||||||
@@ -1563,6 +1620,9 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
if (name.present) {
|
if (name.present) {
|
||||||
map['name'] = Variable<String>(name.value);
|
map['name'] = Variable<String>(name.value);
|
||||||
}
|
}
|
||||||
|
if (isTeamMatch.present) {
|
||||||
|
map['is_team_match'] = Variable<bool>(isTeamMatch.value);
|
||||||
|
}
|
||||||
if (notes.present) {
|
if (notes.present) {
|
||||||
map['notes'] = Variable<String>(notes.value);
|
map['notes'] = Variable<String>(notes.value);
|
||||||
}
|
}
|
||||||
@@ -1585,6 +1645,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
|
|||||||
..write('gameId: $gameId, ')
|
..write('gameId: $gameId, ')
|
||||||
..write('groupId: $groupId, ')
|
..write('groupId: $groupId, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
|
..write('isTeamMatch: $isTeamMatch, ')
|
||||||
..write('notes: $notes, ')
|
..write('notes: $notes, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('endedAt: $endedAt, ')
|
..write('endedAt: $endedAt, ')
|
||||||
@@ -1854,8 +1915,27 @@ class $TeamTableTable extends TeamTable
|
|||||||
type: DriftSqlType.dateTime,
|
type: DriftSqlType.dateTime,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _colorMeta = const VerificationMeta('color');
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [id, name, createdAt];
|
late final GeneratedColumn<String> color = GeneratedColumn<String>(
|
||||||
|
'color',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant('blue'),
|
||||||
|
);
|
||||||
|
static const VerificationMeta _scoreMeta = const VerificationMeta('score');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<int> score = GeneratedColumn<int>(
|
||||||
|
'score',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, name, createdAt, color, score];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
@@ -1889,6 +1969,18 @@ class $TeamTableTable extends TeamTable
|
|||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_createdAtMeta);
|
context.missing(_createdAtMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('color')) {
|
||||||
|
context.handle(
|
||||||
|
_colorMeta,
|
||||||
|
color.isAcceptableOrUnknown(data['color']!, _colorMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('score')) {
|
||||||
|
context.handle(
|
||||||
|
_scoreMeta,
|
||||||
|
score.isAcceptableOrUnknown(data['score']!, _scoreMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1910,6 +2002,14 @@ class $TeamTableTable extends TeamTable
|
|||||||
DriftSqlType.dateTime,
|
DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}created_at'],
|
data['${effectivePrefix}created_at'],
|
||||||
)!,
|
)!,
|
||||||
|
color: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}color'],
|
||||||
|
)!,
|
||||||
|
score: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}score'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1923,10 +2023,14 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
final String id;
|
final String id;
|
||||||
final String name;
|
final String name;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
|
final String color;
|
||||||
|
final int? score;
|
||||||
const TeamTableData({
|
const TeamTableData({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
|
required this.color,
|
||||||
|
this.score,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -1934,6 +2038,10 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
map['id'] = Variable<String>(id);
|
map['id'] = Variable<String>(id);
|
||||||
map['name'] = Variable<String>(name);
|
map['name'] = Variable<String>(name);
|
||||||
map['created_at'] = Variable<DateTime>(createdAt);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
map['color'] = Variable<String>(color);
|
||||||
|
if (!nullToAbsent || score != null) {
|
||||||
|
map['score'] = Variable<int>(score);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1942,6 +2050,10 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
id: Value(id),
|
id: Value(id),
|
||||||
name: Value(name),
|
name: Value(name),
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
|
color: Value(color),
|
||||||
|
score: score == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(score),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1954,6 +2066,8 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
name: serializer.fromJson<String>(json['name']),
|
name: serializer.fromJson<String>(json['name']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
color: serializer.fromJson<String>(json['color']),
|
||||||
|
score: serializer.fromJson<int?>(json['score']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -1963,20 +2077,31 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
'name': serializer.toJson<String>(name),
|
'name': serializer.toJson<String>(name),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
'color': serializer.toJson<String>(color),
|
||||||
|
'score': serializer.toJson<int?>(score),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamTableData copyWith({String? id, String? name, DateTime? createdAt}) =>
|
TeamTableData copyWith({
|
||||||
TeamTableData(
|
String? id,
|
||||||
id: id ?? this.id,
|
String? name,
|
||||||
name: name ?? this.name,
|
DateTime? createdAt,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
String? color,
|
||||||
);
|
Value<int?> score = const Value.absent(),
|
||||||
|
}) => TeamTableData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
color: color ?? this.color,
|
||||||
|
score: score.present ? score.value : this.score,
|
||||||
|
);
|
||||||
TeamTableData copyWithCompanion(TeamTableCompanion data) {
|
TeamTableData copyWithCompanion(TeamTableCompanion data) {
|
||||||
return TeamTableData(
|
return TeamTableData(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
name: data.name.present ? data.name.value : this.name,
|
name: data.name.present ? data.name.value : this.name,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
color: data.color.present ? data.color.value : this.color,
|
||||||
|
score: data.score.present ? data.score.value : this.score,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1985,37 +2110,47 @@ class TeamTableData extends DataClass implements Insertable<TeamTableData> {
|
|||||||
return (StringBuffer('TeamTableData(')
|
return (StringBuffer('TeamTableData(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('createdAt: $createdAt')
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('color: $color, ')
|
||||||
|
..write('score: $score')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(id, name, createdAt);
|
int get hashCode => Object.hash(id, name, createdAt, color, score);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is TeamTableData &&
|
(other is TeamTableData &&
|
||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
other.name == this.name &&
|
other.name == this.name &&
|
||||||
other.createdAt == this.createdAt);
|
other.createdAt == this.createdAt &&
|
||||||
|
other.color == this.color &&
|
||||||
|
other.score == this.score);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
||||||
final Value<String> id;
|
final Value<String> id;
|
||||||
final Value<String> name;
|
final Value<String> name;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
|
final Value<String> color;
|
||||||
|
final Value<int?> score;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const TeamTableCompanion({
|
const TeamTableCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
this.name = const Value.absent(),
|
this.name = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
|
this.color = const Value.absent(),
|
||||||
|
this.score = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
TeamTableCompanion.insert({
|
TeamTableCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
this.color = const Value.absent(),
|
||||||
|
this.score = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : id = Value(id),
|
}) : id = Value(id),
|
||||||
name = Value(name),
|
name = Value(name),
|
||||||
@@ -2024,12 +2159,16 @@ class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
|||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
Expression<String>? name,
|
Expression<String>? name,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
|
Expression<String>? color,
|
||||||
|
Expression<int>? score,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (color != null) 'color': color,
|
||||||
|
if (score != null) 'score': score,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2038,12 +2177,16 @@ class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
|||||||
Value<String>? id,
|
Value<String>? id,
|
||||||
Value<String>? name,
|
Value<String>? name,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
|
Value<String>? color,
|
||||||
|
Value<int?>? score,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return TeamTableCompanion(
|
return TeamTableCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
color: color ?? this.color,
|
||||||
|
score: score ?? this.score,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2060,6 +2203,12 @@ class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
|||||||
if (createdAt.present) {
|
if (createdAt.present) {
|
||||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
}
|
}
|
||||||
|
if (color.present) {
|
||||||
|
map['color'] = Variable<String>(color.value);
|
||||||
|
}
|
||||||
|
if (score.present) {
|
||||||
|
map['score'] = Variable<int>(score.value);
|
||||||
|
}
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@@ -2072,6 +2221,8 @@ class TeamTableCompanion extends UpdateCompanion<TeamTableData> {
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('color: $color, ')
|
||||||
|
..write('score: $score, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@@ -4092,6 +4243,7 @@ typedef $$MatchTableTableCreateCompanionBuilder =
|
|||||||
required String gameId,
|
required String gameId,
|
||||||
Value<String?> groupId,
|
Value<String?> groupId,
|
||||||
required String name,
|
required String name,
|
||||||
|
Value<bool> isTeamMatch,
|
||||||
required String notes,
|
required String notes,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
Value<DateTime?> endedAt,
|
Value<DateTime?> endedAt,
|
||||||
@@ -4103,6 +4255,7 @@ typedef $$MatchTableTableUpdateCompanionBuilder =
|
|||||||
Value<String> gameId,
|
Value<String> gameId,
|
||||||
Value<String?> groupId,
|
Value<String?> groupId,
|
||||||
Value<String> name,
|
Value<String> name,
|
||||||
|
Value<bool> isTeamMatch,
|
||||||
Value<String> notes,
|
Value<String> notes,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<DateTime?> endedAt,
|
Value<DateTime?> endedAt,
|
||||||
@@ -4215,6 +4368,11 @@ class $$MatchTableTableFilterComposer
|
|||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<bool> get isTeamMatch => $composableBuilder(
|
||||||
|
column: $table.isTeamMatch,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnFilters<String> get notes => $composableBuilder(
|
ColumnFilters<String> get notes => $composableBuilder(
|
||||||
column: $table.notes,
|
column: $table.notes,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
@@ -4346,6 +4504,11 @@ class $$MatchTableTableOrderingComposer
|
|||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<bool> get isTeamMatch => $composableBuilder(
|
||||||
|
column: $table.isTeamMatch,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnOrderings<String> get notes => $composableBuilder(
|
ColumnOrderings<String> get notes => $composableBuilder(
|
||||||
column: $table.notes,
|
column: $table.notes,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
@@ -4423,6 +4586,11 @@ class $$MatchTableTableAnnotationComposer
|
|||||||
GeneratedColumn<String> get name =>
|
GeneratedColumn<String> get name =>
|
||||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<bool> get isTeamMatch => $composableBuilder(
|
||||||
|
column: $table.isTeamMatch,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
GeneratedColumn<String> get notes =>
|
GeneratedColumn<String> get notes =>
|
||||||
$composableBuilder(column: $table.notes, builder: (column) => column);
|
$composableBuilder(column: $table.notes, builder: (column) => column);
|
||||||
|
|
||||||
@@ -4566,6 +4734,7 @@ class $$MatchTableTableTableManager
|
|||||||
Value<String> gameId = const Value.absent(),
|
Value<String> gameId = const Value.absent(),
|
||||||
Value<String?> groupId = const Value.absent(),
|
Value<String?> groupId = const Value.absent(),
|
||||||
Value<String> name = const Value.absent(),
|
Value<String> name = const Value.absent(),
|
||||||
|
Value<bool> isTeamMatch = const Value.absent(),
|
||||||
Value<String> notes = const Value.absent(),
|
Value<String> notes = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<DateTime?> endedAt = const Value.absent(),
|
Value<DateTime?> endedAt = const Value.absent(),
|
||||||
@@ -4575,6 +4744,7 @@ class $$MatchTableTableTableManager
|
|||||||
gameId: gameId,
|
gameId: gameId,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
name: name,
|
name: name,
|
||||||
|
isTeamMatch: isTeamMatch,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
endedAt: endedAt,
|
endedAt: endedAt,
|
||||||
@@ -4586,6 +4756,7 @@ class $$MatchTableTableTableManager
|
|||||||
required String gameId,
|
required String gameId,
|
||||||
Value<String?> groupId = const Value.absent(),
|
Value<String?> groupId = const Value.absent(),
|
||||||
required String name,
|
required String name,
|
||||||
|
Value<bool> isTeamMatch = const Value.absent(),
|
||||||
required String notes,
|
required String notes,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
Value<DateTime?> endedAt = const Value.absent(),
|
Value<DateTime?> endedAt = const Value.absent(),
|
||||||
@@ -4595,6 +4766,7 @@ class $$MatchTableTableTableManager
|
|||||||
gameId: gameId,
|
gameId: gameId,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
name: name,
|
name: name,
|
||||||
|
isTeamMatch: isTeamMatch,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
endedAt: endedAt,
|
endedAt: endedAt,
|
||||||
@@ -5109,6 +5281,8 @@ typedef $$TeamTableTableCreateCompanionBuilder =
|
|||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
Value<String> color,
|
||||||
|
Value<int?> score,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $$TeamTableTableUpdateCompanionBuilder =
|
typedef $$TeamTableTableUpdateCompanionBuilder =
|
||||||
@@ -5116,6 +5290,8 @@ typedef $$TeamTableTableUpdateCompanionBuilder =
|
|||||||
Value<String> id,
|
Value<String> id,
|
||||||
Value<String> name,
|
Value<String> name,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
|
Value<String> color,
|
||||||
|
Value<int?> score,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5171,6 +5347,16 @@ class $$TeamTableTableFilterComposer
|
|||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get color => $composableBuilder(
|
||||||
|
column: $table.color,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<int> get score => $composableBuilder(
|
||||||
|
column: $table.score,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
Expression<bool> playerMatchTableRefs(
|
Expression<bool> playerMatchTableRefs(
|
||||||
Expression<bool> Function($$PlayerMatchTableTableFilterComposer f) f,
|
Expression<bool> Function($$PlayerMatchTableTableFilterComposer f) f,
|
||||||
) {
|
) {
|
||||||
@@ -5220,6 +5406,16 @@ class $$TeamTableTableOrderingComposer
|
|||||||
column: $table.createdAt,
|
column: $table.createdAt,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get color => $composableBuilder(
|
||||||
|
column: $table.color,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<int> get score => $composableBuilder(
|
||||||
|
column: $table.score,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TeamTableTableAnnotationComposer
|
class $$TeamTableTableAnnotationComposer
|
||||||
@@ -5240,6 +5436,12 @@ class $$TeamTableTableAnnotationComposer
|
|||||||
GeneratedColumn<DateTime> get createdAt =>
|
GeneratedColumn<DateTime> get createdAt =>
|
||||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get color =>
|
||||||
|
$composableBuilder(column: $table.color, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get score =>
|
||||||
|
$composableBuilder(column: $table.score, builder: (column) => column);
|
||||||
|
|
||||||
Expression<T> playerMatchTableRefs<T extends Object>(
|
Expression<T> playerMatchTableRefs<T extends Object>(
|
||||||
Expression<T> Function($$PlayerMatchTableTableAnnotationComposer a) f,
|
Expression<T> Function($$PlayerMatchTableTableAnnotationComposer a) f,
|
||||||
) {
|
) {
|
||||||
@@ -5297,11 +5499,15 @@ class $$TeamTableTableTableManager
|
|||||||
Value<String> id = const Value.absent(),
|
Value<String> id = const Value.absent(),
|
||||||
Value<String> name = const Value.absent(),
|
Value<String> name = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
Value<String> color = const Value.absent(),
|
||||||
|
Value<int?> score = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => TeamTableCompanion(
|
}) => TeamTableCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
|
color: color,
|
||||||
|
score: score,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
@@ -5309,11 +5515,15 @@ class $$TeamTableTableTableManager
|
|||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
Value<String> color = const Value.absent(),
|
||||||
|
Value<int?> score = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => TeamTableCompanion.insert(
|
}) => TeamTableCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
|
color: color,
|
||||||
|
score: score,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class MatchTable extends Table {
|
|||||||
.references(GroupTable, #id, onDelete: KeyAction.setNull)
|
.references(GroupTable, #id, onDelete: KeyAction.setNull)
|
||||||
.nullable()();
|
.nullable()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
|
BoolColumn get isTeamMatch => boolean().withDefault(const Constant(false))();
|
||||||
TextColumn get notes => text()();
|
TextColumn get notes => text()();
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
DateTimeColumn get endedAt => dateTime().nullable()();
|
DateTimeColumn get endedAt => dateTime().nullable()();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ class TeamTable extends Table {
|
|||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
TextColumn get color => text().withDefault(const Constant('blue'))();
|
||||||
|
IntColumn get score => integer().nullable()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ class Game {
|
|||||||
final String name;
|
final String name;
|
||||||
final Ruleset ruleset;
|
final Ruleset ruleset;
|
||||||
final String description;
|
final String description;
|
||||||
final GameColor color;
|
final AppColor color;
|
||||||
final String icon;
|
final String icon;
|
||||||
|
|
||||||
Game({
|
Game({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.ruleset,
|
required this.ruleset,
|
||||||
this.color = GameColor.orange,
|
this.color = AppColor.orange,
|
||||||
this.description = '',
|
this.description = '',
|
||||||
this.icon = '',
|
this.icon = '',
|
||||||
String? id,
|
String? id,
|
||||||
@@ -33,7 +33,7 @@ class Game {
|
|||||||
String? name,
|
String? name,
|
||||||
Ruleset? ruleset,
|
Ruleset? ruleset,
|
||||||
String? description,
|
String? description,
|
||||||
GameColor? color,
|
AppColor? color,
|
||||||
String? icon,
|
String? icon,
|
||||||
}) {
|
}) {
|
||||||
return Game(
|
return Game(
|
||||||
@@ -73,7 +73,10 @@ class Game {
|
|||||||
orElse: () => Ruleset.singleWinner,
|
orElse: () => Ruleset.singleWinner,
|
||||||
),
|
),
|
||||||
description = json['description'],
|
description = json['description'],
|
||||||
color = GameColor.values.firstWhere((e) => e.name == json['color']),
|
color = AppColor.values.firstWhere(
|
||||||
|
(e) => e.name == json['color'],
|
||||||
|
orElse: () => AppColor.orange,
|
||||||
|
),
|
||||||
icon = json['icon'];
|
icon = json['icon'];
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ class Match {
|
|||||||
final Game game;
|
final Game game;
|
||||||
final Group? group;
|
final Group? group;
|
||||||
final List<Player> players;
|
final List<Player> players;
|
||||||
|
final bool isTeamMatch;
|
||||||
final List<Team>? teams;
|
final List<Team>? teams;
|
||||||
final String notes;
|
final String notes;
|
||||||
Map<String, ScoreEntry?> scores;
|
final Map<String, ScoreEntry?> scores;
|
||||||
|
|
||||||
Match({
|
Match({
|
||||||
required this.name,
|
required this.name,
|
||||||
@@ -26,6 +27,7 @@ class Match {
|
|||||||
required this.players,
|
required this.players,
|
||||||
this.endedAt,
|
this.endedAt,
|
||||||
this.group,
|
this.group,
|
||||||
|
this.isTeamMatch = false,
|
||||||
this.teams,
|
this.teams,
|
||||||
this.notes = '',
|
this.notes = '',
|
||||||
String? id,
|
String? id,
|
||||||
@@ -37,7 +39,7 @@ class Match {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}';
|
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, isTeamMatch: $isTeamMatch, teams: $teams, notes: $notes, scores: $scores, mvp: $mvp}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Match copyWith({
|
Match copyWith({
|
||||||
@@ -48,6 +50,7 @@ class Match {
|
|||||||
Game? game,
|
Game? game,
|
||||||
Group? group,
|
Group? group,
|
||||||
List<Player>? players,
|
List<Player>? players,
|
||||||
|
bool? isTeamMatch,
|
||||||
List<Team>? teams,
|
List<Team>? teams,
|
||||||
String? notes,
|
String? notes,
|
||||||
Map<String, ScoreEntry?>? scores,
|
Map<String, ScoreEntry?>? scores,
|
||||||
@@ -60,6 +63,7 @@ class Match {
|
|||||||
game: game ?? this.game,
|
game: game ?? this.game,
|
||||||
group: group ?? this.group,
|
group: group ?? this.group,
|
||||||
players: players ?? this.players,
|
players: players ?? this.players,
|
||||||
|
isTeamMatch: isTeamMatch ?? this.isTeamMatch,
|
||||||
teams: teams ?? this.teams,
|
teams: teams ?? this.teams,
|
||||||
notes: notes ?? this.notes,
|
notes: notes ?? this.notes,
|
||||||
scores: scores ?? this.scores,
|
scores: scores ?? this.scores,
|
||||||
@@ -78,6 +82,7 @@ class Match {
|
|||||||
game == other.game &&
|
game == other.game &&
|
||||||
group == other.group &&
|
group == other.group &&
|
||||||
const DeepCollectionEquality().equals(players, other.players) &&
|
const DeepCollectionEquality().equals(players, other.players) &&
|
||||||
|
isTeamMatch == other.isTeamMatch &&
|
||||||
const DeepCollectionEquality().equals(teams, other.teams) &&
|
const DeepCollectionEquality().equals(teams, other.teams) &&
|
||||||
notes == other.notes &&
|
notes == other.notes &&
|
||||||
const DeepCollectionEquality().equals(scores, other.scores);
|
const DeepCollectionEquality().equals(scores, other.scores);
|
||||||
@@ -91,6 +96,7 @@ class Match {
|
|||||||
game,
|
game,
|
||||||
group,
|
group,
|
||||||
const DeepCollectionEquality().hash(players),
|
const DeepCollectionEquality().hash(players),
|
||||||
|
isTeamMatch,
|
||||||
const DeepCollectionEquality().hash(teams),
|
const DeepCollectionEquality().hash(teams),
|
||||||
notes,
|
notes,
|
||||||
const DeepCollectionEquality().hash(scores),
|
const DeepCollectionEquality().hash(scores),
|
||||||
@@ -107,11 +113,12 @@ class Match {
|
|||||||
name: '',
|
name: '',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: '',
|
description: '',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
),
|
),
|
||||||
group = null,
|
group = null,
|
||||||
players = [],
|
players = [],
|
||||||
|
isTeamMatch = json['isTeamMatch'],
|
||||||
teams = [],
|
teams = [],
|
||||||
scores = json['scores'] != null
|
scores = json['scores'] != null
|
||||||
? (json['scores'] as Map<String, dynamic>).map(
|
? (json['scores'] as Map<String, dynamic>).map(
|
||||||
@@ -133,11 +140,13 @@ class Match {
|
|||||||
'gameId': game.id,
|
'gameId': game.id,
|
||||||
'groupId': group?.id,
|
'groupId': group?.id,
|
||||||
'playerIds': players.map((player) => player.id).toList(),
|
'playerIds': players.map((player) => player.id).toList(),
|
||||||
|
'isTeamMatch': isTeamMatch,
|
||||||
'teams': teams?.map((team) => team.toJson()).toList(),
|
'teams': teams?.map((team) => team.toJson()).toList(),
|
||||||
'scores': scores.map((key, value) => MapEntry(key, value?.toJson())),
|
'scores': scores.map((key, value) => MapEntry(key, value?.toJson())),
|
||||||
'notes': notes,
|
'notes': notes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Most Valuable Player(s) based on the match's ruleset
|
||||||
List<Player> get mvp {
|
List<Player> get mvp {
|
||||||
if (players.isEmpty || scores.isEmpty) return [];
|
if (players.isEmpty || scores.isEmpty) return [];
|
||||||
|
|
||||||
@@ -195,4 +204,59 @@ class Match {
|
|||||||
return playerScore.score == lowestScore;
|
return playerScore.score == lowestScore;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MVP for team-based matches (Most Valuable Team)
|
||||||
|
List<Team> get mvt {
|
||||||
|
if (teams == null || teams!.isEmpty) return [];
|
||||||
|
|
||||||
|
switch (game.ruleset) {
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
return _getHighestScoreTeam();
|
||||||
|
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
return _getLowestScoreTeam();
|
||||||
|
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return _getHighestScoreTeam().take(1).toList();
|
||||||
|
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return _getLowestScoreTeam().take(1).toList();
|
||||||
|
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
return _getHighestScoreTeam();
|
||||||
|
|
||||||
|
case Ruleset.placement:
|
||||||
|
return _getHighestScoreTeam().take(1).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Team> _getHighestScoreTeam() {
|
||||||
|
if (teams!.every((team) => team.score == null)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final int highestScore = teams!
|
||||||
|
.map((team) => team.score)
|
||||||
|
.whereType<int>()
|
||||||
|
.reduce((max, score) => score > max ? score : max);
|
||||||
|
|
||||||
|
return teams!.where((team) {
|
||||||
|
return team.score == highestScore;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Team> _getLowestScoreTeam() {
|
||||||
|
if (teams!.every((team) => team.score == null)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final int lowestScore = teams!
|
||||||
|
.map((team) => team.score)
|
||||||
|
.whereType<int>()
|
||||||
|
.reduce((min, score) => score < min ? score : min);
|
||||||
|
|
||||||
|
return teams!.where((team) {
|
||||||
|
return team.score == lowestScore;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@@ -7,31 +8,39 @@ class Team {
|
|||||||
final String id;
|
final String id;
|
||||||
final String name;
|
final String name;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
|
final AppColor color;
|
||||||
|
final int? score;
|
||||||
final List<Player> members;
|
final List<Player> members;
|
||||||
|
|
||||||
Team({
|
Team({
|
||||||
String? id,
|
String? id,
|
||||||
required this.name,
|
required this.name,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
|
this.color = AppColor.blue,
|
||||||
|
this.score,
|
||||||
required this.members,
|
required this.members,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
createdAt = createdAt ?? clock.now();
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Team{id: $id, name: $name, members: $members}';
|
return 'Team{id: $id, name: $name, color: $color, score: $score, members: $members}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Team copyWith({
|
Team copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? name,
|
String? name,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
|
AppColor? color,
|
||||||
|
int? score,
|
||||||
List<Player>? members,
|
List<Player>? members,
|
||||||
}) {
|
}) {
|
||||||
return Team(
|
return Team(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
color: color ?? this.color,
|
||||||
|
score: score ?? this.score,
|
||||||
members: members ?? this.members,
|
members: members ?? this.members,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,6 +53,8 @@ class Team {
|
|||||||
id == other.id &&
|
id == other.id &&
|
||||||
name == other.name &&
|
name == other.name &&
|
||||||
createdAt == other.createdAt &&
|
createdAt == other.createdAt &&
|
||||||
|
color == other.color &&
|
||||||
|
score == other.score &&
|
||||||
const DeepCollectionEquality().equals(members, other.members);
|
const DeepCollectionEquality().equals(members, other.members);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -51,6 +62,8 @@ class Team {
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
color,
|
||||||
|
score,
|
||||||
const DeepCollectionEquality().hash(members),
|
const DeepCollectionEquality().hash(members),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -58,12 +71,19 @@ class Team {
|
|||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
|
color = AppColor.values.firstWhere(
|
||||||
|
(e) => e.name == json['color'],
|
||||||
|
orElse: () => AppColor.orange,
|
||||||
|
),
|
||||||
|
score = json['score'] ?? 0,
|
||||||
members = []; // Populated during import via DataTransferService
|
members = []; // Populated during import via DataTransferService
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'createdAt': createdAt.toIso8601String(),
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'color': color.name,
|
||||||
|
'score': score,
|
||||||
'memberIds': members.map((member) => member.id).toList(),
|
'memberIds': members.map((member) => member.id).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "de",
|
"@@locale": "de",
|
||||||
|
"add_team": "Team hinzufügen",
|
||||||
"all_players": "Alle Spieler:innen",
|
"all_players": "Alle Spieler:innen",
|
||||||
"all_players_selected": "Alle Spieler:innen ausgewählt",
|
"all_players_selected": "Alle Spieler:innen ausgewählt",
|
||||||
"amount_of_matches": "Anzahl der Spiele",
|
"amount_of_matches": "Anzahl der Spiele",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"create_match": "Spiel erstellen",
|
"create_match": "Spiel erstellen",
|
||||||
"create_new_group": "Neue Gruppe erstellen",
|
"create_new_group": "Neue Gruppe erstellen",
|
||||||
"create_new_match": "Neues Spiel erstellen",
|
"create_new_match": "Neues Spiel erstellen",
|
||||||
|
"create_teams": "Teams erstellen",
|
||||||
"created_on": "Erstellt am",
|
"created_on": "Erstellt am",
|
||||||
"data": "Daten",
|
"data": "Daten",
|
||||||
"data_successfully_deleted": "Daten erfolgreich gelöscht",
|
"data_successfully_deleted": "Daten erfolgreich gelöscht",
|
||||||
@@ -79,10 +81,12 @@
|
|||||||
"live_edit_mode": "Live-Bearbeitungsmodus",
|
"live_edit_mode": "Live-Bearbeitungsmodus",
|
||||||
"loser": "Verlierer:in",
|
"loser": "Verlierer:in",
|
||||||
"lowest_score": "Niedrigste Punkte",
|
"lowest_score": "Niedrigste Punkte",
|
||||||
|
"manage_members": "Mitglieder bearbeiten",
|
||||||
"match_in_progress": "Spiel läuft...",
|
"match_in_progress": "Spiel läuft...",
|
||||||
"match_name": "Spieltitel",
|
"match_name": "Spieltitel",
|
||||||
"match_profile": "Spielprofil",
|
"match_profile": "Spielprofil",
|
||||||
"matches": "Spiele",
|
"matches": "Spiele",
|
||||||
|
"member": "Mitglied",
|
||||||
"members": "Mitglieder",
|
"members": "Mitglieder",
|
||||||
"most_points": "Höchste Punkte",
|
"most_points": "Höchste Punkte",
|
||||||
"multiple_winners": "Mehrere Gewinner:innen",
|
"multiple_winners": "Mehrere Gewinner:innen",
|
||||||
@@ -92,6 +96,7 @@
|
|||||||
"no_license_text_available": "Kein Lizenztext verfügbar",
|
"no_license_text_available": "Kein Lizenztext verfügbar",
|
||||||
"no_licenses_found": "Keine Lizenzen gefunden",
|
"no_licenses_found": "Keine Lizenzen gefunden",
|
||||||
"no_matches_created_yet": "Noch keine Spiele erstellt",
|
"no_matches_created_yet": "Noch keine Spiele erstellt",
|
||||||
|
"no_players_available": "Keine Spieler:innen verfügbar",
|
||||||
"no_players_created_yet": "Noch keine Spieler:in erstellt",
|
"no_players_created_yet": "Noch keine Spieler:in erstellt",
|
||||||
"no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
|
"no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
|
||||||
"no_players_selected": "Keine Spieler:innen ausgewählt",
|
"no_players_selected": "Keine Spieler:innen ausgewählt",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"no_results_entered_yet": "Noch keine Ergebnisse eingetragen",
|
"no_results_entered_yet": "Noch keine Ergebnisse eingetragen",
|
||||||
"no_second_match_available": "Kein zweites Spiel verfügbar",
|
"no_second_match_available": "Kein zweites Spiel verfügbar",
|
||||||
"no_statistics_available": "Keine Statistiken verfügbar",
|
"no_statistics_available": "Keine Statistiken verfügbar",
|
||||||
|
"no_teams_available": "Keine Teams verfügbar",
|
||||||
"none": "Kein",
|
"none": "Kein",
|
||||||
"none_group": "Keine",
|
"none_group": "Keine",
|
||||||
"not_available": "Nicht verfügbar",
|
"not_available": "Nicht verfügbar",
|
||||||
@@ -112,6 +118,7 @@
|
|||||||
"privacy_policy": "Datenschutzerklärung",
|
"privacy_policy": "Datenschutzerklärung",
|
||||||
"quick_create": "Schnellzugriff",
|
"quick_create": "Schnellzugriff",
|
||||||
"recent_matches": "Letzte Spiele",
|
"recent_matches": "Letzte Spiele",
|
||||||
|
"redistribute": "Neu verteilen",
|
||||||
"result": "Ergebnis",
|
"result": "Ergebnis",
|
||||||
"results": "Ergebnisse",
|
"results": "Ergebnisse",
|
||||||
"ruleset": "Regelwerk",
|
"ruleset": "Regelwerk",
|
||||||
@@ -133,6 +140,9 @@
|
|||||||
"statistics": "Statistiken",
|
"statistics": "Statistiken",
|
||||||
"stats": "Statistiken",
|
"stats": "Statistiken",
|
||||||
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
||||||
|
"team": "Team",
|
||||||
|
"team_match": "Teamspiel",
|
||||||
|
"teams": "Teams",
|
||||||
"there_are_no_games_matching_your_search": "Es gibt keine Spielvorlagen, die deiner Suche entspricht",
|
"there_are_no_games_matching_your_search": "Es gibt keine Spielvorlagen, die deiner Suche entspricht",
|
||||||
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
|
"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.",
|
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden.",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
|
"add_team": "Add Team",
|
||||||
"all_players": "All players",
|
"all_players": "All players",
|
||||||
"all_players_selected": "All players selected",
|
"all_players_selected": "All players selected",
|
||||||
"amount_of_matches": "Amount of Matches",
|
"amount_of_matches": "Amount of Matches",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"create_match": "Create match",
|
"create_match": "Create match",
|
||||||
"create_new_group": "Create new group",
|
"create_new_group": "Create new group",
|
||||||
"create_new_match": "Create new match",
|
"create_new_match": "Create new match",
|
||||||
|
"create_teams": "Create teams",
|
||||||
"created_on": "Created on",
|
"created_on": "Created on",
|
||||||
"data": "Data",
|
"data": "Data",
|
||||||
"data_successfully_deleted": "Data successfully deleted",
|
"data_successfully_deleted": "Data successfully deleted",
|
||||||
@@ -79,10 +81,12 @@
|
|||||||
"live_edit_mode": "Live Edit Mode",
|
"live_edit_mode": "Live Edit Mode",
|
||||||
"loser": "Loser",
|
"loser": "Loser",
|
||||||
"lowest_score": "Lowest Score",
|
"lowest_score": "Lowest Score",
|
||||||
|
"manage_members": "Manage Members",
|
||||||
"match_in_progress": "Match in progress...",
|
"match_in_progress": "Match in progress...",
|
||||||
"match_name": "Match name",
|
"match_name": "Match name",
|
||||||
"match_profile": "Match Profile",
|
"match_profile": "Match Profile",
|
||||||
"matches": "Matches",
|
"matches": "Matches",
|
||||||
|
"member": "Member",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
"most_points": "Most Points",
|
"most_points": "Most Points",
|
||||||
"multiple_winners": "Multiple Winners",
|
"multiple_winners": "Multiple Winners",
|
||||||
@@ -92,6 +96,7 @@
|
|||||||
"no_license_text_available": "No license text available",
|
"no_license_text_available": "No license text available",
|
||||||
"no_licenses_found": "No licenses found",
|
"no_licenses_found": "No licenses found",
|
||||||
"no_matches_created_yet": "No matches created yet",
|
"no_matches_created_yet": "No matches created yet",
|
||||||
|
"no_players_available": "No players available",
|
||||||
"no_players_created_yet": "No players created yet",
|
"no_players_created_yet": "No players created yet",
|
||||||
"no_players_found_with_that_name": "No players found with that name",
|
"no_players_found_with_that_name": "No players found with that name",
|
||||||
"no_players_selected": "No players selected",
|
"no_players_selected": "No players selected",
|
||||||
@@ -99,6 +104,7 @@
|
|||||||
"no_results_entered_yet": "No results entered yet",
|
"no_results_entered_yet": "No results entered yet",
|
||||||
"no_second_match_available": "No second match available",
|
"no_second_match_available": "No second match available",
|
||||||
"no_statistics_available": "No statistics available",
|
"no_statistics_available": "No statistics available",
|
||||||
|
"no_teams_available": "No teams available",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"none_group": "None",
|
"none_group": "None",
|
||||||
"not_available": "Not available",
|
"not_available": "Not available",
|
||||||
@@ -112,6 +118,7 @@
|
|||||||
"privacy_policy": "Privacy Policy",
|
"privacy_policy": "Privacy Policy",
|
||||||
"quick_create": "Quick Create",
|
"quick_create": "Quick Create",
|
||||||
"recent_matches": "Recent Matches",
|
"recent_matches": "Recent Matches",
|
||||||
|
"redistribute": "Redistribute",
|
||||||
"results": "Results",
|
"results": "Results",
|
||||||
"ruleset": "Ruleset",
|
"ruleset": "Ruleset",
|
||||||
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
|
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
|
||||||
@@ -141,6 +148,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"team": "Team",
|
||||||
|
"team_match": "Team Match",
|
||||||
|
"teams": "Teams",
|
||||||
"there_are_no_games_matching_your_search": "There are no games matching your search",
|
"there_are_no_games_matching_your_search": "There are no games matching your search",
|
||||||
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
||||||
"this_cannot_be_undone": "This can't be undone.",
|
"this_cannot_be_undone": "This can't be undone.",
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ abstract class AppLocalizations {
|
|||||||
Locale('en'),
|
Locale('en'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// No description provided for @add_team.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Add Team'**
|
||||||
|
String get add_team;
|
||||||
|
|
||||||
/// No description provided for @all_players.
|
/// No description provided for @all_players.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -248,6 +254,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Create new match'**
|
/// **'Create new match'**
|
||||||
String get create_new_match;
|
String get create_new_match;
|
||||||
|
|
||||||
|
/// No description provided for @create_teams.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create teams'**
|
||||||
|
String get create_teams;
|
||||||
|
|
||||||
/// No description provided for @created_on.
|
/// No description provided for @created_on.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -530,6 +542,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Lowest Score'**
|
/// **'Lowest Score'**
|
||||||
String get lowest_score;
|
String get lowest_score;
|
||||||
|
|
||||||
|
/// No description provided for @manage_members.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Manage Members'**
|
||||||
|
String get manage_members;
|
||||||
|
|
||||||
/// No description provided for @match_in_progress.
|
/// No description provided for @match_in_progress.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -554,6 +572,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Matches'**
|
/// **'Matches'**
|
||||||
String get matches;
|
String get matches;
|
||||||
|
|
||||||
|
/// No description provided for @member.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Member'**
|
||||||
|
String get member;
|
||||||
|
|
||||||
/// No description provided for @members.
|
/// No description provided for @members.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -608,6 +632,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'No matches created yet'**
|
/// **'No matches created yet'**
|
||||||
String get no_matches_created_yet;
|
String get no_matches_created_yet;
|
||||||
|
|
||||||
|
/// No description provided for @no_players_available.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No players available'**
|
||||||
|
String get no_players_available;
|
||||||
|
|
||||||
/// No description provided for @no_players_created_yet.
|
/// No description provided for @no_players_created_yet.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -650,6 +680,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'No statistics available'**
|
/// **'No statistics available'**
|
||||||
String get no_statistics_available;
|
String get no_statistics_available;
|
||||||
|
|
||||||
|
/// No description provided for @no_teams_available.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No teams available'**
|
||||||
|
String get no_teams_available;
|
||||||
|
|
||||||
/// No description provided for @none.
|
/// No description provided for @none.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -728,6 +764,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Recent Matches'**
|
/// **'Recent Matches'**
|
||||||
String get recent_matches;
|
String get recent_matches;
|
||||||
|
|
||||||
|
/// No description provided for @redistribute.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Redistribute'**
|
||||||
|
String get redistribute;
|
||||||
|
|
||||||
/// No description provided for @results.
|
/// No description provided for @results.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -848,6 +890,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'Successfully added player {playerName}'**
|
/// **'Successfully added player {playerName}'**
|
||||||
String successfully_added_player(String playerName);
|
String successfully_added_player(String playerName);
|
||||||
|
|
||||||
|
/// No description provided for @team.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Team'**
|
||||||
|
String get team;
|
||||||
|
|
||||||
|
/// No description provided for @team_match.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Team Match'**
|
||||||
|
String get team_match;
|
||||||
|
|
||||||
|
/// No description provided for @teams.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Teams'**
|
||||||
|
String get teams;
|
||||||
|
|
||||||
/// No description provided for @there_are_no_games_matching_your_search.
|
/// No description provided for @there_are_no_games_matching_your_search.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import 'app_localizations.dart';
|
|||||||
class AppLocalizationsDe extends AppLocalizations {
|
class AppLocalizationsDe extends AppLocalizations {
|
||||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get add_team => 'Team hinzufügen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get all_players => 'Alle Spieler:innen';
|
String get all_players => 'Alle Spieler:innen';
|
||||||
|
|
||||||
@@ -85,6 +88,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get create_new_match => 'Neues Spiel erstellen';
|
String get create_new_match => 'Neues Spiel erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_teams => 'Teams erstellen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get created_on => 'Erstellt am';
|
String get created_on => 'Erstellt am';
|
||||||
|
|
||||||
@@ -240,6 +246,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get lowest_score => 'Niedrigste Punkte';
|
String get lowest_score => 'Niedrigste Punkte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manage_members => 'Mitglieder bearbeiten';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get match_in_progress => 'Spiel läuft...';
|
String get match_in_progress => 'Spiel läuft...';
|
||||||
|
|
||||||
@@ -252,6 +261,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get matches => 'Spiele';
|
String get matches => 'Spiele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get member => 'Mitglied';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get members => 'Mitglieder';
|
String get members => 'Mitglieder';
|
||||||
|
|
||||||
@@ -279,6 +291,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get no_matches_created_yet => 'Noch keine Spiele erstellt';
|
String get no_matches_created_yet => 'Noch keine Spiele erstellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_available => 'Keine Spieler:innen verfügbar';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_players_created_yet => 'Noch keine Spieler:in erstellt';
|
String get no_players_created_yet => 'Noch keine Spieler:in erstellt';
|
||||||
|
|
||||||
@@ -301,6 +316,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get no_statistics_available => 'Keine Statistiken verfügbar';
|
String get no_statistics_available => 'Keine Statistiken verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_teams_available => 'Keine Teams verfügbar';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get none => 'Kein';
|
String get none => 'Kein';
|
||||||
|
|
||||||
@@ -340,6 +358,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get recent_matches => 'Letzte Spiele';
|
String get recent_matches => 'Letzte Spiele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redistribute => 'Neu verteilen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get results => 'Ergebnisse';
|
String get results => 'Ergebnisse';
|
||||||
|
|
||||||
@@ -407,6 +428,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
return 'Spieler:in $playerName erfolgreich hinzugefügt';
|
return 'Spieler:in $playerName erfolgreich hinzugefügt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get team => 'Team';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get team_match => 'Teamspiel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get teams => 'Teams';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get there_are_no_games_matching_your_search =>
|
String get there_are_no_games_matching_your_search =>
|
||||||
'Es gibt keine Spielvorlagen, die deiner Suche entspricht';
|
'Es gibt keine Spielvorlagen, die deiner Suche entspricht';
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import 'app_localizations.dart';
|
|||||||
class AppLocalizationsEn extends AppLocalizations {
|
class AppLocalizationsEn extends AppLocalizations {
|
||||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get add_team => 'Add Team';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get all_players => 'All players';
|
String get all_players => 'All players';
|
||||||
|
|
||||||
@@ -85,6 +88,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get create_new_match => 'Create new match';
|
String get create_new_match => 'Create new match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_teams => 'Create teams';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get created_on => 'Created on';
|
String get created_on => 'Created on';
|
||||||
|
|
||||||
@@ -240,6 +246,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get lowest_score => 'Lowest Score';
|
String get lowest_score => 'Lowest Score';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manage_members => 'Manage Members';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get match_in_progress => 'Match in progress...';
|
String get match_in_progress => 'Match in progress...';
|
||||||
|
|
||||||
@@ -252,6 +261,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get matches => 'Matches';
|
String get matches => 'Matches';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get member => 'Member';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get members => 'Members';
|
String get members => 'Members';
|
||||||
|
|
||||||
@@ -279,6 +291,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get no_matches_created_yet => 'No matches created yet';
|
String get no_matches_created_yet => 'No matches created yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_available => 'No players available';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get no_players_created_yet => 'No players created yet';
|
String get no_players_created_yet => 'No players created yet';
|
||||||
|
|
||||||
@@ -301,6 +316,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get no_statistics_available => 'No statistics available';
|
String get no_statistics_available => 'No statistics available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_teams_available => 'No teams available';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get none => 'None';
|
String get none => 'None';
|
||||||
|
|
||||||
@@ -340,6 +358,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get recent_matches => 'Recent Matches';
|
String get recent_matches => 'Recent Matches';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redistribute => 'Redistribute';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get results => 'Results';
|
String get results => 'Results';
|
||||||
|
|
||||||
@@ -407,6 +428,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
return 'Successfully added player $playerName';
|
return 'Successfully added player $playerName';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get team => 'Team';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get team_match => 'Team Match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get teams => 'Teams';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get there_are_no_games_matching_your_search =>
|
String get there_are_no_games_matching_your_search =>
|
||||||
'There are no games matching your search';
|
'There are no games matching your search';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:tallee/data/db/database.dart';
|
|||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/player_selection.dart';
|
import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||||
|
|
||||||
@@ -96,19 +96,24 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomWidthButton(
|
Padding(
|
||||||
text: widget.groupToEdit == null
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
? loc.create_group
|
child: AnimatedDialogButton(
|
||||||
: loc.edit_group,
|
buttonConstraints: const BoxConstraints(
|
||||||
sizeRelativeToWidth: 0.95,
|
minWidth: double.infinity,
|
||||||
buttonType: ButtonType.primary,
|
minHeight: 50,
|
||||||
onPressed:
|
),
|
||||||
(_groupNameController.text.isEmpty ||
|
buttonText: widget.groupToEdit == null
|
||||||
(selectedPlayers.length < 2))
|
? loc.create_group
|
||||||
? null
|
: loc.edit_group,
|
||||||
: _saveGroup,
|
buttonType: ButtonType.primary,
|
||||||
|
onPressed:
|
||||||
|
(_groupNameController.text.isEmpty ||
|
||||||
|
(selectedPlayers.length < 2))
|
||||||
|
? null
|
||||||
|
: _saveGroup,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
return TextIconTile(
|
return TextIconTile(
|
||||||
text: member.name,
|
text: member.name,
|
||||||
suffixText: getNameCountText(member),
|
suffixText: getNameCountText(member),
|
||||||
iconEnabled: false,
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
/// Games filtered according to the current search query
|
/// Games filtered according to the current search query
|
||||||
late List<Game> filteredGames;
|
late List<Game> filteredGames;
|
||||||
|
|
||||||
|
List<Game> get games =>
|
||||||
|
widget.games..sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
@@ -59,7 +62,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
selectedGameId = widget.initialGameId;
|
selectedGameId = widget.initialGameId;
|
||||||
|
|
||||||
// Start with all games visible
|
// Start with all games visible
|
||||||
filteredGames = List<Game>.from(widget.games);
|
filteredGames = List<Game>.from(games);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@@ -77,9 +80,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
Navigator.of(context).pop(
|
Navigator.of(context).pop(
|
||||||
selectedGameId == ''
|
selectedGameId == ''
|
||||||
? null
|
? null
|
||||||
: widget.games.firstWhere(
|
: games.firstWhere((game) => game.id == selectedGameId),
|
||||||
(game) => game.id == selectedGameId,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -99,7 +100,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
);
|
);
|
||||||
if (result != null && result.game != null) {
|
if (result != null && result.game != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.games.insert(0, result.game);
|
games.insert(0, result.game);
|
||||||
});
|
});
|
||||||
_refreshFromSource();
|
_refreshFromSource();
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: filteredGames.isNotEmpty,
|
visible: filteredGames.isNotEmpty,
|
||||||
replacement: Visibility(
|
replacement: Visibility(
|
||||||
visible: widget.games.isNotEmpty,
|
visible: games.isNotEmpty,
|
||||||
replacement: TopCenteredMessage(
|
replacement: TopCenteredMessage(
|
||||||
icon: Icons.info,
|
icon: Icons.info,
|
||||||
title: loc.info,
|
title: loc.info,
|
||||||
@@ -160,10 +161,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
return GameTile(
|
return GameTile(
|
||||||
title: game.name,
|
title: game.name,
|
||||||
description: game.description,
|
description: game.description,
|
||||||
badgeText: translateRulesetToString(
|
subtitle: translateRulesetToString(game.ruleset, context),
|
||||||
game.ruleset,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
badgeColor: getColorFromGameColor(game.color),
|
badgeColor: getColorFromGameColor(game.color),
|
||||||
isHighlighted: selectedGameId == game.id,
|
isHighlighted: selectedGameId == game.id,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -190,7 +188,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
);
|
);
|
||||||
if (result != null && result.game != null) {
|
if (result != null && result.game != null) {
|
||||||
// Find the index in the original list to mutate
|
// Find the index in the original list to mutate
|
||||||
final originalIndex = widget.games.indexWhere(
|
final originalIndex = games.indexWhere(
|
||||||
(g) => g.id == game.id,
|
(g) => g.id == game.id,
|
||||||
);
|
);
|
||||||
if (originalIndex == -1) {
|
if (originalIndex == -1) {
|
||||||
@@ -202,12 +200,12 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
if (selectedGameId == game.id) {
|
if (selectedGameId == game.id) {
|
||||||
selectedGameId = '';
|
selectedGameId = '';
|
||||||
}
|
}
|
||||||
widget.games.removeAt(originalIndex);
|
games.removeAt(originalIndex);
|
||||||
widget.onGamesUpdated?.call();
|
widget.onGamesUpdated?.call();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.games[originalIndex] = result.game;
|
games[originalIndex] = result.game;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_refreshFromSource();
|
_refreshFromSource();
|
||||||
@@ -229,13 +227,13 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
final q = query.toLowerCase().trim();
|
final q = query.toLowerCase().trim();
|
||||||
if (q.isEmpty) {
|
if (q.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredGames = List<Game>.from(widget.games);
|
filteredGames = List<Game>.from(games);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredGames = widget.games.where((game) {
|
filteredGames = games.where((game) {
|
||||||
final name = game.name.toLowerCase();
|
final name = game.name.toLowerCase();
|
||||||
final description = game.description.toLowerCase();
|
final description = game.description.toLowerCase();
|
||||||
return name.contains(q) || description.contains(q);
|
return name.contains(q) || description.contains(q);
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
|
|
||||||
late List<(Ruleset, String)> _rulesets;
|
late List<(Ruleset, String)> _rulesets;
|
||||||
late List<(GameColor, String)> _colors;
|
late List<(AppColor, String)> _colors;
|
||||||
|
|
||||||
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
||||||
GameColor? selectedColor = GameColor.orange;
|
AppColor? selectedColor = AppColor.orange;
|
||||||
|
|
||||||
/// Controller for the game name input field.
|
/// Controller for the game name input field.
|
||||||
final _gameNameController = TextEditingController();
|
final _gameNameController = TextEditingController();
|
||||||
@@ -87,10 +87,10 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
_colors = List.generate(
|
_colors = List.generate(
|
||||||
GameColor.values.length,
|
AppColor.values.length,
|
||||||
(index) => (
|
(index) => (
|
||||||
GameColor.values[index],
|
AppColor.values[index],
|
||||||
translateGameColorToString(GameColor.values[index], context),
|
translateGameColorToString(AppColor.values[index], context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import 'package:tallee/data/models/player.dart';
|
|||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
|
||||||
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_teams/create_teams_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/player_selection.dart';
|
import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
||||||
@@ -59,6 +60,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
|
|
||||||
Group? selectedGroup;
|
Group? selectedGroup;
|
||||||
Game? selectedGame;
|
Game? selectedGame;
|
||||||
|
bool isTeamMatch = false;
|
||||||
List<Player> selectedPlayers = [];
|
List<Player> selectedPlayers = [];
|
||||||
|
|
||||||
/// GlobalKey for ScaffoldMessenger to show snackbars
|
/// GlobalKey for ScaffoldMessenger to show snackbars
|
||||||
@@ -135,24 +137,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
trailing: selectedGame == null
|
trailing: selectedGame == null
|
||||||
? Text(loc.none_group)
|
? Text(loc.none_group)
|
||||||
: Text(selectedGame!.name),
|
: Text(selectedGame!.name),
|
||||||
onPressed: () async {
|
onPressed: () async => await onChoosingGame(),
|
||||||
selectedGame = await Navigator.of(context).push(
|
|
||||||
adaptivePageRoute(
|
|
||||||
builder: (context) => ChooseGameView(
|
|
||||||
games: gamesList,
|
|
||||||
initialGameId: selectedGame?.id ?? '',
|
|
||||||
onGamesUpdated: widget.onMatchesUpdated,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
if (selectedGame != null) {
|
|
||||||
hintText = selectedGame!.name;
|
|
||||||
} else {
|
|
||||||
hintText = loc.match_name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Group selection tile.
|
// Group selection tile.
|
||||||
@@ -161,36 +146,20 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
trailing: selectedGroup == null
|
trailing: selectedGroup == null
|
||||||
? Text(loc.none_group)
|
? Text(loc.none_group)
|
||||||
: Text(selectedGroup!.name),
|
: Text(selectedGroup!.name),
|
||||||
onPressed: () async {
|
onPressed: () async => onChoosingGroup(),
|
||||||
// Remove all players from the previously selected group from
|
|
||||||
// the selected players list, in case the user deselects the
|
|
||||||
// group or selects a different group.
|
|
||||||
selectedPlayers.removeWhere(
|
|
||||||
(player) =>
|
|
||||||
selectedGroup?.members.any(
|
|
||||||
(member) => member.id == player.id,
|
|
||||||
) ??
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
selectedGroup = await Navigator.of(context).push(
|
|
||||||
adaptivePageRoute(
|
|
||||||
builder: (context) => ChooseGroupView(
|
|
||||||
groups: groupsList,
|
|
||||||
initialGroupId: selectedGroup?.id ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
if (selectedGroup != null) {
|
|
||||||
setState(() {
|
|
||||||
selectedPlayers += [...selectedGroup!.members];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (!isEditMode())
|
||||||
|
ChooseTile(
|
||||||
|
title: loc.team_match,
|
||||||
|
trailing: Switch.adaptive(
|
||||||
|
activeTrackColor: CustomTheme.primaryColor,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: -15),
|
||||||
|
value: isTeamMatch,
|
||||||
|
onChanged: (value) => setState(() => isTeamMatch = value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Player selection widget.
|
// Player selection widget.
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PlayerSelection(
|
child: PlayerSelection(
|
||||||
@@ -206,15 +175,21 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Create or save button.
|
// Create or save button.
|
||||||
CustomWidthButton(
|
Padding(
|
||||||
text: buttonText,
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
sizeRelativeToWidth: 0.95,
|
child: AnimatedDialogButton(
|
||||||
buttonType: ButtonType.primary,
|
buttonConstraints: const BoxConstraints(
|
||||||
onPressed: _enableCreateGameButton()
|
minWidth: double.infinity,
|
||||||
? () {
|
minHeight: 50,
|
||||||
buttonNavigation(context);
|
),
|
||||||
}
|
buttonType: ButtonType.primary,
|
||||||
: null,
|
onPressed: isSubmitButtonEnabled()
|
||||||
|
? () {
|
||||||
|
submitButtonNavigation(context);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
buttonText: buttonText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -227,12 +202,86 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
return widget.matchToEdit != null;
|
return widget.matchToEdit != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a match was provided to the view, this method prefills the input fields
|
||||||
|
void prefillMatchDetails() {
|
||||||
|
final match = widget.matchToEdit!;
|
||||||
|
_matchNameController.text = match.name;
|
||||||
|
selectedPlayers = match.players;
|
||||||
|
selectedGame = match.game;
|
||||||
|
|
||||||
|
if (match.group != null) {
|
||||||
|
selectedGroup = match.group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onChoosingGame() async {
|
||||||
|
selectedGame = await Navigator.of(context).push(
|
||||||
|
adaptivePageRoute(
|
||||||
|
builder: (context) => ChooseGameView(
|
||||||
|
games: gamesList,
|
||||||
|
initialGameId: selectedGame?.id ?? '',
|
||||||
|
onGamesUpdated: widget.onMatchesUpdated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
if (selectedGame != null) {
|
||||||
|
hintText = selectedGame!.name;
|
||||||
|
} else {
|
||||||
|
hintText = AppLocalizations.of(context).match_name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onChoosingGroup() async {
|
||||||
|
// Remove all players from the previously selected group from
|
||||||
|
// the selected players list, in case the user deselects the
|
||||||
|
// group or selects a different group.
|
||||||
|
selectedPlayers.removeWhere(
|
||||||
|
(player) =>
|
||||||
|
selectedGroup?.members.any((member) => member.id == player.id) ??
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
selectedGroup = await Navigator.of(context).push(
|
||||||
|
adaptivePageRoute(
|
||||||
|
builder: (context) => ChooseGroupView(
|
||||||
|
groups: groupsList,
|
||||||
|
initialGroupId: selectedGroup?.id ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (selectedGroup != null) {
|
||||||
|
setState(() {
|
||||||
|
selectedPlayers += [...selectedGroup!.members];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the selected players are from the currently selected group,
|
||||||
|
// the group is also deselected.
|
||||||
|
Future<void> removeGroupWhenNoMemberLeft() async {
|
||||||
|
if (selectedGroup == null) return;
|
||||||
|
|
||||||
|
if (!selectedPlayers.any(
|
||||||
|
(player) =>
|
||||||
|
selectedGroup!.members.any((member) => member.id == player.id),
|
||||||
|
)) {
|
||||||
|
setState(() {
|
||||||
|
selectedGroup = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Determines whether the "Create Match" button should be enabled.
|
/// Determines whether the "Create Match" button should be enabled.
|
||||||
///
|
///
|
||||||
/// Returns `true` if:
|
/// Returns `true` if:
|
||||||
/// - A game is selected AND
|
/// - A game is selected AND
|
||||||
/// - Either a group is selected OR at least 2 players are selected.
|
/// - Either a group is selected OR at least 2 players are selected.
|
||||||
bool _enableCreateGameButton() {
|
bool isSubmitButtonEnabled() {
|
||||||
return ((selectedGroup != null || selectedPlayers.length > 1) &&
|
return ((selectedGroup != null || selectedPlayers.length > 1) &&
|
||||||
selectedGame != null);
|
selectedGame != null);
|
||||||
}
|
}
|
||||||
@@ -241,20 +290,35 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
///
|
///
|
||||||
/// If a match is being edited, updates the match in the database.
|
/// If a match is being edited, updates the match in the database.
|
||||||
/// Otherwise, creates a new match and navigates to the MatchResultView.
|
/// Otherwise, creates a new match and navigates to the MatchResultView.
|
||||||
void buttonNavigation(BuildContext context) async {
|
void submitButtonNavigation(BuildContext context) async {
|
||||||
if (isEditMode()) {
|
if (isEditMode()) {
|
||||||
await updateMatch();
|
await updateMatch();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
final match = await createMatch();
|
|
||||||
|
|
||||||
|
final match = await createMatch();
|
||||||
|
|
||||||
|
if (isTeamMatch) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
adaptivePageRoute(
|
||||||
|
fullscreenDialog: !isTeamMatch,
|
||||||
|
builder: (context) => CreateTeamsView(
|
||||||
|
match: match,
|
||||||
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: !isTeamMatch,
|
||||||
builder: (context) => MatchResultView(
|
builder: (context) => MatchResultView(
|
||||||
match: match,
|
match: match,
|
||||||
onWinnerChanged: widget.onWinnerChanged,
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
@@ -327,36 +391,12 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
group: selectedGroup,
|
group: selectedGroup,
|
||||||
players: selectedPlayers,
|
players: selectedPlayers,
|
||||||
|
isTeamMatch: isTeamMatch,
|
||||||
game: selectedGame!,
|
game: selectedGame!,
|
||||||
);
|
);
|
||||||
await db.matchDao.addMatch(match: match);
|
|
||||||
|
// Team matches are saved in OrganizeTeamsView
|
||||||
|
if (!isTeamMatch) await db.matchDao.addMatch(match: match);
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a match was provided to the view, this method prefills the input fields
|
|
||||||
void prefillMatchDetails() {
|
|
||||||
final match = widget.matchToEdit!;
|
|
||||||
_matchNameController.text = match.name;
|
|
||||||
selectedPlayers = match.players;
|
|
||||||
selectedGame = match.game;
|
|
||||||
|
|
||||||
if (match.group != null) {
|
|
||||||
selectedGroup = match.group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the selected players are from the currently selected group,
|
|
||||||
// the group is also deselected.
|
|
||||||
Future<void> removeGroupWhenNoMemberLeft() async {
|
|
||||||
if (selectedGroup == null) return;
|
|
||||||
|
|
||||||
if (!selectedPlayers.any(
|
|
||||||
(player) =>
|
|
||||||
selectedGroup!.members.any((member) => member.id == player.id),
|
|
||||||
)) {
|
|
||||||
setState(() {
|
|
||||||
selectedGroup = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.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/match.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_teams/manage_members_view.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/team_creation_tile.dart';
|
||||||
|
|
||||||
|
class CreateTeamsView extends StatefulWidget {
|
||||||
|
const CreateTeamsView({super.key, required this.match, this.onWinnerChanged});
|
||||||
|
|
||||||
|
final Match match;
|
||||||
|
final VoidCallback? onWinnerChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreateTeamsView> createState() => _CreateTeamsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateTeamsViewState extends State<CreateTeamsView> {
|
||||||
|
final Random random = Random();
|
||||||
|
List<Player> get matchPlayers => widget.match.players;
|
||||||
|
|
||||||
|
late List<Team> teams;
|
||||||
|
late List<TextEditingController> nameController;
|
||||||
|
final int initialTeamCount = 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
// Init the teams
|
||||||
|
teams = List.generate(
|
||||||
|
initialTeamCount,
|
||||||
|
(index) => Team(
|
||||||
|
name: '${loc.team} ${index + 1}',
|
||||||
|
color: getTeamColor(index),
|
||||||
|
members: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Init the controllers
|
||||||
|
nameController = teams.map(getNewController).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(title: Text(loc.create_teams)),
|
||||||
|
body: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(top: 12, bottom: 96),
|
||||||
|
itemCount: teams.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return TeamCreationTile(
|
||||||
|
color: teams[index].color,
|
||||||
|
controller: nameController[index],
|
||||||
|
hintText: '${loc.team} ${index + 1}',
|
||||||
|
onDelete: teams.length <= 2 ? null : () => removeTeam(index),
|
||||||
|
onColorSelection: (color) {
|
||||||
|
setState(() {
|
||||||
|
teams[index] = teams[index].copyWith(color: color);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Button row
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom + 20,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Add new team
|
||||||
|
MainMenuButton(
|
||||||
|
icon: Icons.add,
|
||||||
|
text: loc.add_team,
|
||||||
|
onPressed: teams.length >= widget.match.players.length
|
||||||
|
? null
|
||||||
|
: addTeam,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
|
||||||
|
// Confirm teams
|
||||||
|
MainMenuButton(
|
||||||
|
icon: Icons.arrow_forward_sharp,
|
||||||
|
onPressed: teams.length >= 2
|
||||||
|
? () {
|
||||||
|
final match = widget.match.copyWith(teams: teams);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
adaptivePageRoute(
|
||||||
|
builder: (context) => ManageMembersView(
|
||||||
|
match: match,
|
||||||
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new team with a default name and color based on the current number
|
||||||
|
Team getNewTeam() {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Team(
|
||||||
|
name: '${loc.team} ${teams.length + 1}',
|
||||||
|
color: getTeamColor(teams.length),
|
||||||
|
members: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a [TextEditingController] for the given team and sets up a listener
|
||||||
|
/// to update the team's name whenever the text changes.
|
||||||
|
TextEditingController getNewController(Team team) {
|
||||||
|
final textController = TextEditingController(text: team.name);
|
||||||
|
textController.addListener(() {
|
||||||
|
final index = teams.indexWhere((t) => t.id == team.id);
|
||||||
|
if (index == -1) return;
|
||||||
|
teams[index] = teams[index].copyWith(name: textController.text);
|
||||||
|
});
|
||||||
|
return textController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new team to the list of teams, creates a corresponding controller,
|
||||||
|
/// and redistributes the players among all teams.
|
||||||
|
void addTeam() {
|
||||||
|
setState(() {
|
||||||
|
final newTeam = getNewTeam();
|
||||||
|
teams.add(newTeam);
|
||||||
|
nameController.add(getNewController(newTeam));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the team with the given index. If there are less than 2 teams the
|
||||||
|
/// removed team gets replaced with a new one
|
||||||
|
void removeTeam(int index) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
teams.removeAt(index);
|
||||||
|
final removedController = nameController.removeAt(index);
|
||||||
|
removedController.dispose();
|
||||||
|
|
||||||
|
// Update index-based team names and default colors
|
||||||
|
for (int i = 0; i < nameController.length; i++) {
|
||||||
|
if (nameController[i].text.contains(
|
||||||
|
RegExp('^${RegExp.escape(loc.team)} \\d+\$'),
|
||||||
|
)) {
|
||||||
|
nameController[i].text = '${loc.team} ${i + 1}';
|
||||||
|
|
||||||
|
// Reset color to default if it was based on the index
|
||||||
|
final previousIndex = i < index ? i : i + 1;
|
||||||
|
if (teams[i].color == getTeamColor(previousIndex)) {
|
||||||
|
teams[i] = teams[i].copyWith(color: getTeamColor(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (final c in nameController) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import 'dart:core' hide Match;
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_numeric_text/flutter_numeric_text.dart';
|
||||||
|
import 'package:fluttericon/rpg_awesome_icons.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tallee/core/common.dart';
|
||||||
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/data/db/database.dart';
|
||||||
|
import 'package:tallee/data/models/match.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/text_icon_list_tile.dart';
|
||||||
|
|
||||||
|
/// Displays the given [teams] as a flat reorderable list where every team is
|
||||||
|
/// preceded by a header row and followed by its members. Members can be
|
||||||
|
/// dragged across team boundaries to be reassigned to another team.
|
||||||
|
class ManageMembersView extends StatefulWidget {
|
||||||
|
const ManageMembersView({
|
||||||
|
super.key,
|
||||||
|
required this.match,
|
||||||
|
required this.onWinnerChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Match match;
|
||||||
|
|
||||||
|
final VoidCallback? onWinnerChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ManageMembersView> createState() => _ManageMembersViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ManageMembersViewState extends State<ManageMembersView> {
|
||||||
|
late AppDatabase db;
|
||||||
|
|
||||||
|
List<Team> get teams => widget.match.teams!;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
redistributePlayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(title: Text(loc.manage_members)),
|
||||||
|
body: Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: ReorderableListView.builder(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 12, 0, 96),
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
itemCount: allItemsCount,
|
||||||
|
onReorderItem: onReorderItem,
|
||||||
|
proxyDecorator: (child, index, animation) =>
|
||||||
|
Material(type: MaterialType.transparency, child: child),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final teamIndex = teamIndexForFlat(index);
|
||||||
|
final memberIndex = memberIndexForFlat(index, teamIndex);
|
||||||
|
final team = teams[teamIndex];
|
||||||
|
|
||||||
|
if (memberIndex == -1) {
|
||||||
|
return buildTeamTile(team: team);
|
||||||
|
}
|
||||||
|
|
||||||
|
final player = team.members[memberIndex];
|
||||||
|
return ReorderableDelayedDragStartListener(
|
||||||
|
key: ValueKey('player_${player.id}'),
|
||||||
|
index: index,
|
||||||
|
child: TextIconListTile(
|
||||||
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
|
icon: Icons.drag_handle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
MainMenuButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
redistributePlayers();
|
||||||
|
}),
|
||||||
|
icon: Icons.cached,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
MainMenuButton(
|
||||||
|
onPressed: allTeamsHaveMembers
|
||||||
|
? () async => submitMatch()
|
||||||
|
: null,
|
||||||
|
text: loc.create_match,
|
||||||
|
icon: RpgAwesome.clovers_card,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTeamTile({required Team team}) {
|
||||||
|
final color = getColorFromGameColor(team.color);
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
final length = team.members.length;
|
||||||
|
final memberText = length == 1 ? loc.member : loc.members;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
key: ValueKey(team.id),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Color circle
|
||||||
|
Container(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
|
||||||
|
// Team name
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
team.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Member length
|
||||||
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: NumericText(
|
||||||
|
'$length $memberText',
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.hintColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterates through all teams and redistributes players randomly and
|
||||||
|
// as evenly as possible.
|
||||||
|
void redistributePlayers() {
|
||||||
|
for (final team in teams) {
|
||||||
|
team.members.clear();
|
||||||
|
}
|
||||||
|
var matchPlayers = widget.match.players;
|
||||||
|
Random random = Random();
|
||||||
|
|
||||||
|
if (matchPlayers.isEmpty || teams.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final shuffledPlayers = [...matchPlayers]..shuffle(random);
|
||||||
|
|
||||||
|
for (int i = 0; i < shuffledPlayers.length; i++) {
|
||||||
|
final teamIndex = i % teams.length;
|
||||||
|
teams[teamIndex].members.add(shuffledPlayers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles moving a member from one team to another
|
||||||
|
void onReorderItem(int oldIndex, int newIndex) {
|
||||||
|
final sourceTeamIndex = teamIndexForFlat(oldIndex);
|
||||||
|
final sourceMemberIndex = memberIndexForFlat(oldIndex, sourceTeamIndex);
|
||||||
|
|
||||||
|
// Headers themselves can't be reordered.
|
||||||
|
if (sourceMemberIndex == -1) return;
|
||||||
|
|
||||||
|
// When moving down, the target index is shifted by 1
|
||||||
|
// because the item is removed first.
|
||||||
|
var targetIndex = newIndex;
|
||||||
|
if (newIndex > oldIndex) targetIndex -= 1;
|
||||||
|
targetIndex = targetIndex.clamp(0, allItemsCount - 1);
|
||||||
|
|
||||||
|
// Resolve target location based on the item currently
|
||||||
|
// at targetIndex before the move.
|
||||||
|
int destTeamIndex;
|
||||||
|
int insertPositionInTeam;
|
||||||
|
|
||||||
|
if (targetIndex >= allItemsCount - 1 && newIndex >= allItemsCount) {
|
||||||
|
// dropped at the very end, append to the last team.
|
||||||
|
destTeamIndex = teams.length - 1;
|
||||||
|
insertPositionInTeam = teams[destTeamIndex].members.length;
|
||||||
|
} else {
|
||||||
|
destTeamIndex = teamIndexForFlat(targetIndex);
|
||||||
|
final anchorMemberIndex = memberIndexForFlat(targetIndex, destTeamIndex);
|
||||||
|
|
||||||
|
if (anchorMemberIndex == -1) {
|
||||||
|
// dropped on a header, direction decides which team the player gets added
|
||||||
|
// if moving down, insert as first member of that team.
|
||||||
|
// if moving UP, append to the previous team.
|
||||||
|
final isMovingDown = newIndex > oldIndex;
|
||||||
|
if (isMovingDown) {
|
||||||
|
insertPositionInTeam = 0;
|
||||||
|
} else {
|
||||||
|
final previousTeamIndex = destTeamIndex - 1;
|
||||||
|
if (previousTeamIndex < 0) {
|
||||||
|
// above the very first header, stay at top of team 0.
|
||||||
|
insertPositionInTeam = 0;
|
||||||
|
} else {
|
||||||
|
destTeamIndex = previousTeamIndex;
|
||||||
|
insertPositionInTeam = teams[destTeamIndex].members.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertPositionInTeam = anchorMemberIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
final sourceMembers = teams[sourceTeamIndex].members;
|
||||||
|
final player = sourceMembers.removeAt(sourceMemberIndex);
|
||||||
|
|
||||||
|
// Adjust insert index if removed from before the insert point in the
|
||||||
|
// same team.
|
||||||
|
if (sourceTeamIndex == destTeamIndex &&
|
||||||
|
insertPositionInTeam > sourceMembers.length) {
|
||||||
|
insertPositionInTeam = sourceMembers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
teams[destTeamIndex].members.insert(insertPositionInTeam, player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total players + teams length
|
||||||
|
int get allItemsCount {
|
||||||
|
var count = 0;
|
||||||
|
for (final team in teams) {
|
||||||
|
count += 1 + team.members.length;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of the team that owns the flat-list item at [flatIndex].
|
||||||
|
int teamIndexForFlat(int flatIndex) {
|
||||||
|
var remaining = flatIndex;
|
||||||
|
for (var i = 0; i < teams.length; i++) {
|
||||||
|
final size = 1 + teams[i].members.length;
|
||||||
|
if (remaining < size) return i;
|
||||||
|
remaining -= size;
|
||||||
|
}
|
||||||
|
return teams.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the member index within its team, or `-1` if the item at
|
||||||
|
/// [flatIndex] is the team header.
|
||||||
|
int memberIndexForFlat(int flatIndex, int teamIndex) {
|
||||||
|
var offset = 0;
|
||||||
|
for (var i = 0; i < teamIndex; i++) {
|
||||||
|
offset += 1 + teams[i].members.length;
|
||||||
|
}
|
||||||
|
// offset now points to the header of [teamIndex]. Anything beyond is a
|
||||||
|
// member of that team.
|
||||||
|
final localIndex = flatIndex - offset;
|
||||||
|
return localIndex == 0 ? -1 : localIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get allTeamsHaveMembers =>
|
||||||
|
teams.every((team) => team.members.isNotEmpty);
|
||||||
|
|
||||||
|
void submitMatch() async {
|
||||||
|
final match = widget.match;
|
||||||
|
await db.matchDao.addMatch(match: match);
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => MatchResultView(
|
||||||
|
match: match,
|
||||||
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(route) => route.isFirst,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/data/models/match.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart';
|
||||||
|
|
||||||
|
class LiveEditView extends StatefulWidget {
|
||||||
|
const LiveEditView({super.key, required this.match});
|
||||||
|
final Match match;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LiveEditView> createState() => _LiveEditViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LiveEditViewState extends State<LiveEditView> {
|
||||||
|
List<Team> get allTeams =>
|
||||||
|
(widget.match.teams ?? [])..sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
List<Player> get allPlayers =>
|
||||||
|
widget.match.players..sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
List<int> scores = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.match.isTeamMatch) {
|
||||||
|
scores = List.generate(
|
||||||
|
allTeams.length,
|
||||||
|
(index) => allTeams[index].score ?? 0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scores = List.generate(
|
||||||
|
allPlayers.length,
|
||||||
|
(index) => widget.match.scores[allPlayers[index].id]?.score ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(widget.match.name),
|
||||||
|
leading: HapticIconButton(
|
||||||
|
onPressed: () => Navigator.pop(context, scores),
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(child: buildLiveEditWidget(widget.match.isTeamMatch)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildLiveEditWidget(bool isTeamMatch) {
|
||||||
|
if (isTeamMatch) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: allTeams.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return LiveEditListTile(
|
||||||
|
title: allTeams[index].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
scores[index] = value;
|
||||||
|
},
|
||||||
|
value: scores[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return LiveEditListTile(
|
||||||
|
title: allPlayers[index].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
scores[index] = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
value: scores[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import 'package:tallee/presentation/views/main_menu/match_view/create_match/crea
|
|||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.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/buttons/main_menu_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/cards/team_card.dart';
|
||||||
import 'package:tallee/presentation/widgets/colored_icon_container.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_alert_dialog.dart';
|
||||||
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||||
@@ -43,13 +44,13 @@ class MatchDetailView extends StatefulWidget {
|
|||||||
class _MatchDetailViewState extends State<MatchDetailView> {
|
class _MatchDetailViewState extends State<MatchDetailView> {
|
||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
|
|
||||||
late Match match;
|
late Match localMatch;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
match = widget.match;
|
localMatch = widget.match;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -83,7 +84,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
),
|
),
|
||||||
).then((confirmed) async {
|
).then((confirmed) async {
|
||||||
if (confirmed! && context.mounted) {
|
if (confirmed! && context.mounted) {
|
||||||
await db.matchDao.deleteMatch(matchId: match.id);
|
await db.matchDao.deleteMatch(matchId: localMatch.id);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
widget.onMatchUpdate.call();
|
widget.onMatchUpdate.call();
|
||||||
@@ -117,7 +118,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
|
|
||||||
// Match Name
|
// Match Name
|
||||||
Text(
|
Text(
|
||||||
match.name,
|
localMatch.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -129,7 +130,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
|
|
||||||
// Creation Date
|
// Creation Date
|
||||||
Text(
|
Text(
|
||||||
'${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(match.createdAt)}',
|
'${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(localMatch.createdAt)}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: CustomTheme.textColor,
|
color: CustomTheme.textColor,
|
||||||
@@ -139,14 +140,14 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// Group Name
|
// Group Name
|
||||||
if (match.group != null) ...[
|
if (localMatch.group != null) ...[
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.group),
|
const Icon(Icons.group),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${match.group!.name}${getExtraPlayerCount(match)}',
|
'${localMatch.group!.name}${getExtraPlayerCount(localMatch)}',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -154,25 +155,60 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Players
|
// Teams or Players
|
||||||
InfoTile(
|
if (localMatch.isTeamMatch) ...[
|
||||||
title: loc.players,
|
// Teams
|
||||||
icon: Icons.people,
|
InfoTile(
|
||||||
horizontalAlignment: CrossAxisAlignment.start,
|
title: loc.teams,
|
||||||
content: Wrap(
|
icon: Icons.scoreboard,
|
||||||
alignment: WrapAlignment.start,
|
horizontalAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
content:
|
||||||
spacing: 12,
|
localMatch.teams != null && localMatch.teams!.isNotEmpty
|
||||||
runSpacing: 8,
|
? Wrap(
|
||||||
children: match.players.map((player) {
|
alignment: WrapAlignment.start,
|
||||||
return TextIconTile(
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
text: player.name,
|
spacing: 12,
|
||||||
suffixText: getNameCountText(player),
|
runSpacing: 8,
|
||||||
iconEnabled: false,
|
children: (localMatch.teams ?? []).map((team) {
|
||||||
);
|
return TeamCard(team: team);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
loc.no_teams_available,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
] else ...[
|
||||||
|
// Players
|
||||||
|
InfoTile(
|
||||||
|
title: loc.players,
|
||||||
|
icon: Icons.people,
|
||||||
|
horizontalAlignment: CrossAxisAlignment.start,
|
||||||
|
content: localMatch.players.isNotEmpty
|
||||||
|
? Wrap(
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: localMatch.players.map((player) {
|
||||||
|
return TextIconTile(
|
||||||
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
loc.no_players_available,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
// Game
|
// Game
|
||||||
@@ -186,12 +222,12 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
),
|
),
|
||||||
child: GameLabel(
|
child: GameLabel(
|
||||||
title: match.game.name,
|
title: localMatch.game.name,
|
||||||
description: translateRulesetToString(
|
description: translateRulesetToString(
|
||||||
match.game.ruleset,
|
localMatch.game.ruleset,
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
color: match.game.color,
|
color: localMatch.game.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -222,7 +258,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (context) => CreateMatchView(
|
builder: (context) => CreateMatchView(
|
||||||
matchToEdit: match,
|
matchToEdit: localMatch,
|
||||||
onMatchUpdated: onMatchUpdated,
|
onMatchUpdated: onMatchUpdated,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -238,12 +274,10 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (context) => MatchResultView(
|
builder: (context) => MatchResultView(
|
||||||
match: match,
|
match: localMatch,
|
||||||
onWinnerChanged: () {
|
onWinnerChanged: () async {
|
||||||
widget.onMatchUpdate.call();
|
widget.onMatchUpdate.call();
|
||||||
setState(() {
|
await updateScoresForCurrentMatch();
|
||||||
updateScoresForCurrentMatch();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -263,7 +297,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
/// updates the match in this view
|
/// updates the match in this view
|
||||||
void onMatchUpdated(Match editedMatch) {
|
void onMatchUpdated(Match editedMatch) {
|
||||||
setState(() {
|
setState(() {
|
||||||
match = editedMatch;
|
localMatch = editedMatch;
|
||||||
});
|
});
|
||||||
widget.onMatchUpdate.call();
|
widget.onMatchUpdate.call();
|
||||||
}
|
}
|
||||||
@@ -284,95 +318,113 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
/// Returns the result row for single winner/loser rulesets or a placeholder
|
/// Returns the result row for single winner/loser rulesets or a placeholder
|
||||||
/// if no result is entered yet
|
/// if no result is entered yet
|
||||||
List<Widget> getSingleResultRow(AppLocalizations loc) {
|
List<Widget> getSingleResultRow(AppLocalizations loc) {
|
||||||
if (match.mvp.isNotEmpty) {
|
final ruleset = localMatch.game.ruleset;
|
||||||
final ruleset = match.game.ruleset;
|
|
||||||
|
|
||||||
if (ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser) {
|
if (localMatch.mvp.isNotEmpty || localMatch.mvt.isNotEmpty) {
|
||||||
return [
|
// Single winner/loser, multiple winner
|
||||||
Text(
|
final names = localMatch.isTeamMatch
|
||||||
ruleset == Ruleset.singleWinner ? loc.winner : loc.loser,
|
? localMatch.mvt.map((t) => t.name).toList()
|
||||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
: localMatch.mvp.map((p) => p.name).toList();
|
||||||
),
|
final mvpNames = names.length == 1 ? names.first : names.join(', ');
|
||||||
Text(
|
|
||||||
match.mvp.first.name,
|
final label = ruleset == Ruleset.singleWinner
|
||||||
|
? loc.winner
|
||||||
|
: ruleset == Ruleset.singleLoser
|
||||||
|
? loc.loser
|
||||||
|
: loc.winners;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Text(
|
||||||
|
mvpNames,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: CustomTheme.primaryColor,
|
color: CustomTheme.primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
),
|
||||||
} else if (match.game.ruleset == Ruleset.multipleWinners) {
|
];
|
||||||
return [
|
} else {
|
||||||
Text(
|
// No result yet
|
||||||
loc.winners,
|
return [
|
||||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
Text(
|
||||||
),
|
loc.no_results_entered_yet,
|
||||||
Flexible(
|
style: const TextStyle(fontSize: 14, color: CustomTheme.textColor),
|
||||||
child: Container(
|
),
|
||||||
padding: const EdgeInsets.only(left: 10),
|
];
|
||||||
child: Text(
|
|
||||||
match.mvp.map((player) => player.name).join(', '),
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: CustomTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No results yet
|
|
||||||
return [
|
|
||||||
Text(
|
|
||||||
loc.no_results_entered_yet,
|
|
||||||
style: const TextStyle(fontSize: 14, color: CustomTheme.textColor),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the result widget for scores or placement
|
/// Returns the result widget for scores or placement
|
||||||
Widget getMultiResultRows(AppLocalizations loc) {
|
Widget getMultiResultRows(AppLocalizations loc) {
|
||||||
List<(String, int)> playerScores = [];
|
List<(String, int)> scores = getSortedScores();
|
||||||
for (var player in match.players) {
|
|
||||||
int score = match.scores[player.id]?.score ?? 0;
|
|
||||||
playerScores.add((player.name, score));
|
|
||||||
}
|
|
||||||
|
|
||||||
final ruleset = match.game.ruleset;
|
|
||||||
|
|
||||||
if (ruleset == Ruleset.highestScore || ruleset == Ruleset.placement) {
|
|
||||||
playerScores.sort((a, b) => b.$2.compareTo(a.$2));
|
|
||||||
} else if (ruleset == Ruleset.lowestScore) {
|
|
||||||
playerScores.sort((a, b) => a.$2.compareTo(b.$2));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < playerScores.length; i++)
|
for (var i = 0; i < scores.length; i++)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
playerScores[i].$1,
|
scores[i].$1,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: CustomTheme.textColor,
|
color: CustomTheme.textColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
getResultValueText(loc, i, playerScores[i].$2),
|
getResultValueText(loc, i, scores[i].$2),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a list of player/team names and their corresponding scores, sorted by score according to the ruleset
|
||||||
|
List<(String, int)> getSortedScores() {
|
||||||
|
List<(String, int)> namedScores = [];
|
||||||
|
|
||||||
|
if (localMatch.isTeamMatch) {
|
||||||
|
final teams = localMatch.teams ?? [];
|
||||||
|
for (var team in teams) {
|
||||||
|
int score = team.score ?? 0;
|
||||||
|
namedScores.add((team.name, score));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ruleset = localMatch.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.highestScore || ruleset == Ruleset.placement) {
|
||||||
|
namedScores.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
|
} else if (ruleset == Ruleset.lowestScore) {
|
||||||
|
namedScores.sort((a, b) => a.$2.compareTo(b.$2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final scores = localMatch.scores;
|
||||||
|
for (var player in localMatch.players) {
|
||||||
|
int score = scores[player.id]?.score ?? 0;
|
||||||
|
namedScores.add((player.name, score));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ruleset = localMatch.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.highestScore || ruleset == Ruleset.placement) {
|
||||||
|
namedScores.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
|
} else if (ruleset == Ruleset.lowestScore) {
|
||||||
|
namedScores.sort((a, b) => a.$2.compareTo(b.$2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namedScores;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the text widget for the score or placement value, styled according to the ruleset
|
||||||
Widget getResultValueText(AppLocalizations loc, int index, int score) {
|
Widget getResultValueText(AppLocalizations loc, int index, int score) {
|
||||||
final ruleset = match.game.ruleset;
|
final ruleset = localMatch.game.ruleset;
|
||||||
|
|
||||||
if (ruleset == Ruleset.placement) {
|
if (ruleset == Ruleset.placement) {
|
||||||
return Text(
|
return Text(
|
||||||
@@ -410,9 +462,9 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
|
|
||||||
// Returns if the result can be displayed in a single row
|
// Returns if the result can be displayed in a single row
|
||||||
bool isSingleRowResult() {
|
bool isSingleRowResult() {
|
||||||
return match.game.ruleset == Ruleset.singleWinner ||
|
return localMatch.game.ruleset == Ruleset.singleWinner ||
|
||||||
match.game.ruleset == Ruleset.singleLoser ||
|
localMatch.game.ruleset == Ruleset.singleLoser ||
|
||||||
match.game.ruleset == Ruleset.multipleWinners;
|
localMatch.game.ruleset == Ruleset.multipleWinners;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPlacementText(BuildContext context, int rank) {
|
String getPlacementText(BuildContext context, int rank) {
|
||||||
@@ -443,9 +495,19 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateScoresForCurrentMatch() {
|
Future<void> updateScoresForCurrentMatch() async {
|
||||||
db.scoreEntryDao
|
if (localMatch.isTeamMatch) {
|
||||||
.getAllMatchScores(matchId: match.id)
|
final teams = await db.teamDao.getTeamsByMatchId(matchId: localMatch.id);
|
||||||
.then((scores) => match.scores = scores);
|
setState(() {
|
||||||
|
localMatch = localMatch.copyWith(teams: teams);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
final scores = await db.scoreEntryDao.getAllMatchScores(
|
||||||
|
matchId: localMatch.id,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
localMatch = localMatch.copyWith(scores: scores);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
game: Game(
|
game: Game(
|
||||||
name: 'Game name',
|
name: 'Game name',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
),
|
),
|
||||||
group: Group(
|
group: Group(
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const allDependencies = <Package>[
|
|||||||
_cli_util,
|
_cli_util,
|
||||||
_clock,
|
_clock,
|
||||||
_code_assets,
|
_code_assets,
|
||||||
|
_code_builder,
|
||||||
_collection,
|
_collection,
|
||||||
_convert,
|
_convert,
|
||||||
_coverage,
|
_coverage,
|
||||||
@@ -153,7 +154,6 @@ const allDependencies = <Package>[
|
|||||||
_source_map_stack_trace,
|
_source_map_stack_trace,
|
||||||
_source_maps,
|
_source_maps,
|
||||||
_source_span,
|
_source_span,
|
||||||
_sqlcipher_flutter_libs,
|
|
||||||
_sqlite3,
|
_sqlite3,
|
||||||
_sqlite3_flutter_libs,
|
_sqlite3_flutter_libs,
|
||||||
_sqlparser,
|
_sqlparser,
|
||||||
@@ -670,17 +670,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// build_runner 2.15.0
|
/// build_runner 2.13.1
|
||||||
const _build_runner = Package(
|
const _build_runner = Package(
|
||||||
name: 'build_runner',
|
name: 'build_runner',
|
||||||
description: 'A build system for Dart code generation and modular compilation.',
|
description: 'A build system for Dart code generation and modular compilation.',
|
||||||
repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
|
repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '2.15.0',
|
version: '2.13.1',
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel'), PackageRef('yaml')],
|
dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('built_collection'), PackageRef('built_value'), PackageRef('code_builder'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel'), PackageRef('yaml')],
|
||||||
devDependencies: [PackageRef('stream_channel'), PackageRef('test')],
|
devDependencies: [PackageRef('stream_channel'), PackageRef('test')],
|
||||||
license: '''Copyright 2016, the Dart project authors.
|
license: '''Copyright 2016, the Dart project authors.
|
||||||
|
|
||||||
@@ -1510,6 +1510,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// code_builder 4.11.1
|
||||||
|
const _code_builder = Package(
|
||||||
|
name: 'code_builder',
|
||||||
|
description: 'A fluent, builder-based library for generating valid Dart code.',
|
||||||
|
repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder',
|
||||||
|
authors: [],
|
||||||
|
version: '4.11.1',
|
||||||
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
|
isMarkdown: false,
|
||||||
|
isSdk: false,
|
||||||
|
dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('matcher'), PackageRef('meta')],
|
||||||
|
devDependencies: [PackageRef('build'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('source_gen'), PackageRef('test')],
|
||||||
|
license: '''Copyright 2016, the Dart project authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
|
);
|
||||||
|
|
||||||
/// collection 1.19.1
|
/// collection 1.19.1
|
||||||
const _collection = Package(
|
const _collection = Package(
|
||||||
name: 'collection',
|
name: 'collection',
|
||||||
@@ -2540,14 +2581,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// drift 2.33.0
|
/// drift 2.31.0
|
||||||
const _drift = Package(
|
const _drift = Package(
|
||||||
name: 'drift',
|
name: 'drift',
|
||||||
description: 'Drift is a reactive library to store relational data in Dart and Flutter applications.',
|
description: 'Drift is a reactive library to store relational data in Dart and Flutter applications.',
|
||||||
homepage: 'https://drift.simonbinder.eu/',
|
homepage: 'https://drift.simonbinder.eu/',
|
||||||
repository: 'https://github.com/simolus3/drift',
|
repository: 'https://github.com/simolus3/drift',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '2.33.0',
|
version: '2.31.0',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -2576,14 +2617,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// drift_dev 2.33.0
|
/// drift_dev 2.31.0
|
||||||
const _drift_dev = Package(
|
const _drift_dev = Package(
|
||||||
name: 'drift_dev',
|
name: 'drift_dev',
|
||||||
description: 'Dev-dependency for users of drift. Contains the generator and development tools.',
|
description: 'Dev-dependency for users of drift. Contains the generator and development tools.',
|
||||||
homepage: 'https://drift.simonbinder.eu/',
|
homepage: 'https://drift.simonbinder.eu/',
|
||||||
repository: 'https://github.com/simolus3/drift',
|
repository: 'https://github.com/simolus3/drift',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '2.33.0',
|
version: '2.31.0',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -2612,18 +2653,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// drift_flutter 0.3.0
|
/// drift_flutter 0.2.8
|
||||||
const _drift_flutter = Package(
|
const _drift_flutter = Package(
|
||||||
name: 'drift_flutter',
|
name: 'drift_flutter',
|
||||||
description: 'Easily set up drift databases across platforms in Flutter apps.',
|
description: 'Easily set up drift databases across platforms in Flutter apps.',
|
||||||
homepage: 'https://drift.simonbinder.eu/',
|
homepage: 'https://drift.simonbinder.eu/',
|
||||||
repository: 'https://github.com/simolus3/drift',
|
repository: 'https://github.com/simolus3/drift',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.3.0',
|
version: '0.2.8',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('drift'), PackageRef('flutter'), PackageRef('meta'), PackageRef('path'), PackageRef('path_provider'), PackageRef('sqlite3'), PackageRef('sqlite3_flutter_libs'), PackageRef('sqlcipher_flutter_libs')],
|
dependencies: [PackageRef('drift'), PackageRef('flutter'), PackageRef('meta'), PackageRef('path'), PackageRef('path_provider'), PackageRef('sqlite3'), PackageRef('sqlite3_flutter_libs')],
|
||||||
devDependencies: [PackageRef('build_runner'), PackageRef('drift_dev'), PackageRef('lints'), PackageRef('test'), PackageRef('flutter_test'), PackageRef('async')],
|
devDependencies: [PackageRef('build_runner'), PackageRef('drift_dev'), PackageRef('lints'), PackageRef('test'), PackageRef('flutter_test'), PackageRef('async')],
|
||||||
license: '''MIT License
|
license: '''MIT License
|
||||||
|
|
||||||
@@ -3016,18 +3057,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// file_saver 0.4.0
|
/// file_saver 0.3.1
|
||||||
const _file_saver = Package(
|
const _file_saver = Package(
|
||||||
name: 'file_saver',
|
name: 'file_saver',
|
||||||
description: 'Save files from bytes, paths, streams, and URLs across Android, iOS, Web, Windows, macOS, and Linux.',
|
description: 'A Flutter plugin for saving files across all platforms (Android, iOS, Web, Windows, macOS, Linux). Save files from bytes, File objects, file paths, or download from URLs with a single method call. Features include MIME type support, Dio integration, and platform-specific save locations. Supports saveAs() dialog for user-selected locations on supported platforms.',
|
||||||
homepage: 'https://hassanansari.dev',
|
homepage: 'https://hassanansari.dev',
|
||||||
repository: 'https://github.com/incrediblezayed/file_saver',
|
repository: 'https://github.com/incrediblezayed/file_saver',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.4.0',
|
version: '0.3.1',
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('collection'), PackageRef('dio'), PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('path_provider'), PackageRef('web')],
|
dependencies: [PackageRef('collection'), PackageRef('dio'), PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('path_provider'), PackageRef('path_provider_linux'), PackageRef('path_provider_windows'), PackageRef('web')],
|
||||||
devDependencies: [PackageRef('flutter_lints'), PackageRef('flutter_test')],
|
devDependencies: [PackageRef('flutter_lints'), PackageRef('flutter_test')],
|
||||||
license: '''BSD 3-Clause License
|
license: '''BSD 3-Clause License
|
||||||
|
|
||||||
@@ -37642,264 +37683,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// sqlcipher_flutter_libs 0.7.0+eol
|
/// sqlite3 2.9.4
|
||||||
const _sqlcipher_flutter_libs = Package(
|
|
||||||
name: 'sqlcipher_flutter_libs',
|
|
||||||
description: 'Not used anymore, update to version 3.x of package:sqlite3 instead',
|
|
||||||
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/legacy/sqlcipher_flutter_libs',
|
|
||||||
authors: [],
|
|
||||||
version: '0.7.0+eol',
|
|
||||||
spdxIdentifiers: ['Pixar', 'MIT', 'BSD-3-Clause-HP'],
|
|
||||||
isMarkdown: false,
|
|
||||||
isSdk: false,
|
|
||||||
dependencies: [],
|
|
||||||
devDependencies: [],
|
|
||||||
license: '''sqlcipher_flutter_libs
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Simon Binder
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
OpenSSL
|
|
||||||
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
https://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
SQLCipher
|
|
||||||
|
|
||||||
Copyright (c) 2008-2020 Zetetic LLC
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the ZETETIC LLC nor the
|
|
||||||
names of its contributors may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
|
|
||||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
|
||||||
);
|
|
||||||
|
|
||||||
/// sqlite3 3.3.1
|
|
||||||
const _sqlite3 = Package(
|
const _sqlite3 = Package(
|
||||||
name: 'sqlite3',
|
name: 'sqlite3',
|
||||||
description: 'Provides lightweight yet convenient bindings to SQLite by using dart:ffi',
|
description: 'Provides lightweight yet convenient bindings to SQLite by using dart:ffi',
|
||||||
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3',
|
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '3.3.1',
|
version: '2.9.4',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('collection'), PackageRef('ffi'), PackageRef('meta'), PackageRef('path'), PackageRef('web'), PackageRef('typed_data'), PackageRef('hooks'), PackageRef('code_assets'), PackageRef('native_toolchain_c'), PackageRef('crypto')],
|
dependencies: [PackageRef('collection'), PackageRef('ffi'), PackageRef('meta'), PackageRef('path'), PackageRef('web'), PackageRef('typed_data')],
|
||||||
devDependencies: [PackageRef('analyzer'), PackageRef('build_daemon'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('http'), PackageRef('lints'), PackageRef('shelf'), PackageRef('shelf_static'), PackageRef('stream_channel'), PackageRef('test'), PackageRef('pub_semver'), PackageRef('convert'), PackageRef('package_config'), PackageRef('logging')],
|
devDependencies: [PackageRef('analyzer'), PackageRef('build_daemon'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('http'), PackageRef('lints'), PackageRef('shelf'), PackageRef('shelf_static'), PackageRef('stream_channel'), PackageRef('test'), PackageRef('pub_semver'), PackageRef('convert')],
|
||||||
license: '''MIT License
|
license: '''MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 Simon Binder
|
Copyright (c) 2020 Simon Binder
|
||||||
@@ -37923,17 +37718,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// sqlite3_flutter_libs 0.6.0+eol
|
/// sqlite3_flutter_libs 0.5.42
|
||||||
const _sqlite3_flutter_libs = Package(
|
const _sqlite3_flutter_libs = Package(
|
||||||
name: 'sqlite3_flutter_libs',
|
name: 'sqlite3_flutter_libs',
|
||||||
description: 'Not used anymore, update to version 3.x of package:sqlite3 instead',
|
description: 'Flutter plugin to include native sqlite3 libraries with your app',
|
||||||
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/legacy/sqlite3_flutter_libs',
|
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/v2/sqlite3_flutter_libs',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.6.0+eol',
|
version: '0.5.42',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [],
|
dependencies: [PackageRef('flutter')],
|
||||||
devDependencies: [],
|
devDependencies: [],
|
||||||
license: '''MIT License
|
license: '''MIT License
|
||||||
|
|
||||||
@@ -37958,14 +37753,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// sqlparser 0.44.4
|
/// sqlparser 0.43.1
|
||||||
const _sqlparser = Package(
|
const _sqlparser = Package(
|
||||||
name: 'sqlparser',
|
name: 'sqlparser',
|
||||||
description: 'Parses sqlite statements and performs static analysis on them',
|
description: 'Parses sqlite statements and performs static analysis on them',
|
||||||
homepage: 'https://github.com/simolus3/drift/tree/develop/sqlparser',
|
homepage: 'https://github.com/simolus3/drift/tree/develop/sqlparser',
|
||||||
repository: 'https://github.com/simolus3/drift',
|
repository: 'https://github.com/simolus3/drift',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.44.4',
|
version: '0.43.1',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -39620,12 +39415,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// tallee 0.0.35+283
|
/// tallee 0.0.33+273
|
||||||
const _tallee = Package(
|
const _tallee = Package(
|
||||||
name: 'tallee',
|
name: 'tallee',
|
||||||
description: 'Tracking App for Card Games',
|
description: 'Tracking App for Card Games',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.0.35+283',
|
version: '0.0.33+273',
|
||||||
spdxIdentifiers: ['LGPL-3.0'],
|
spdxIdentifiers: ['LGPL-3.0'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ class AnimatedDialogButton extends StatefulWidget {
|
|||||||
this.buttonConstraints,
|
this.buttonConstraints,
|
||||||
this.buttonType = ButtonType.primary,
|
this.buttonType = ButtonType.primary,
|
||||||
this.isDescructive = false,
|
this.isDescructive = false,
|
||||||
|
this.content,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String buttonText;
|
final String buttonText;
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
final BoxConstraints? buttonConstraints;
|
final BoxConstraints? buttonConstraints;
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ class AnimatedDialogButton extends StatefulWidget {
|
|||||||
|
|
||||||
final bool isDescructive;
|
final bool isDescructive;
|
||||||
|
|
||||||
|
final Widget? content;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AnimatedDialogButton> createState() => _AnimatedDialogButtonState();
|
State<AnimatedDialogButton> createState() => _AnimatedDialogButtonState();
|
||||||
}
|
}
|
||||||
@@ -38,28 +41,40 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textStyling = _getTextStyling();
|
final textStyling = _getTextStyling();
|
||||||
final buttonDecoration = _getButtonDecoration();
|
final buttonDecoration = _getButtonDecoration();
|
||||||
|
final isDisabled = widget.onPressed == null;
|
||||||
|
|
||||||
return GestureDetector(
|
return IgnorePointer(
|
||||||
onTapDown: (_) => setState(() => _isPressed = true),
|
ignoring: isDisabled,
|
||||||
onTapUp: (_) => setState(() => _isPressed = false),
|
child: Opacity(
|
||||||
onTapCancel: () => setState(() => _isPressed = false),
|
opacity: isDisabled ? 0.4 : 1.0,
|
||||||
onTap: widget.onPressed,
|
child: GestureDetector(
|
||||||
child: AnimatedScale(
|
onTapDown: (_) => setState(() => _isPressed = true),
|
||||||
scale: _isPressed ? 0.95 : 1.0,
|
onTapUp: (_) => setState(() => _isPressed = false),
|
||||||
duration: const Duration(milliseconds: 100),
|
onTapCancel: () => setState(() => _isPressed = false),
|
||||||
child: AnimatedOpacity(
|
onTap: widget.onPressed,
|
||||||
opacity: _isPressed ? 0.6 : 1.0,
|
child: AnimatedScale(
|
||||||
duration: const Duration(milliseconds: 100),
|
scale: _isPressed ? 0.95 : 1.0,
|
||||||
child: Center(
|
duration: const Duration(milliseconds: 100),
|
||||||
child: Container(
|
child: AnimatedOpacity(
|
||||||
constraints: widget.buttonConstraints,
|
opacity: _isPressed ? 0.6 : 1.0,
|
||||||
decoration: buttonDecoration,
|
duration: const Duration(milliseconds: 100),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
child: Center(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
child: Container(
|
||||||
child: Text(
|
constraints: widget.buttonConstraints,
|
||||||
widget.buttonText,
|
decoration: buttonDecoration,
|
||||||
style: textStyling,
|
padding: const EdgeInsets.symmetric(
|
||||||
textAlign: TextAlign.center,
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: widget.buttonText == ''
|
||||||
|
? widget.content!
|
||||||
|
: Text(
|
||||||
|
widget.buttonText,
|
||||||
|
style: textStyling,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
@@ -91,6 +92,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
@@ -128,6 +130,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class MainMenuButton extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// The callback to be invoked when the button is pressed.
|
/// The callback to be invoked when the button is pressed.
|
||||||
final void Function() onPressed;
|
final void Function()? onPressed;
|
||||||
|
|
||||||
/// The icon of the button.
|
/// The icon of the button.
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
@@ -32,9 +32,11 @@ class MainMenuButton extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MainMenuButtonState extends State<MainMenuButton>
|
class _MainMenuButtonState extends State<MainMenuButton>
|
||||||
with SingleTickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
|
late AnimationController _disabledAnimationController;
|
||||||
late Animation<double> _scaleAnimation;
|
late Animation<double> _scaleAnimation;
|
||||||
|
late Animation<double> _disabledScaleAnimation;
|
||||||
|
|
||||||
/// How long the button needs to be pressed to register it as long press
|
/// How long the button needs to be pressed to register it as long press
|
||||||
Timer? _longPressTimer;
|
Timer? _longPressTimer;
|
||||||
@@ -53,45 +55,67 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_disabledAnimationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
|
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
|
||||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_disabledScaleAnimation = Tween<double>(begin: 1.0, end: 0.98).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _disabledAnimationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScaleTransition(
|
return ScaleTransition(
|
||||||
scale: _scaleAnimation,
|
scale: widget.onPressed == null
|
||||||
|
? _disabledScaleAnimation
|
||||||
|
: _scaleAnimation,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTapDown: (_) {
|
onTapDown: (_) {
|
||||||
_animationController.forward();
|
if (widget.onPressed == null) {
|
||||||
if (widget.onLongPressed != null) {
|
_disabledAnimationController.forward();
|
||||||
_longPressTimer = Timer(
|
} else {
|
||||||
const Duration(milliseconds: 400),
|
_animationController.forward();
|
||||||
() async {
|
if (widget.onLongPressed != null) {
|
||||||
_isLongPressing = true;
|
_longPressTimer = Timer(
|
||||||
widget.onLongPressed?.call();
|
const Duration(milliseconds: 400),
|
||||||
await HapticFeedback.heavyImpact();
|
() async {
|
||||||
_repeatTimer = Timer.periodic(
|
_isLongPressing = true;
|
||||||
const Duration(milliseconds: 250),
|
widget.onLongPressed?.call();
|
||||||
(_) async {
|
await HapticFeedback.heavyImpact();
|
||||||
widget.onLongPressed?.call();
|
_repeatTimer = Timer.periodic(
|
||||||
await HapticFeedback.heavyImpact();
|
const Duration(milliseconds: 250),
|
||||||
},
|
(_) async {
|
||||||
);
|
widget.onLongPressed?.call();
|
||||||
},
|
await HapticFeedback.heavyImpact();
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTapUp: (_) async {
|
onTapUp: (_) async {
|
||||||
_cancelTimers();
|
if (widget.onPressed == null) {
|
||||||
if (mounted && !_isLongPressing) {
|
_disabledAnimationController.reverse();
|
||||||
await HapticFeedback.selectionClick();
|
} else {
|
||||||
widget.onPressed();
|
_cancelTimers();
|
||||||
|
if (mounted && !_isLongPressing) {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
|
widget.onPressed?.call();
|
||||||
|
}
|
||||||
|
_isLongPressing = false;
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
await _animationController.reverse();
|
||||||
}
|
}
|
||||||
_isLongPressing = false;
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
await _animationController.reverse();
|
|
||||||
},
|
},
|
||||||
onTapCancel: () {
|
onTapCancel: () {
|
||||||
_isLongPressing = false;
|
_isLongPressing = false;
|
||||||
@@ -100,7 +124,7 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: widget.onPressed == null ? Colors.grey : Colors.white,
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||||
@@ -131,6 +155,7 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_cancelTimers();
|
_cancelTimers();
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
|
_disabledAnimationController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
103
lib/presentation/widgets/cards/team_card.dart
Normal file
103
lib/presentation/widgets/cards/team_card.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/core/common.dart';
|
||||||
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
|
|
||||||
|
class TeamCard extends StatelessWidget {
|
||||||
|
const TeamCard({
|
||||||
|
super.key,
|
||||||
|
required this.team,
|
||||||
|
this.compact = false,
|
||||||
|
this.width = double.infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Team team;
|
||||||
|
|
||||||
|
final bool compact;
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final teamColor = getColorFromGameColor(team.color);
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: teamColor.withAlpha(50),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: teamColor, width: 2),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
team.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
width: 1,
|
||||||
|
height: 14,
|
||||||
|
color: Colors.white.withValues(alpha: 0.35),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.people_alt_rounded, size: 14, color: Colors.white),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'${team.members.length}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: teamColor.withAlpha(50),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: teamColor, width: 2),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
team.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
|
children: team.members.map((player) {
|
||||||
|
return TextIconTile(
|
||||||
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class GameLabel extends StatelessWidget {
|
|||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final GameColor color;
|
final AppColor color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -143,9 +143,9 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
child: TextIconTile(
|
child: TextIconTile(
|
||||||
text: player.name,
|
text: player.name,
|
||||||
suffixText: getNameCountText(player),
|
suffixText: getNameCountText(player),
|
||||||
onIconTap: () {
|
onIconTap: () async {
|
||||||
setState(() async {
|
await HapticFeedback.selectionClick();
|
||||||
await HapticFeedback.selectionClick();
|
setState(() {
|
||||||
// Removes the player from the selection and notifies the parent.
|
// Removes the player from the selection and notifies the parent.
|
||||||
selectedPlayers.remove(player);
|
selectedPlayers.remove(player);
|
||||||
widget.onChanged([...selectedPlayers]);
|
widget.onChanged([...selectedPlayers]);
|
||||||
@@ -252,6 +252,9 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
suggestedPlayers = suggestedPlayers
|
||||||
|
.where((p) => !selectedPlayers.any((sp) => sp.id == p.id))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, use the loaded players from the database.
|
// Otherwise, use the loaded players from the database.
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ class TextInputField extends StatelessWidget {
|
|||||||
filled: true,
|
filled: true,
|
||||||
fillColor: CustomTheme.boxColor,
|
fillColor: CustomTheme.boxColor,
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: const TextStyle(fontSize: 18),
|
|
||||||
counterText: showCounterText ? null : '',
|
counterText: showCounterText ? null : '',
|
||||||
enabledBorder: const OutlineInputBorder(
|
enabledBorder: const OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:tallee/core/enums.dart';
|
|||||||
class GameTile extends StatelessWidget {
|
class GameTile extends StatelessWidget {
|
||||||
/// A list tile widget that displays a title and description, with optional highlighting and badge.
|
/// A list tile widget that displays a title and description, with optional highlighting and badge.
|
||||||
/// - [title]: The title text displayed on the tile.
|
/// - [title]: The title text displayed on the tile.
|
||||||
|
/// - [subtitle]: An optional subtitle displayed under the title.
|
||||||
/// - [description]: The description text displayed below the title.
|
/// - [description]: The description text displayed below the title.
|
||||||
/// - [onTap]: The callback invoked when the tile is tapped.
|
/// - [onTap]: The callback invoked when the tile is tapped.
|
||||||
/// - [onLongPress]: The callback invoked when the tile is tapped.
|
/// - [onLongPress]: The callback invoked when the tile is tapped.
|
||||||
@@ -17,6 +18,7 @@ class GameTile extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
this.subtitle,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.isHighlighted = false,
|
this.isHighlighted = false,
|
||||||
@@ -24,25 +26,20 @@ class GameTile extends StatelessWidget {
|
|||||||
this.badgeColor,
|
this.badgeColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The title text displayed on the tile.
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
/// The description text displayed below the title.
|
final String? subtitle;
|
||||||
|
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
/// The callback invoked when the tile is tapped.
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
/// The callback invoked when the tile is long-pressed.
|
|
||||||
final VoidCallback? onLongPress;
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
/// A boolean to determine if the tile should be highlighted.
|
|
||||||
final bool isHighlighted;
|
final bool isHighlighted;
|
||||||
|
|
||||||
/// Optional text to display in a badge on the right side of the title.
|
|
||||||
final String? badgeText;
|
final String? badgeText;
|
||||||
|
|
||||||
/// Optional color for the badge background.
|
|
||||||
final Color? badgeColor;
|
final Color? badgeColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -51,7 +48,7 @@ class GameTile extends StatelessWidget {
|
|||||||
? (badgeColor!.computeLuminance() > 0.5 ? Colors.black : Colors.white)
|
? (badgeColor!.computeLuminance() > 0.5 ? Colors.black : Colors.white)
|
||||||
: Colors.white;
|
: Colors.white;
|
||||||
|
|
||||||
final gameColor = badgeColor ?? getColorFromGameColor(GameColor.orange);
|
final gameColor = badgeColor ?? getColorFromGameColor(AppColor.orange);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -67,13 +64,14 @@ class GameTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
|
||||||
decoration: !isHighlighted
|
decoration: !isHighlighted
|
||||||
? CustomTheme.standardBoxDecoration
|
? CustomTheme.standardBoxDecoration
|
||||||
: CustomTheme.highlightedBoxDecoration.copyWith(
|
: CustomTheme.highlightedBoxDecoration.copyWith(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: gameColor.withValues(alpha: 0.9),
|
color: gameColor.withValues(alpha: 0.9),
|
||||||
width: 2,
|
width: 2,
|
||||||
|
strokeAlign: BorderSide.strokeAlignCenter,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -118,6 +116,21 @@ class GameTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Title
|
||||||
|
if (subtitle != null && subtitle!.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: CustomTheme.hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
// Badge
|
// Badge
|
||||||
if (badgeText != null) ...[
|
if (badgeText != null) ...[
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ class _GroupTileState extends State<GroupTile> {
|
|||||||
TextIconTile(
|
TextIconTile(
|
||||||
text: member.name,
|
text: member.name,
|
||||||
suffixText: getNameCountText(member),
|
suffixText: getNameCountText(member),
|
||||||
iconEnabled: false,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import 'package:tallee/core/custom_theme.dart';
|
|||||||
class CustomCheckboxListTile extends StatelessWidget {
|
class CustomCheckboxListTile extends StatelessWidget {
|
||||||
const CustomCheckboxListTile({
|
const CustomCheckboxListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.content,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String text;
|
final Widget content;
|
||||||
final bool value;
|
final bool value;
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool> onChanged;
|
||||||
|
|
||||||
@@ -39,16 +39,7 @@ class CustomCheckboxListTile extends StatelessWidget {
|
|||||||
onChanged(v);
|
onChanged(v);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(child: content),
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ class CustomRadioListTile<T> extends StatelessWidget {
|
|||||||
/// - [onContainerTap]: The callback invoked when the container is tapped.
|
/// - [onContainerTap]: The callback invoked when the container is tapped.
|
||||||
const CustomRadioListTile({
|
const CustomRadioListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.content,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onContainerTap,
|
required this.onContainerTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The text to display next to the radio button.
|
/// The text to display next to the radio button.
|
||||||
final String text;
|
final Widget content;
|
||||||
|
|
||||||
/// The value associated with the radio button.
|
/// The value associated with the radio button.
|
||||||
final T value;
|
final T value;
|
||||||
@@ -37,16 +37,7 @@ class CustomRadioListTile<T> extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Radio<T>(value: value, toggleable: true),
|
Radio<T>(value: value, toggleable: true),
|
||||||
Expanded(
|
Expanded(child: content),
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,36 +5,34 @@ import 'package:tallee/l10n/generated/app_localizations.dart';
|
|||||||
|
|
||||||
class ScoreListTile extends StatelessWidget {
|
class ScoreListTile extends StatelessWidget {
|
||||||
/// A custom list tile widget that has a text field for inputting a score.
|
/// A custom list tile widget that has a text field for inputting a score.
|
||||||
/// - [text]: The leading text to be displayed.
|
/// - [content]: The leading Widget to be displayed.
|
||||||
/// - [controller]: The controller for the text field to input the score.
|
/// - [controller]: The controller for the text field to input the score.
|
||||||
const ScoreListTile({
|
const ScoreListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.content,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
|
this.horizontalPadding = 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The text to display next to the radio button.
|
final Widget content;
|
||||||
final String text;
|
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
final double horizontalPadding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||||
decoration: const BoxDecoration(color: CustomTheme.boxColor),
|
decoration: const BoxDecoration(color: CustomTheme.boxColor),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
content,
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:tallee/core/custom_theme.dart';
|
|||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/models/match.dart';
|
import 'package:tallee/data/models/match.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/cards/team_card.dart';
|
||||||
import 'package:tallee/presentation/widgets/game_label.dart';
|
import 'package:tallee/presentation/widgets/game_label.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
|
|
||||||
@@ -17,13 +18,11 @@ class MatchTile extends StatefulWidget {
|
|||||||
/// - [match]: The match data to be displayed.
|
/// - [match]: The match data to be displayed.
|
||||||
/// - [onTap]: The callback invoked when the tile is tapped.
|
/// - [onTap]: The callback invoked when the tile is tapped.
|
||||||
/// - [width]: Optional width for the tile.
|
/// - [width]: Optional width for the tile.
|
||||||
/// - [compact]: Whether to display the tile in a compact mode
|
|
||||||
const MatchTile({
|
const MatchTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.match,
|
required this.match,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.width,
|
this.width,
|
||||||
this.compact = false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The match data to be displayed.
|
/// The match data to be displayed.
|
||||||
@@ -35,9 +34,6 @@ class MatchTile extends StatefulWidget {
|
|||||||
/// Optional width for the tile.
|
/// Optional width for the tile.
|
||||||
final double? width;
|
final double? width;
|
||||||
|
|
||||||
/// Whether to display the tile in a compact mode
|
|
||||||
final bool compact;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MatchTile> createState() => _MatchTileState();
|
State<MatchTile> createState() => _MatchTileState();
|
||||||
}
|
}
|
||||||
@@ -100,40 +96,59 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
] else if (widget.compact) ...[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.person, size: 16, color: Colors.grey),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${match.players.length} ${loc.players}',
|
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
] else ...[
|
] else ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Game + Ruleset Badge
|
// Game + Ruleset Badge
|
||||||
if (!widget.compact)
|
GameLabel(
|
||||||
GameLabel(
|
title: match.game.name,
|
||||||
title: match.game.name,
|
description: translateRulesetToString(
|
||||||
description: translateRulesetToString(
|
match.game.ruleset,
|
||||||
match.game.ruleset,
|
context,
|
||||||
context,
|
|
||||||
),
|
|
||||||
color: match.game.color,
|
|
||||||
),
|
),
|
||||||
|
color: match.game.color,
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Winner / In Progress Info
|
// Winner / In Progress Info
|
||||||
if (match.mvp.isNotEmpty) ...[
|
if (match.isTeamMatch && match.mvt.isNotEmpty) ...[
|
||||||
|
// MVT Display for team matches
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.green.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
getMvpIcon(),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
getMvtText(loc),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
] else if (match.mvp.isNotEmpty) ...[
|
||||||
|
// MVP Display for player matches
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
@@ -167,6 +182,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
] else ...[
|
] else ...[
|
||||||
|
// Match in progress display
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
@@ -205,8 +221,46 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Players List
|
if (match.teams != null &&
|
||||||
if (players.isNotEmpty && widget.compact == false) ...[
|
match.teams!.isNotEmpty &&
|
||||||
|
match.isTeamMatch) ...[
|
||||||
|
// Team display
|
||||||
|
Text(
|
||||||
|
loc.teams,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final useSingleColumn = match.teams!.any(
|
||||||
|
(team) => team.name.length > 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
const spacing = 8.0;
|
||||||
|
final itemWidth = useSingleColumn
|
||||||
|
? constraints.maxWidth
|
||||||
|
: (constraints.maxWidth - spacing) / 2;
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
spacing: spacing,
|
||||||
|
runSpacing: spacing,
|
||||||
|
children: match.teams!.map((team) {
|
||||||
|
return TeamCard(
|
||||||
|
team: team,
|
||||||
|
compact: true,
|
||||||
|
width: itemWidth,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
] else if (players.isNotEmpty) ...[
|
||||||
|
// Player display
|
||||||
Text(
|
Text(
|
||||||
loc.players,
|
loc.players,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -223,10 +277,17 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
return TextIconTile(
|
return TextIconTile(
|
||||||
text: player.name,
|
text: player.name,
|
||||||
suffixText: getNameCountText(player),
|
suffixText: getNameCountText(player),
|
||||||
iconEnabled: false,
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
] else ...[
|
||||||
|
Text(
|
||||||
|
loc.no_players_available,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: CustomTheme.hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -252,6 +313,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the appropriate text based on the match's ruleset and MVP.
|
||||||
String getMvpText(AppLocalizations loc) {
|
String getMvpText(AppLocalizations loc) {
|
||||||
if (widget.match.mvp.isEmpty) return '';
|
if (widget.match.mvp.isEmpty) return '';
|
||||||
final ruleset = widget.match.game.ruleset;
|
final ruleset = widget.match.game.ruleset;
|
||||||
@@ -275,11 +337,41 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
return '${loc.winner}: n.A.';
|
return '${loc.winner}: n.A.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the appropriate text based on the match's ruleset and MVT.
|
||||||
|
String getMvtText(AppLocalizations loc) {
|
||||||
|
if (widget.match.mvt.isEmpty) return '';
|
||||||
|
final ruleset = widget.match.game.ruleset;
|
||||||
|
|
||||||
|
switch (ruleset) {
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return '${loc.loser}: ${widget.match.mvt.first.name}';
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
final mvt = widget.match.mvt;
|
||||||
|
final mvtScore =
|
||||||
|
widget.match.teams!
|
||||||
|
.firstWhere((team) => team.id == mvt.first.id)
|
||||||
|
.score ??
|
||||||
|
0;
|
||||||
|
final mvtNames = mvt.map((team) => team.name).join(', ');
|
||||||
|
return '${loc.winner}: $mvtNames (${getPointLabel(loc, mvtScore)})';
|
||||||
|
case Ruleset.placement:
|
||||||
|
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
final mvtNames = widget.match.mvt.map((team) => team.name).join(', ');
|
||||||
|
return '${loc.winners}: $mvtNames';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the appropriate icon based on the match's ruleset.
|
||||||
Icon getMvpIcon() {
|
Icon getMvpIcon() {
|
||||||
final icon = getRulesetIcon(widget.match.game.ruleset);
|
final icon = getRulesetIcon(widget.match.game.ruleset);
|
||||||
|
|
||||||
switch (widget.match.game.ruleset) {
|
switch (widget.match.game.ruleset) {
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
return Icon(icon, size: 20, color: Colors.amber);
|
return Icon(icon, size: 20, color: Colors.amber);
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return Icon(icon, size: 20, color: Colors.blue);
|
return Icon(icon, size: 20, color: Colors.blue);
|
||||||
@@ -287,8 +379,6 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
return Icon(icon, size: 20, color: Colors.orange);
|
return Icon(icon, size: 20, color: Colors.orange);
|
||||||
case Ruleset.highestScore:
|
case Ruleset.highestScore:
|
||||||
return Icon(icon, size: 20, color: Colors.green);
|
return Icon(icon, size: 20, color: Colors.green);
|
||||||
case Ruleset.multipleWinners:
|
|
||||||
return Icon(icon, size: 20, color: Colors.amber);
|
|
||||||
case Ruleset.placement:
|
case Ruleset.placement:
|
||||||
return Icon(icon, size: 20, color: Colors.deepOrangeAccent);
|
return Icon(icon, size: 20, color: Colors.deepOrangeAccent);
|
||||||
}
|
}
|
||||||
|
|||||||
144
lib/presentation/widgets/tiles/team_creation_tile.dart
Normal file
144
lib/presentation/widgets/tiles/team_creation_tile.dart
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttericon/font_awesome_icons.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/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||||
|
|
||||||
|
class TeamCreationTile extends StatefulWidget {
|
||||||
|
const TeamCreationTile({
|
||||||
|
super.key,
|
||||||
|
required this.color,
|
||||||
|
required this.controller,
|
||||||
|
required this.hintText,
|
||||||
|
this.onDelete,
|
||||||
|
this.onColorSelection,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AppColor color;
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
final String hintText;
|
||||||
|
|
||||||
|
final VoidCallback? onDelete;
|
||||||
|
|
||||||
|
final ValueChanged<AppColor>? onColorSelection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TeamCreationTile> createState() => _TeamCreationTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TeamCreationTileState extends State<TeamCreationTile> {
|
||||||
|
final teamColors = List.generate(
|
||||||
|
AppColor.values.length,
|
||||||
|
(index) => getTeamColor(index),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: CustomTheme.standardMargin,
|
||||||
|
decoration: CustomTheme.standardBoxDecoration,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12,
|
||||||
|
horizontal: 6,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Name input + delete icon
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextInputField(
|
||||||
|
controller: widget.controller,
|
||||||
|
hintText: widget.hintText,
|
||||||
|
maxLength: Constants.MAX_TEAM_NAME_LENGTH,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
HapticIconButton(
|
||||||
|
icon: const Icon(FontAwesome.trash),
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
iconSize: 25,
|
||||||
|
onPressed: widget.onDelete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Color label
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: Text(
|
||||||
|
loc.color,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Color picker
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: teamColors.map((color) {
|
||||||
|
final isSelected = widget.color == color;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
widget.onColorSelection?.call(color);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: getColorFromGameColor(color),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: isSelected
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
required this.text,
|
required this.text,
|
||||||
this.suffixText = '',
|
this.suffixText = '',
|
||||||
this.icon,
|
this.icon,
|
||||||
|
this.color,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
/// The icon to display in the tile.
|
/// The icon to display in the tile.
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
|
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
/// The callback to be invoked when the icon is pressed.
|
/// The callback to be invoked when the icon is pressed.
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
@@ -31,7 +34,17 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
decoration: CustomTheme.standardBoxDecoration,
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Color.lerp(CustomTheme.onBoxColor, color?.withAlpha(10), 0.1) ??
|
||||||
|
CustomTheme.boxColor,
|
||||||
|
border: Border.all(
|
||||||
|
color: color ?? CustomTheme.boxBorderColor,
|
||||||
|
width: color != null ? 2 : 1,
|
||||||
|
strokeAlign: BorderSide.strokeAlignCenter,
|
||||||
|
),
|
||||||
|
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import 'package:tallee/core/custom_theme.dart';
|
|||||||
class TextIconTile extends StatelessWidget {
|
class TextIconTile extends StatelessWidget {
|
||||||
/// A tile widget that displays text with an optional icon that can be tapped.
|
/// A tile widget that displays text with an optional icon that can be tapped.
|
||||||
/// - [text]: The text to display in the tile.
|
/// - [text]: The text to display in the tile.
|
||||||
/// - [iconEnabled]: A boolean to determine if the icon should be displayed.
|
|
||||||
/// - [onIconTap]: The callback to be invoked when the icon is tapped.
|
/// - [onIconTap]: The callback to be invoked when the icon is tapped.
|
||||||
|
/// - [icon]: Optional custom icon. Defaults to [Icons.close].
|
||||||
const TextIconTile({
|
const TextIconTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.text,
|
||||||
this.suffixText = '',
|
this.suffixText = '',
|
||||||
this.iconEnabled = true,
|
|
||||||
this.onIconTap,
|
this.onIconTap,
|
||||||
|
this.icon = Icons.close,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The text to display in the tile.
|
/// The text to display in the tile.
|
||||||
@@ -19,14 +19,16 @@ class TextIconTile extends StatelessWidget {
|
|||||||
|
|
||||||
final String suffixText;
|
final String suffixText;
|
||||||
|
|
||||||
/// A boolean to determine if the icon should be displayed.
|
|
||||||
final bool iconEnabled;
|
|
||||||
|
|
||||||
/// The callback to be invoked when the icon is tapped.
|
/// The callback to be invoked when the icon is tapped.
|
||||||
final VoidCallback? onIconTap;
|
final VoidCallback? onIconTap;
|
||||||
|
|
||||||
|
/// The icon to display. Defaults to [Icons.close].
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final iconEnabled = onIconTap != null;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -65,10 +67,7 @@ class TextIconTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (iconEnabled) ...<Widget>[
|
if (iconEnabled) ...<Widget>[
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
GestureDetector(
|
GestureDetector(onTap: onIconTap, child: Icon(icon, size: 20)),
|
||||||
onTap: onIconTap,
|
|
||||||
child: const Icon(Icons.close, size: 20),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -200,13 +200,9 @@ class DataTransferService {
|
|||||||
.map((id) => playerById[id])
|
.map((id) => playerById[id])
|
||||||
.whereType<Player>()
|
.whereType<Player>()
|
||||||
.toList();
|
.toList();
|
||||||
|
final team = Team.fromJson(map);
|
||||||
|
|
||||||
return Team(
|
return team.copyWith(members: members);
|
||||||
id: map['id'] as String,
|
|
||||||
name: map['name'] as String,
|
|
||||||
members: members,
|
|
||||||
createdAt: DateTime.parse(map['createdAt'] as String),
|
|
||||||
);
|
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +227,7 @@ class DataTransferService {
|
|||||||
final endedAt = map['endedAt'] != null
|
final endedAt = map['endedAt'] != null
|
||||||
? DateTime.parse(map['endedAt'] as String)
|
? DateTime.parse(map['endedAt'] as String)
|
||||||
: null;
|
: null;
|
||||||
|
final isTeamMatch = map['isTeamMatch'] as bool;
|
||||||
final notes = map['notes'] as String? ?? '';
|
final notes = map['notes'] as String? ?? '';
|
||||||
final scoresJson = map['scores'] as Map<String, dynamic>? ?? {};
|
final scoresJson = map['scores'] as Map<String, dynamic>? ?? {};
|
||||||
final scores = scoresJson.map(
|
final scores = scoresJson.map(
|
||||||
@@ -262,6 +259,7 @@ class DataTransferService {
|
|||||||
game: game,
|
game: game,
|
||||||
group: group,
|
group: group,
|
||||||
players: players,
|
players: players,
|
||||||
|
isTeamMatch: isTeamMatch,
|
||||||
teams: teams.isEmpty ? null : teams,
|
teams: teams.isEmpty ? null : teams,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
endedAt: endedAt,
|
endedAt: endedAt,
|
||||||
@@ -278,7 +276,7 @@ class DataTransferService {
|
|||||||
name: 'Unknown',
|
name: 'Unknown',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: '',
|
description: '',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
52
pubspec.lock
52
pubspec.lock
@@ -85,10 +85,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
|
sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.15.0"
|
version: "2.13.1"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -177,6 +177,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
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:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -325,26 +333,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: "8033500116b24398fba0cca0369cc31678cd627c01e41753a61186911cea743e"
|
sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.33.0"
|
version: "2.31.0"
|
||||||
drift_dev:
|
drift_dev:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: drift_dev
|
name: drift_dev
|
||||||
sha256: b3dd5b75e30522a91da8abda9f5bb17230cb038097f6d15fa75d42bb563428aa
|
sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.33.0"
|
version: "2.31.0"
|
||||||
drift_flutter:
|
drift_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift_flutter
|
name: drift_flutter
|
||||||
sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166"
|
sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.2.8"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -389,10 +397,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_saver
|
name: file_saver
|
||||||
sha256: "68c9a085d9bb4546e0a31d1e583a48d7c17a6987d538788ea064f0043b1fc02d"
|
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.3.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1114,38 +1122,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.2"
|
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:
|
sqlite3:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5"
|
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
version: "2.9.4"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454"
|
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0+eol"
|
version: "0.5.42"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978
|
sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.44.4"
|
version: "0.43.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1427,5 +1427,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.12.0 <4.0.0"
|
dart: ">=3.10.3 <4.0.0"
|
||||||
flutter: ">=3.41.0"
|
flutter: ">=3.38.4"
|
||||||
|
|||||||
24
pubspec.yaml
24
pubspec.yaml
@@ -1,19 +1,19 @@
|
|||||||
name: tallee
|
name: tallee
|
||||||
description: "Tracking App for Card Games"
|
description: "Tracking App for Card Games"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.35+283
|
version: 0.0.33+340
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.12.0
|
sdk: ^3.8.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
clock: ^1.1.2
|
clock: ^1.1.2
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
cupertino_icons: ^1.0.9
|
cupertino_icons: ^1.0.6
|
||||||
drift: ^2.33.0
|
drift: ^2.27.0
|
||||||
drift_flutter: ^0.3.0
|
drift_flutter: ^0.2.4
|
||||||
file_picker: ^11.0.2
|
file_picker: ^11.0.2
|
||||||
file_saver: ^0.4.0
|
file_saver: ^0.3.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
@@ -24,20 +24,20 @@ dependencies:
|
|||||||
font_awesome_flutter: ^11.0.0
|
font_awesome_flutter: ^11.0.0
|
||||||
intl: any
|
intl: any
|
||||||
json_schema: ^5.2.2
|
json_schema: ^5.2.2
|
||||||
package_info_plus: ^9.0.1
|
package_info_plus: ^9.0.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
provider: ^6.1.5
|
provider: ^6.1.5
|
||||||
skeletonizer: ^2.1.3
|
skeletonizer: ^2.1.0+1
|
||||||
url_launcher: ^6.3.2
|
url_launcher: ^6.3.2
|
||||||
uuid: ^4.5.3
|
uuid: ^4.5.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
arb_utils: ^0.11.0
|
arb_utils: ^0.11.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.15.0
|
build_runner: ^2.7.0
|
||||||
dart_pubspec_licenses: ^3.2.0
|
dart_pubspec_licenses: ^3.0.14
|
||||||
drift_dev: ^2.33.0
|
drift_dev: ^2.27.0
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ void main() {
|
|||||||
name: 'Test Game',
|
name: 'Test Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: 'A test game',
|
description: 'A test game',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
testMatch1 = Match(
|
testMatch1 = Match(
|
||||||
@@ -507,34 +507,36 @@ void main() {
|
|||||||
deleted = await database.matchDao.deleteAllMatches();
|
deleted = await database.matchDao.deleteAllMatches();
|
||||||
expect(deleted, isFalse);
|
expect(deleted, isFalse);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test('deleteMatchesByGame() deletes all matches for a game', () async {
|
test('deleteMatchesByGame() deletes all matches for a game', () async {
|
||||||
await database.matchDao.addMatch(match: testMatch1);
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
await database.matchDao.addMatch(match: testMatch2);
|
await database.matchDao.addMatch(match: testMatch2);
|
||||||
|
|
||||||
var count = await database.matchDao.getMatchCountByGame(
|
var count = await database.matchDao.getMatchCountByGame(
|
||||||
gameId: testGame.id,
|
gameId: testGame.id,
|
||||||
);
|
);
|
||||||
expect(count, 2);
|
expect(count, 2);
|
||||||
|
|
||||||
final deletedCount = await database.matchDao.deleteMatchesByGame(
|
final deletedCount = await database.matchDao.deleteMatchesByGame(
|
||||||
gameId: testGame.id,
|
gameId: testGame.id,
|
||||||
);
|
);
|
||||||
expect(deletedCount, 2);
|
expect(deletedCount, 2);
|
||||||
|
|
||||||
count = await database.matchDao.getMatchCountByGame(gameId: testGame.id);
|
count = await database.matchDao.getMatchCountByGame(
|
||||||
expect(count, 0);
|
gameId: testGame.id,
|
||||||
|
);
|
||||||
|
expect(count, 0);
|
||||||
|
|
||||||
final allMatches = await database.matchDao.getAllMatches();
|
final allMatches = await database.matchDao.getAllMatches();
|
||||||
expect(allMatches, isEmpty);
|
expect(allMatches, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deleteMatchesByGame() returns 0 for non-existent game', () async {
|
test('deleteMatchesByGame() returns 0 for non-existent game', () async {
|
||||||
final deletedCount = await database.matchDao.deleteMatchesByGame(
|
final deletedCount = await database.matchDao.deleteMatchesByGame(
|
||||||
gameId: 'non-existent-game-id',
|
gameId: 'non-existent-game-id',
|
||||||
);
|
);
|
||||||
expect(deletedCount, 0);
|
expect(deletedCount, 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:core' hide Match;
|
import 'dart:core' hide Match;
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
@@ -49,7 +49,7 @@ void main() {
|
|||||||
testGame = Game(
|
testGame = Game(
|
||||||
name: 'Test Game',
|
name: 'Test Game',
|
||||||
ruleset: Ruleset.highestScore,
|
ruleset: Ruleset.highestScore,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
testMatch1 = Match(
|
testMatch1 = Match(
|
||||||
@@ -327,5 +327,200 @@ void main() {
|
|||||||
expect(deleted, isFalse);
|
expect(deleted, isFalse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('SCORE', () {
|
||||||
|
test('updateTeamScore() works correctly', () async {
|
||||||
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
|
|
||||||
|
final updated = await database.teamDao.updateTeamScore(
|
||||||
|
teamId: testTeam1.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
score: 5,
|
||||||
|
);
|
||||||
|
expect(updated, isTrue);
|
||||||
|
final team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, 5);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set-/removeWinnerTeam() works correctly', () async {
|
||||||
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
|
|
||||||
|
final set = await database.teamDao.setWinnerTeam(
|
||||||
|
teamId: testTeam1.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(set, isTrue);
|
||||||
|
|
||||||
|
var team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, 1);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final removed = await database.teamDao.removeWinnerTeam(
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(removed, isTrue);
|
||||||
|
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, isNull);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNull);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set-/removeLoserTeam() works correctly', () async {
|
||||||
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
|
|
||||||
|
final set = await database.teamDao.setLoserTeam(
|
||||||
|
teamId: testTeam1.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(set, isTrue);
|
||||||
|
|
||||||
|
var team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, 0);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final removed = await database.teamDao.removeLoserTeam(
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(removed, isTrue);
|
||||||
|
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, isNull);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNull);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set-/removeWinnerTeams() works correctly', () async {
|
||||||
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
|
|
||||||
|
final set = await database.teamDao.setWinnerTeams(
|
||||||
|
winners: [testTeam1, testTeam2],
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(set, isTrue);
|
||||||
|
|
||||||
|
// check both teams got the winner score
|
||||||
|
var team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, 1);
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam2.id);
|
||||||
|
expect(team.score, 1);
|
||||||
|
|
||||||
|
// check all members of both teams got the winner score
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final member in testTeam2.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final removed = await database.teamDao.removeWinnerTeam(
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(removed, isTrue);
|
||||||
|
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, isNull);
|
||||||
|
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam2.id);
|
||||||
|
expect(team.score, isNull);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final member in testTeam2.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNull);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setTeamPlacements() works correctly', () async {
|
||||||
|
await database.matchDao.addMatch(match: testMatch1);
|
||||||
|
|
||||||
|
final set = await database.teamDao.setTeamPlacements(
|
||||||
|
teams: [testTeam1, testTeam2],
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(set, isTrue);
|
||||||
|
|
||||||
|
var team = await database.teamDao.getTeamById(teamId: testTeam1.id);
|
||||||
|
expect(team.score, 2);
|
||||||
|
team = await database.teamDao.getTeamById(teamId: testTeam2.id);
|
||||||
|
expect(team.score, 1);
|
||||||
|
|
||||||
|
for (final member in testTeam1.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final member in testTeam2.members) {
|
||||||
|
final entry = await database.scoreEntryDao.getScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: testMatch1.id,
|
||||||
|
);
|
||||||
|
expect(entry, isNotNull);
|
||||||
|
expect(entry!.score, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void main() {
|
|||||||
name: 'Chess',
|
name: 'Chess',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: 'A classic strategy game',
|
description: 'A classic strategy game',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'chess_icon',
|
icon: 'chess_icon',
|
||||||
);
|
);
|
||||||
testGame2 = Game(
|
testGame2 = Game(
|
||||||
@@ -36,7 +36,7 @@ void main() {
|
|||||||
name: 'Poker',
|
name: 'Poker',
|
||||||
ruleset: Ruleset.multipleWinners,
|
ruleset: Ruleset.multipleWinners,
|
||||||
description: 'Card game with multiple winners',
|
description: 'Card game with multiple winners',
|
||||||
color: GameColor.red,
|
color: AppColor.red,
|
||||||
icon: 'poker_icon',
|
icon: 'poker_icon',
|
||||||
);
|
);
|
||||||
testGame3 = Game(
|
testGame3 = Game(
|
||||||
@@ -44,7 +44,7 @@ void main() {
|
|||||||
name: 'Monopoly',
|
name: 'Monopoly',
|
||||||
ruleset: Ruleset.highestScore,
|
ruleset: Ruleset.highestScore,
|
||||||
description: 'A board game about real estate',
|
description: 'A board game about real estate',
|
||||||
color: GameColor.orange,
|
color: AppColor.orange,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -124,7 +124,7 @@ void main() {
|
|||||||
name: 'Game\'s & "Special" <Name>',
|
name: 'Game\'s & "Special" <Name>',
|
||||||
ruleset: Ruleset.multipleWinners,
|
ruleset: Ruleset.multipleWinners,
|
||||||
description: 'Description with émojis 🎮🎲',
|
description: 'Description with émojis 🎮🎲',
|
||||||
color: GameColor.purple,
|
color: AppColor.purple,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
await database.gameDao.addGame(game: specialGame);
|
await database.gameDao.addGame(game: specialGame);
|
||||||
@@ -280,19 +280,19 @@ void main() {
|
|||||||
|
|
||||||
await database.gameDao.updateGameColor(
|
await database.gameDao.updateGameColor(
|
||||||
gameId: testGame1.id,
|
gameId: testGame1.id,
|
||||||
color: GameColor.green,
|
color: AppColor.green,
|
||||||
);
|
);
|
||||||
|
|
||||||
final updatedGame = await database.gameDao.getGameById(
|
final updatedGame = await database.gameDao.getGameById(
|
||||||
gameId: testGame1.id,
|
gameId: testGame1.id,
|
||||||
);
|
);
|
||||||
expect(updatedGame.color, GameColor.green);
|
expect(updatedGame.color, AppColor.green);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateGameColor() does nothing for non-existent game', () async {
|
test('updateGameColor() does nothing for non-existent game', () async {
|
||||||
final updated = await database.gameDao.updateGameColor(
|
final updated = await database.gameDao.updateGameColor(
|
||||||
gameId: 'non-existent-id',
|
gameId: 'non-existent-id',
|
||||||
color: GameColor.green,
|
color: AppColor.green,
|
||||||
);
|
);
|
||||||
expect(updated, isFalse);
|
expect(updated, isFalse);
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ void main() {
|
|||||||
name: newName,
|
name: newName,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newGameColor = GameColor.teal;
|
const newGameColor = AppColor.teal;
|
||||||
await database.gameDao.updateGameColor(
|
await database.gameDao.updateGameColor(
|
||||||
gameId: testGame1.id,
|
gameId: testGame1.id,
|
||||||
color: newGameColor,
|
color: newGameColor,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ void main() {
|
|||||||
name: 'Test Game',
|
name: 'Test Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: 'A test game',
|
description: 'A test game',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
testMatch1 = Match(
|
testMatch1 = Match(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ void main() {
|
|||||||
name: 'Test Game',
|
name: 'Test Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: 'A test game',
|
description: 'A test game',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
);
|
);
|
||||||
testMatch1 = Match(
|
testMatch1 = Match(
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ void main() {
|
|||||||
name: 'Chess',
|
name: 'Chess',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: 'Strategic board game',
|
description: 'Strategic board game',
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'chess_icon',
|
icon: 'chess_icon',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -55,7 +55,12 @@ void main() {
|
|||||||
members: [testPlayer1, testPlayer2],
|
members: [testPlayer1, testPlayer2],
|
||||||
);
|
);
|
||||||
|
|
||||||
testTeam = Team(name: 'Test Team', members: [testPlayer1, testPlayer2]);
|
testTeam = Team(
|
||||||
|
name: 'Test Team',
|
||||||
|
color: AppColor.yellow,
|
||||||
|
score: 5,
|
||||||
|
members: [testPlayer1, testPlayer2],
|
||||||
|
);
|
||||||
|
|
||||||
testMatch = Match(
|
testMatch = Match(
|
||||||
name: 'Test Match',
|
name: 'Test Match',
|
||||||
@@ -137,9 +142,6 @@ void main() {
|
|||||||
await database.playerDao.addPlayer(player: testPlayer2);
|
await database.playerDao.addPlayer(player: testPlayer2);
|
||||||
await database.gameDao.addGame(game: testGame);
|
await database.gameDao.addGame(game: testGame);
|
||||||
await database.groupDao.addGroup(group: testGroup);
|
await database.groupDao.addGroup(group: testGroup);
|
||||||
/*
|
|
||||||
await database.teamDao.addTeam(team: testTeam);
|
|
||||||
*/
|
|
||||||
await database.matchDao.addMatch(match: testMatch);
|
await database.matchDao.addMatch(match: testMatch);
|
||||||
|
|
||||||
final ctx = await getContext(tester);
|
final ctx = await getContext(tester);
|
||||||
@@ -448,19 +450,19 @@ void main() {
|
|||||||
Game(
|
Game(
|
||||||
name: 'Red Game',
|
name: 'Red Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
color: GameColor.red,
|
color: AppColor.red,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
Game(
|
Game(
|
||||||
name: 'Blue Game',
|
name: 'Blue Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
Game(
|
Game(
|
||||||
name: 'Green Game',
|
name: 'Green Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
color: GameColor.green,
|
color: AppColor.green,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -484,19 +486,19 @@ void main() {
|
|||||||
Game(
|
Game(
|
||||||
name: 'Highest Score Game',
|
name: 'Highest Score Game',
|
||||||
ruleset: Ruleset.highestScore,
|
ruleset: Ruleset.highestScore,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
Game(
|
Game(
|
||||||
name: 'Lowest Score Game',
|
name: 'Lowest Score Game',
|
||||||
ruleset: Ruleset.lowestScore,
|
ruleset: Ruleset.lowestScore,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
Game(
|
Game(
|
||||||
name: 'Single Winner',
|
name: 'Single Winner',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
color: GameColor.blue,
|
color: AppColor.blue,
|
||||||
icon: 'icon',
|
icon: 'icon',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -669,6 +671,8 @@ void main() {
|
|||||||
'name': testTeam.name,
|
'name': testTeam.name,
|
||||||
'memberIds': [testPlayer1.id],
|
'memberIds': [testPlayer1.id],
|
||||||
'createdAt': testTeam.createdAt.toIso8601String(),
|
'createdAt': testTeam.createdAt.toIso8601String(),
|
||||||
|
'color': testTeam.color.name,
|
||||||
|
'score': testTeam.score,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -682,6 +686,8 @@ void main() {
|
|||||||
expect(teams[0].name, testTeam.name);
|
expect(teams[0].name, testTeam.name);
|
||||||
expect(teams[0].members.length, 1);
|
expect(teams[0].members.length, 1);
|
||||||
expect(teams[0].members[0].id, testPlayer1.id);
|
expect(teams[0].members[0].id, testPlayer1.id);
|
||||||
|
expect(teams[0].color, testTeam.color);
|
||||||
|
expect(teams[0].score, testTeam.score);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parseTeamsFromJson() empty list', () {
|
test('parseTeamsFromJson() empty list', () {
|
||||||
@@ -718,6 +724,9 @@ void main() {
|
|||||||
'gameId': testGame.id,
|
'gameId': testGame.id,
|
||||||
'groupId': testGroup.id,
|
'groupId': testGroup.id,
|
||||||
'playerIds': [testPlayer1.id, testPlayer2.id],
|
'playerIds': [testPlayer1.id, testPlayer2.id],
|
||||||
|
'isTeamMatch': false,
|
||||||
|
'teams': null,
|
||||||
|
'scores': null,
|
||||||
'notes': testMatch.notes,
|
'notes': testMatch.notes,
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
},
|
},
|
||||||
@@ -773,6 +782,9 @@ void main() {
|
|||||||
'name': testMatch.name,
|
'name': testMatch.name,
|
||||||
'gameId': 'non-existent-game-id',
|
'gameId': 'non-existent-game-id',
|
||||||
'playerIds': [testPlayer1.id],
|
'playerIds': [testPlayer1.id],
|
||||||
|
'isTeamMatch': false,
|
||||||
|
'teams': null,
|
||||||
|
'scores': null,
|
||||||
'notes': '',
|
'notes': '',
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
},
|
},
|
||||||
@@ -804,6 +816,9 @@ void main() {
|
|||||||
'gameId': testGame.id,
|
'gameId': testGame.id,
|
||||||
'groupId': null,
|
'groupId': null,
|
||||||
'playerIds': [testPlayer1.id],
|
'playerIds': [testPlayer1.id],
|
||||||
|
'isTeamMatch': false,
|
||||||
|
'teams': null,
|
||||||
|
'scores': null,
|
||||||
'notes': '',
|
'notes': '',
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
},
|
},
|
||||||
@@ -834,6 +849,9 @@ void main() {
|
|||||||
'name': testMatch.name,
|
'name': testMatch.name,
|
||||||
'gameId': testGame.id,
|
'gameId': testGame.id,
|
||||||
'playerIds': [testPlayer1.id],
|
'playerIds': [testPlayer1.id],
|
||||||
|
'isTeamMatch': false,
|
||||||
|
'teams': null,
|
||||||
|
'scores': null,
|
||||||
'notes': '',
|
'notes': '',
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
'endedAt': endedDate.toIso8601String(),
|
'endedAt': endedDate.toIso8601String(),
|
||||||
@@ -853,7 +871,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validateJsonSchema()', () async {
|
test('validateJsonSchema() works correctly', () async {
|
||||||
final validJson = json.encode({
|
final validJson = json.encode({
|
||||||
'players': [
|
'players': [
|
||||||
{
|
{
|
||||||
@@ -897,6 +915,15 @@ void main() {
|
|||||||
},
|
},
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
'endedAt': null,
|
'endedAt': null,
|
||||||
|
'isTeamMatch': true,
|
||||||
|
'teams': [
|
||||||
|
{
|
||||||
|
'id': testTeam.id,
|
||||||
|
'name': testTeam.name,
|
||||||
|
'memberIds': [testPlayer1.id, testPlayer2.id],
|
||||||
|
'createdAt': testTeam.createdAt.toIso8601String(),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -904,5 +931,28 @@ void main() {
|
|||||||
final isValid = await DataTransferService.validateJsonSchema(validJson);
|
final isValid = await DataTransferService.validateJsonSchema(validJson);
|
||||||
expect(isValid, true);
|
expect(isValid, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('validateJsonSchema() validates exported json file', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer2);
|
||||||
|
await database.gameDao.addGame(game: testGame);
|
||||||
|
await database.groupDao.addGroup(group: testGroup);
|
||||||
|
await database.matchDao.addMatch(match: testMatch);
|
||||||
|
|
||||||
|
final ctx = await getContext(tester);
|
||||||
|
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
|
||||||
|
|
||||||
|
expect(jsonString, isNotEmpty);
|
||||||
|
|
||||||
|
// Schema validation requires real async operations (rootBundle,
|
||||||
|
// HttpClient within json_schema). These must run via
|
||||||
|
// tester.runAsync, otherwise the test hangs due to a pending timer.
|
||||||
|
final isValid = await tester.runAsync(
|
||||||
|
() => DataTransferService.validateJsonSchema(jsonString),
|
||||||
|
);
|
||||||
|
expect(isValid, true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user