Compare commits
23 Commits
feature/13
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| 13c88cb958 | |||
| 6e4375e459 | |||
| 59d1efb4fb | |||
| 611033b5cd | |||
| 4e98dcde41 | |||
| a304d9adf7 | |||
| 23d00c64ab | |||
| 4726d170a1 | |||
| 3fe421676c | |||
| b0b039875a | |||
| 840faab024 | |||
| 6c50eaefc7 | |||
| 4f91130cb5 | |||
| 2214ea8e7d | |||
| 5b7ef4051d | |||
| e3c39521a0 | |||
| b83719f16d | |||
| de0344d63d | |||
| 4ae1432943 | |||
| feb2b756bd | |||
| bfad74db22 | |||
| 8e20fe1034 | |||
| 81aad9280c |
@@ -1,9 +1,5 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||
|
||||
linter:
|
||||
rules:
|
||||
avoid_print: false
|
||||
@@ -15,4 +11,8 @@ linter:
|
||||
prefer_const_literals_to_create_immutables: true
|
||||
unnecessary_const: true
|
||||
lines_longer_than_80_chars: false
|
||||
constant_identifier_names: false
|
||||
constant_identifier_names: false
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||
|
||||
@@ -85,21 +85,21 @@ class CustomTheme {
|
||||
);
|
||||
|
||||
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
||||
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
||||
textStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.textColor)),
|
||||
hintStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.hintColor)),
|
||||
);
|
||||
|
||||
static final RadioThemeData radioTheme = RadioThemeData(
|
||||
fillColor: WidgetStateProperty.resolveWith<Color>((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return primaryColor;
|
||||
return CustomTheme.primaryColor;
|
||||
}
|
||||
return textColor;
|
||||
return CustomTheme.textColor;
|
||||
}),
|
||||
);
|
||||
|
||||
static const InputDecorationTheme inputDecorationTheme = InputDecorationTheme(
|
||||
labelStyle: TextStyle(color: textColor),
|
||||
hintStyle: TextStyle(color: hintColor),
|
||||
labelStyle: TextStyle(color: CustomTheme.textColor),
|
||||
hintStyle: TextStyle(color: CustomTheme.hintColor),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/group_table.dart';
|
||||
import 'package:tallee/data/db/tables/match_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'group_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [GroupTable, PlayerGroupTable])
|
||||
@DriftAccessor(tables: [GroupTable, PlayerGroupTable, MatchTable])
|
||||
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
GroupDao(super.db);
|
||||
|
||||
@@ -205,8 +206,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Retrieves the number of groups in the database.
|
||||
Future<int> getGroupCount() async {
|
||||
final count =
|
||||
@@ -235,10 +234,13 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
/// Replaces all players in a group with the provided list of players.
|
||||
/// Removes all existing players from the group and adds the new players.
|
||||
/// Also adds any new players to the player table if they don't exist.
|
||||
Future<void> replaceGroupPlayers({
|
||||
/// Returns `true` if the group exists and players were replaced, `false` otherwise.
|
||||
Future<bool> replaceGroupPlayers({
|
||||
required String groupId,
|
||||
required List<Player> newPlayers,
|
||||
}) async {
|
||||
if (!await groupExists(groupId: groupId)) return false;
|
||||
|
||||
await db.transaction(() async {
|
||||
// Remove all existing players from the group
|
||||
final deleteQuery = delete(db.playerGroupTable)
|
||||
@@ -270,5 +272,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
),
|
||||
);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ mixin _$GroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||
$PlayerGroupTableTable get playerGroupTable =>
|
||||
attachedDatabase.playerGroupTable;
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||
}
|
||||
|
||||
@@ -268,6 +268,34 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
return count ?? 0;
|
||||
}
|
||||
|
||||
/// Retrieves all matches associated with the given [groupId].
|
||||
/// Queries the database directly, filtering by [groupId].
|
||||
Future<List<Match>> getGroupMatches({required String groupId}) async {
|
||||
final query = select(matchTable)..where((m) => m.groupId.equals(groupId));
|
||||
final rows = await query.get();
|
||||
|
||||
return Future.wait(
|
||||
rows.map((row) async {
|
||||
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
||||
final group = await db.groupDao.getGroupById(groupId: groupId);
|
||||
final players =
|
||||
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
|
||||
final winner = await db.matchDao.getWinner(matchId: row.id);
|
||||
return Match(
|
||||
id: row.id,
|
||||
name: row.name ?? '',
|
||||
game: game,
|
||||
group: group,
|
||||
players: players,
|
||||
notes: row.notes ?? '',
|
||||
createdAt: row.createdAt,
|
||||
endedAt: row.endedAt,
|
||||
winner: winner,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if a match with the given [matchId] exists in the database.
|
||||
/// Returns `true` if the match exists, otherwise `false`.
|
||||
Future<bool> matchExists({required String matchId}) async {
|
||||
@@ -338,6 +366,17 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// Removes the group association of the match with the given [matchId].
|
||||
/// Sets the groupId to null.
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> removeMatchGroup({required String matchId}) async {
|
||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||
final rowsAffected = await query.write(
|
||||
const MatchTableCompanion(groupId: Value(null)),
|
||||
);
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// Updates the createdAt timestamp of the match with the given [matchId].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> updateMatchCreatedAt({
|
||||
|
||||
@@ -1123,7 +1123,7 @@ class $MatchTableTable extends MatchTable
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES group_table (id) ON DELETE CASCADE',
|
||||
'REFERENCES group_table (id) ON DELETE SET NULL',
|
||||
),
|
||||
);
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@@ -2780,7 +2780,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
'group_table',
|
||||
limitUpdateKind: UpdateKind.delete,
|
||||
),
|
||||
result: [TableUpdate('match_table', kind: UpdateKind.delete)],
|
||||
result: [TableUpdate('match_table', kind: UpdateKind.update)],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName(
|
||||
|
||||
@@ -7,13 +7,15 @@ class MatchTable extends Table {
|
||||
TextColumn get gameId =>
|
||||
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
||||
// Nullable if there is no group associated with the match
|
||||
TextColumn get groupId =>
|
||||
text().references(GroupTable, #id, onDelete: KeyAction.cascade).nullable()();
|
||||
TextColumn get name => text().nullable()();
|
||||
// onDelete: If a group gets deleted, groupId in the match gets set to null
|
||||
TextColumn get groupId => text()
|
||||
.references(GroupTable, #id, onDelete: KeyAction.setNull)
|
||||
.nullable()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get notes => text().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get endedAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {id};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,10 @@
|
||||
"days_ago": "vor {count} Tagen",
|
||||
"delete": "Löschen",
|
||||
"delete_all_data": "Alle Daten löschen",
|
||||
"delete_group": "Gruppe löschen",
|
||||
"delete_group": "Diese Gruppe löschen",
|
||||
"delete_match": "Spiel löschen",
|
||||
"edit_group": "Gruppe bearbeiten",
|
||||
"edit_match": "Gruppe bearbeiten",
|
||||
"enter_points": "Punkte eingeben",
|
||||
"enter_results": "Ergebnisse eintragen",
|
||||
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
||||
@@ -75,7 +74,6 @@
|
||||
"player_name": "Spieler:innenname",
|
||||
"players": "Spieler:innen",
|
||||
"players_count": "{count} Spieler",
|
||||
"points": "Punkte",
|
||||
"privacy_policy": "Datenschutzerklärung",
|
||||
"quick_create": "Schnellzugriff",
|
||||
"recent_matches": "Letzte Spiele",
|
||||
@@ -89,14 +87,12 @@
|
||||
"save_changes": "Änderungen speichern",
|
||||
"search_for_groups": "Nach Gruppen suchen",
|
||||
"search_for_players": "Nach Spieler:innen suchen",
|
||||
"select_winner": "Gewinner:in wählen",
|
||||
"select_loser": "Verlierer:in wählen",
|
||||
"select_winner": "Gewinner:in wählen:",
|
||||
"selected_players": "Ausgewählte Spieler:innen",
|
||||
"settings": "Einstellungen",
|
||||
"single_loser": "Ein:e Verlierer:in",
|
||||
"single_winner": "Ein:e Gewinner:in",
|
||||
"highest_score": "Höchste Punkte",
|
||||
"loser": "Verlierer:in",
|
||||
"lowest_score": "Niedrigste Punkte",
|
||||
"multiple_winners": "Mehrere Gewinner:innen",
|
||||
"statistics": "Statistiken",
|
||||
|
||||
@@ -83,9 +83,6 @@
|
||||
"@edit_match": {
|
||||
"description": "Button & Appbar label for editing a match"
|
||||
},
|
||||
"@enter_points": {
|
||||
"description": "Label to enter players points"
|
||||
},
|
||||
"@enter_results": {
|
||||
"description": "Button text to enter match results"
|
||||
},
|
||||
@@ -235,9 +232,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@points": {
|
||||
"description": "Points label"
|
||||
},
|
||||
"@privacy_policy": {
|
||||
"description": "Privacy policy menu item"
|
||||
},
|
||||
@@ -277,9 +271,6 @@
|
||||
"@select_winner": {
|
||||
"description": "Label to select the winner"
|
||||
},
|
||||
"@select_loser": {
|
||||
"description": "Label to select the loser"
|
||||
},
|
||||
"@selected_players": {
|
||||
"description": "Shows the number of selected players"
|
||||
},
|
||||
@@ -360,7 +351,6 @@
|
||||
"delete_match": "Delete Match",
|
||||
"edit_group": "Edit Group",
|
||||
"edit_match": "Edit Match",
|
||||
"enter_points": "Enter points",
|
||||
"enter_results": "Enter Results",
|
||||
"error_creating_group": "Error while creating group, please try again",
|
||||
"error_deleting_group": "Error while deleting group, please try again",
|
||||
@@ -409,7 +399,6 @@
|
||||
"player_name": "Player name",
|
||||
"players": "Players",
|
||||
"players_count": "{count} Players",
|
||||
"points": "Points",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"quick_create": "Quick Create",
|
||||
"recent_matches": "Recent Matches",
|
||||
@@ -422,14 +411,12 @@
|
||||
"save_changes": "Save Changes",
|
||||
"search_for_groups": "Search for groups",
|
||||
"search_for_players": "Search for players",
|
||||
"select_winner": "Select Winner",
|
||||
"select_loser": "Select Loser",
|
||||
"select_winner": "Select Winner:",
|
||||
"selected_players": "Selected players",
|
||||
"settings": "Settings",
|
||||
"single_loser": "Single Loser",
|
||||
"single_winner": "Single Winner",
|
||||
"highest_score": "Highest Score",
|
||||
"loser": "Loser",
|
||||
"lowest_score": "Lowest Score",
|
||||
"multiple_winners": "Multiple Winners",
|
||||
"statistics": "Statistics",
|
||||
|
||||
@@ -254,12 +254,6 @@ abstract class AppLocalizations {
|
||||
/// **'Edit Match'**
|
||||
String get edit_match;
|
||||
|
||||
/// Label to enter players points
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter points'**
|
||||
String get enter_points;
|
||||
|
||||
/// Button text to enter match results
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -548,12 +542,6 @@ abstract class AppLocalizations {
|
||||
/// **'{count} Players'**
|
||||
String players_count(int count);
|
||||
|
||||
/// Points label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Points'**
|
||||
String get points;
|
||||
|
||||
/// Privacy policy menu item
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -629,15 +617,9 @@ abstract class AppLocalizations {
|
||||
/// Label to select the winner
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Winner'**
|
||||
/// **'Select Winner:'**
|
||||
String get select_winner;
|
||||
|
||||
/// Label to select the loser
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Loser'**
|
||||
String get select_loser;
|
||||
|
||||
/// Shows the number of selected players
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -668,12 +650,6 @@ abstract class AppLocalizations {
|
||||
/// **'Highest Score'**
|
||||
String get highest_score;
|
||||
|
||||
/// No description provided for @loser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loser'**
|
||||
String get loser;
|
||||
|
||||
/// No description provided for @lowest_score.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -79,7 +79,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get delete_all_data => 'Alle Daten löschen';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Gruppe löschen';
|
||||
String get delete_group => 'Diese Gruppe löschen';
|
||||
|
||||
@override
|
||||
String get delete_match => 'Spiel löschen';
|
||||
@@ -90,9 +90,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get edit_match => 'Gruppe bearbeiten';
|
||||
|
||||
@override
|
||||
String get enter_points => 'Punkte eingeben';
|
||||
|
||||
@override
|
||||
String get enter_results => 'Ergebnisse eintragen';
|
||||
|
||||
@@ -243,9 +240,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return '$count Spieler';
|
||||
}
|
||||
|
||||
@override
|
||||
String get points => 'Punkte';
|
||||
|
||||
@override
|
||||
String get privacy_policy => 'Datenschutzerklärung';
|
||||
|
||||
@@ -287,10 +281,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get search_for_players => 'Nach Spieler:innen suchen';
|
||||
|
||||
@override
|
||||
String get select_winner => 'Gewinner:in wählen';
|
||||
|
||||
@override
|
||||
String get select_loser => 'Verlierer:in wählen';
|
||||
String get select_winner => 'Gewinner:in wählen:';
|
||||
|
||||
@override
|
||||
String get selected_players => 'Ausgewählte Spieler:innen';
|
||||
@@ -307,9 +298,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get highest_score => 'Höchste Punkte';
|
||||
|
||||
@override
|
||||
String get loser => 'Verlierer:in';
|
||||
|
||||
@override
|
||||
String get lowest_score => 'Niedrigste Punkte';
|
||||
|
||||
|
||||
@@ -90,9 +90,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get edit_match => 'Edit Match';
|
||||
|
||||
@override
|
||||
String get enter_points => 'Enter points';
|
||||
|
||||
@override
|
||||
String get enter_results => 'Enter Results';
|
||||
|
||||
@@ -243,9 +240,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return '$count Players';
|
||||
}
|
||||
|
||||
@override
|
||||
String get points => 'Points';
|
||||
|
||||
@override
|
||||
String get privacy_policy => 'Privacy Policy';
|
||||
|
||||
@@ -287,10 +281,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get search_for_players => 'Search for players';
|
||||
|
||||
@override
|
||||
String get select_winner => 'Select Winner';
|
||||
|
||||
@override
|
||||
String get select_loser => 'Select Loser';
|
||||
String get select_winner => 'Select Winner:';
|
||||
|
||||
@override
|
||||
String get selected_players => 'Selected players';
|
||||
@@ -307,9 +298,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get highest_score => 'Highest Score';
|
||||
|
||||
@override
|
||||
String get loser => 'Loser';
|
||||
|
||||
@override
|
||||
String get lowest_score => 'Lowest Score';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
@@ -11,11 +12,13 @@ import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
|
||||
class CreateGroupView extends StatefulWidget {
|
||||
const CreateGroupView({super.key, this.groupToEdit});
|
||||
const CreateGroupView({super.key, this.groupToEdit, this.onMembersChanged});
|
||||
|
||||
/// The group to edit, if any
|
||||
final Group? groupToEdit;
|
||||
|
||||
final VoidCallback? onMembersChanged;
|
||||
|
||||
@override
|
||||
State<CreateGroupView> createState() => _CreateGroupViewState();
|
||||
}
|
||||
@@ -69,49 +72,6 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
title: Text(
|
||||
widget.groupToEdit == null ? loc.create_new_group : loc.edit_group,
|
||||
),
|
||||
actions: widget.groupToEdit == null
|
||||
? []
|
||||
: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
if (widget.groupToEdit != null) {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(loc.delete_group),
|
||||
content: Text(loc.this_cannot_be_undone),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(false),
|
||||
child: Text(loc.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(true),
|
||||
child: Text(loc.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((confirmed) async {
|
||||
if (confirmed == true && context.mounted) {
|
||||
bool success = await db.groupDao.deleteGroup(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
showSnackbar(message: loc.error_deleting_group);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
@@ -122,6 +82,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
child: TextInputField(
|
||||
controller: _groupNameController,
|
||||
hintText: loc.group_name,
|
||||
maxLength: Constants.MAX_GROUP_NAME_LENGTH,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -144,42 +105,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
(_groupNameController.text.isEmpty ||
|
||||
(selectedPlayers.length < 2))
|
||||
? null
|
||||
: () async {
|
||||
late Group? updatedGroup;
|
||||
late bool success;
|
||||
if (widget.groupToEdit == null) {
|
||||
success = await db.groupDao.addGroup(
|
||||
group: Group(
|
||||
name: _groupNameController.text.trim(),
|
||||
members: selectedPlayers,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
updatedGroup = Group(
|
||||
id: widget.groupToEdit!.id,
|
||||
name: _groupNameController.text.trim(),
|
||||
description: '',
|
||||
members: selectedPlayers,
|
||||
);
|
||||
//TODO: Implement group editing in database
|
||||
/*
|
||||
success = await db.groupDao.updateGroup(
|
||||
group: updatedGroup,
|
||||
);
|
||||
*/
|
||||
success = true;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
} else {
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
: loc.error_editing_group,
|
||||
);
|
||||
}
|
||||
},
|
||||
: _saveGroup,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
@@ -189,6 +115,104 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Saves the group by creating a new one or updating the existing one,
|
||||
/// depending on whether the widget is in edit mode.
|
||||
Future<void> _saveGroup() async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
late bool success;
|
||||
Group? updatedGroup;
|
||||
|
||||
if (widget.groupToEdit == null) {
|
||||
success = await _createGroup();
|
||||
} else {
|
||||
final result = await _editGroup();
|
||||
success = result.$1;
|
||||
updatedGroup = result.$2;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (success) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
} else {
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
: loc.error_editing_group,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles creating a new group and returns whether the operation was successful.
|
||||
Future<bool> _createGroup() async {
|
||||
final groupName = _groupNameController.text.trim();
|
||||
|
||||
final success = await db.groupDao.addGroup(
|
||||
group: Group(name: groupName, description: '', members: selectedPlayers),
|
||||
);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// Handles editing an existing group and returns a tuple of
|
||||
/// (success, updatedGroup).
|
||||
Future<(bool, Group?)> _editGroup() async {
|
||||
final groupName = _groupNameController.text.trim();
|
||||
|
||||
Group? updatedGroup = Group(
|
||||
id: widget.groupToEdit!.id,
|
||||
name: groupName,
|
||||
description: '',
|
||||
members: selectedPlayers,
|
||||
);
|
||||
|
||||
bool successfullNameChange = true;
|
||||
bool successfullMemberChange = true;
|
||||
|
||||
if (widget.groupToEdit!.name != groupName) {
|
||||
successfullNameChange = await db.groupDao.updateGroupName(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
newName: groupName,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.groupToEdit!.members != selectedPlayers) {
|
||||
successfullMemberChange = await db.groupDao.replaceGroupPlayers(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
newPlayers: selectedPlayers,
|
||||
);
|
||||
await deleteObsoleteMatchGroupRelations();
|
||||
widget.onMembersChanged?.call();
|
||||
}
|
||||
|
||||
final success = successfullNameChange && successfullMemberChange;
|
||||
|
||||
return (success, updatedGroup);
|
||||
}
|
||||
|
||||
/// Removes the group association from matches that no longer belong to the edited group.
|
||||
///
|
||||
/// After updating the group's members, matches that were previously linked to
|
||||
/// this group but don't have any of the newly selected players are considered
|
||||
/// obsolete. For each such match, the group association is removed by setting
|
||||
/// its [groupId] to null.
|
||||
Future<void> deleteObsoleteMatchGroupRelations() async {
|
||||
final groupMatches = await db.matchDao.getGroupMatches(
|
||||
groupId: widget.groupToEdit!.id,
|
||||
);
|
||||
|
||||
final selectedPlayerIds = selectedPlayers.map((p) => p.id).toSet();
|
||||
final relationshipsToDelete = groupMatches.where((match) {
|
||||
return !match.players.any(
|
||||
(player) => selectedPlayerIds.contains(player.id),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
for (var match in relationshipsToDelete) {
|
||||
await db.matchDao.removeMatchGroup(matchId: match.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a snackbar with the given message and optional action.
|
||||
///
|
||||
/// [message] The message to display in the snackbar.
|
||||
|
||||
@@ -191,7 +191,12 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) {
|
||||
return CreateGroupView(groupToEdit: _group);
|
||||
return CreateGroupView(
|
||||
groupToEdit: _group,
|
||||
onMembersChanged: () {
|
||||
_loadStatistics();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -242,10 +247,8 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
|
||||
/// Loads statistics for this group
|
||||
Future<void> _loadStatistics() async {
|
||||
final matches = await db.matchDao.getAllMatches();
|
||||
final groupMatches = matches
|
||||
.where((match) => match.group?.id == _group.id)
|
||||
.toList();
|
||||
isLoading = true;
|
||||
final groupMatches = await db.matchDao.getGroupMatches(groupId: _group.id);
|
||||
|
||||
setState(() {
|
||||
totalMatches = groupMatches.length;
|
||||
@@ -260,7 +263,9 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
|
||||
// Count wins for each player
|
||||
for (var match in matches) {
|
||||
if (match.winner != null) {
|
||||
if (match.winner != null &&
|
||||
_group.members.any((m) => m.id == match.winner?.id)) {
|
||||
print(match.winner);
|
||||
bestPlayerCounts.update(
|
||||
match.winner!,
|
||||
(value) => value + 1,
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/match.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
@@ -176,7 +175,37 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: getResultWidget(loc),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
/// TODO: Implement different ruleset results display
|
||||
if (match.winner != null) ...[
|
||||
Text(
|
||||
loc.winner,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
match.winner!.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Text(
|
||||
loc.no_results_entered_yet,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -235,91 +264,4 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
});
|
||||
widget.onMatchUpdate.call();
|
||||
}
|
||||
|
||||
/// Returns the widget to be displayed in the result [InfoTile]
|
||||
/// TODO: Update when score logic is overhauled
|
||||
Widget getResultWidget(AppLocalizations loc) {
|
||||
if (isSingleRowResult()) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: getResultRow(loc),
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
for (var player in match.players)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
player.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'0 ${loc.points}',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the result row for single winner/loser rulesets or a placeholder
|
||||
/// if no result is entered yet
|
||||
/// TODO: Update when score logic is overhauled
|
||||
List<Widget> getResultRow(AppLocalizations loc) {
|
||||
if (match.winner != null && match.game.ruleset == Ruleset.singleWinner) {
|
||||
return [
|
||||
Text(
|
||||
loc.winner,
|
||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||
),
|
||||
Text(
|
||||
match.winner!.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (match.game.ruleset == Ruleset.singleLoser) {
|
||||
return [
|
||||
Text(
|
||||
loc.loser,
|
||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||
),
|
||||
Text(
|
||||
match.winner!.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
Text(
|
||||
loc.no_results_entered_yet,
|
||||
style: const TextStyle(fontSize: 14, color: CustomTheme.textColor),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns if the result can be displayed in a single row
|
||||
bool isSingleRowResult() {
|
||||
return match.game.ruleset == Ruleset.singleWinner ||
|
||||
match.game.ruleset == Ruleset.singleLoser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/match.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/score_list_tile.dart';
|
||||
|
||||
class MatchResultView extends StatefulWidget {
|
||||
/// A view that allows selecting and saving the winner of a match
|
||||
/// [match]: The match for which the winner is to be selected
|
||||
/// [onWinnerChanged]: Optional callback invoked when the winner is changed
|
||||
const MatchResultView({
|
||||
super.key,
|
||||
required this.match,
|
||||
this.ruleset = Ruleset.singleWinner,
|
||||
this.onWinnerChanged,
|
||||
});
|
||||
const MatchResultView({super.key, required this.match, this.onWinnerChanged});
|
||||
|
||||
/// The match for which the winner is to be selected
|
||||
final Match match;
|
||||
|
||||
/// The ruleset of the match, determines how the winner is selected or how
|
||||
/// scores are entered
|
||||
final Ruleset ruleset;
|
||||
|
||||
/// Optional callback invoked when the winner is changed
|
||||
final VoidCallback? onWinnerChanged;
|
||||
|
||||
@@ -41,9 +29,6 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
/// List of all players who participated in the match
|
||||
late final List<Player> allPlayers;
|
||||
|
||||
/// List of text controllers for score entry, one for each player
|
||||
late final List<TextEditingController> controller;
|
||||
|
||||
/// Currently selected winner player
|
||||
Player? _selectedPlayer;
|
||||
|
||||
@@ -54,19 +39,10 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
allPlayers = widget.match.players;
|
||||
allPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
controller = List.generate(
|
||||
allPlayers.length,
|
||||
(index) => TextEditingController(),
|
||||
);
|
||||
|
||||
if (widget.match.winner != null) {
|
||||
if (rulesetSupportsWinnerSelection()) {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.winner!.id,
|
||||
);
|
||||
} else if (rulesetSupportsScoreEntry()) {
|
||||
/// TODO: Update when score logic is overhauled
|
||||
}
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.winner!.id,
|
||||
);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
@@ -74,7 +50,6 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
@@ -110,77 +85,50 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${getTitleForRuleset(loc)}:',
|
||||
loc.select_winner,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (rulesetSupportsWinnerSelection())
|
||||
Expanded(
|
||||
child: RadioGroup<Player>(
|
||||
groupValue: _selectedPlayer,
|
||||
onChanged: (Player? value) async {
|
||||
setState(() {
|
||||
_selectedPlayer = value;
|
||||
});
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: allPlayers.length,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomRadioListTile(
|
||||
text: allPlayers[index].name,
|
||||
value: allPlayers[index],
|
||||
onContainerTap: (value) async {
|
||||
setState(() {
|
||||
// Check if the already selected player is the same as the newly tapped player.
|
||||
if (_selectedPlayer == value) {
|
||||
// If yes deselected the player by setting it to null.
|
||||
_selectedPlayer = null;
|
||||
} else {
|
||||
// If no assign the newly tapped player to the selected player.
|
||||
(_selectedPlayer = value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (rulesetSupportsScoreEntry())
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
Expanded(
|
||||
child: RadioGroup<Player>(
|
||||
groupValue: _selectedPlayer,
|
||||
onChanged: (Player? value) async {
|
||||
setState(() {
|
||||
_selectedPlayer = value;
|
||||
});
|
||||
await _handleWinnerSaving();
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: allPlayers.length,
|
||||
itemBuilder: (context, index) {
|
||||
print(allPlayers[index].name);
|
||||
return ScoreListTile(
|
||||
return CustomRadioListTile(
|
||||
text: allPlayers[index].name,
|
||||
controller: controller[index],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Divider(indent: 20),
|
||||
value: allPlayers[index],
|
||||
onContainerTap: (value) async {
|
||||
setState(() {
|
||||
// Check if the already selected player is the same as the newly tapped player.
|
||||
if (_selectedPlayer == value) {
|
||||
// If yes deselected the player by setting it to null.
|
||||
_selectedPlayer = null;
|
||||
} else {
|
||||
// If no assign the newly tapped player to the selected player.
|
||||
(_selectedPlayer = value);
|
||||
}
|
||||
});
|
||||
await _handleWinnerSaving();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
CustomWidthButton(
|
||||
text: loc.save_changes,
|
||||
sizeRelativeToWidth: 0.95,
|
||||
onPressed: () async {
|
||||
await _handleSaving();
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop(_selectedPlayer);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -189,75 +137,15 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
|
||||
/// Handles saving or removing the winner in the database
|
||||
/// based on the current selection.
|
||||
Future<void> _handleSaving() async {
|
||||
if (widget.ruleset == Ruleset.singleWinner) {
|
||||
await _handleWinner();
|
||||
} else if (widget.ruleset == Ruleset.singleLoser) {
|
||||
await _handleLoser();
|
||||
} else if (widget.ruleset == Ruleset.lowestScore ||
|
||||
widget.ruleset == Ruleset.highestScore) {
|
||||
await _handleScores();
|
||||
}
|
||||
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
Future<bool> _handleWinner() async {
|
||||
Future<void> _handleWinnerSaving() async {
|
||||
if (_selectedPlayer == null) {
|
||||
return await db.matchDao.removeWinner(matchId: widget.match.id);
|
||||
await db.matchDao.removeWinner(matchId: widget.match.id);
|
||||
} else {
|
||||
return await db.matchDao.setWinner(
|
||||
await db.matchDao.setWinner(
|
||||
matchId: widget.match.id,
|
||||
winnerId: _selectedPlayer!.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _handleLoser() async {
|
||||
if (_selectedPlayer == null) {
|
||||
/// TODO: Update when score logic is overhauled
|
||||
return false;
|
||||
} else {
|
||||
/// TODO: Update when score logic is overhauled
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles saving the scores for each player in the database.
|
||||
Future<bool> _handleScores() async {
|
||||
for (int i = 0; i < allPlayers.length; i++) {
|
||||
var text = controller[i].text;
|
||||
if (text.isEmpty) {
|
||||
text = '0';
|
||||
}
|
||||
final score = int.parse(text);
|
||||
await db.playerMatchDao.updatePlayerScore(
|
||||
matchId: widget.match.id,
|
||||
playerId: allPlayers[i].id,
|
||||
newScore: score,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String getTitleForRuleset(AppLocalizations loc) {
|
||||
switch (widget.ruleset) {
|
||||
case Ruleset.singleWinner:
|
||||
return loc.select_winner;
|
||||
case Ruleset.singleLoser:
|
||||
return loc.select_loser;
|
||||
default:
|
||||
return loc.enter_points;
|
||||
}
|
||||
}
|
||||
|
||||
bool rulesetSupportsWinnerSelection() {
|
||||
return widget.ruleset == Ruleset.singleWinner ||
|
||||
widget.ruleset == Ruleset.singleLoser;
|
||||
}
|
||||
|
||||
bool rulesetSupportsScoreEntry() {
|
||||
return widget.ruleset == Ruleset.lowestScore ||
|
||||
widget.ruleset == Ruleset.highestScore;
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36318,13 +36318,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.''',
|
||||
);
|
||||
|
||||
/// sqlite3_flutter_libs 0.5.41
|
||||
/// sqlite3_flutter_libs 0.5.42
|
||||
const _sqlite3_flutter_libs = Package(
|
||||
name: 'sqlite3_flutter_libs',
|
||||
description: 'Flutter plugin to include native sqlite3 libraries with your app',
|
||||
homepage: 'https://github.com/simolus3/sqlite3.dart/tree/v2/sqlite3_flutter_libs',
|
||||
authors: [],
|
||||
version: '0.5.41',
|
||||
version: '0.5.42',
|
||||
spdxIdentifiers: ['MIT'],
|
||||
isMarkdown: false,
|
||||
isSdk: false,
|
||||
@@ -37796,12 +37796,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.''',
|
||||
);
|
||||
|
||||
/// tallee 0.0.18+252
|
||||
/// tallee 0.0.19+253
|
||||
const _tallee = Package(
|
||||
name: 'tallee',
|
||||
description: 'Tracking App for Card Games',
|
||||
authors: [],
|
||||
version: '0.0.18+252',
|
||||
version: '0.0.19+253',
|
||||
spdxIdentifiers: ['LGPL-3.0'],
|
||||
isMarkdown: false,
|
||||
isSdk: false,
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
|
||||
class ScoreListTile extends StatelessWidget {
|
||||
/// A custom list tile widget that has a text field for inputting a score.
|
||||
/// - [text]: The leading text to be displayed.
|
||||
/// - [controller]: The controller for the text field to input the score.
|
||||
const ScoreListTile({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.controller,
|
||||
/*
|
||||
required this.onContainerTap,
|
||||
*/
|
||||
});
|
||||
|
||||
/// The text to display next to the radio button.
|
||||
final String text;
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
/// The callback invoked when the container is tapped.
|
||||
/*
|
||||
final ValueChanged<T> onContainerTap;
|
||||
*/
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: const BoxDecoration(color: CustomTheme.boxColor),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
height: 40,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 4,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
cursorColor: CustomTheme.textColor,
|
||||
decoration: InputDecoration(
|
||||
hintText: loc.points,
|
||||
counterText: '',
|
||||
filled: true,
|
||||
fillColor: CustomTheme.onBoxColor,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 0,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(
|
||||
color: CustomTheme.textColor.withAlpha(100),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(
|
||||
color: CustomTheme.primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
name: tallee
|
||||
description: "Tracking App for Card Games"
|
||||
publish_to: 'none'
|
||||
version: 0.0.18+252
|
||||
version: 0.0.19+253
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
@@ -31,9 +31,9 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.5.4
|
||||
build_runner: ^2.7.0
|
||||
dart_pubspec_licenses: ^3.0.14
|
||||
drift_dev: ^2.27.0
|
||||
drift_dev: ^2.29.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
|
||||
@@ -42,7 +42,7 @@ void main() {
|
||||
testPlayer4 = Player(name: 'Diana', description: '');
|
||||
testPlayer5 = Player(name: 'Eve', description: '');
|
||||
testGroup1 = Group(
|
||||
name: 'Test Group 2',
|
||||
name: 'Test Group 1',
|
||||
description: '',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
@@ -307,5 +307,69 @@ void main() {
|
||||
expect(fetchedMatch.winner, isNotNull);
|
||||
expect(fetchedMatch.winner!.id, testPlayer5.id);
|
||||
});
|
||||
|
||||
test(
|
||||
'removeMatchGroup removes group from match with existing group',
|
||||
() async {
|
||||
await database.matchDao.addMatch(match: testMatch1);
|
||||
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: testMatch1.id,
|
||||
);
|
||||
expect(removed, isTrue);
|
||||
|
||||
final updatedMatch = await database.matchDao.getMatchById(
|
||||
matchId: testMatch1.id,
|
||||
);
|
||||
expect(updatedMatch.group, null);
|
||||
expect(updatedMatch.game.id, testMatch1.game.id);
|
||||
expect(updatedMatch.name, testMatch1.name);
|
||||
expect(updatedMatch.notes, testMatch1.notes);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'removeMatchGroup on match that already has no group still succeeds',
|
||||
() async {
|
||||
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
|
||||
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: testMatchOnlyPlayers.id,
|
||||
);
|
||||
expect(removed, isTrue);
|
||||
|
||||
final updatedMatch = await database.matchDao.getMatchById(
|
||||
matchId: testMatchOnlyPlayers.id,
|
||||
);
|
||||
expect(updatedMatch.group, null);
|
||||
},
|
||||
);
|
||||
|
||||
test('removeMatchGroup on non-existing match returns false', () async {
|
||||
final removed = await database.matchDao.removeMatchGroup(
|
||||
matchId: 'non-existing-id',
|
||||
);
|
||||
expect(removed, isFalse);
|
||||
});
|
||||
|
||||
test('Fetching all matches related to a group', () async {
|
||||
var matches = await database.matchDao.getGroupMatches(
|
||||
groupId: 'non-existing-id',
|
||||
);
|
||||
|
||||
expect(matches, isEmpty);
|
||||
|
||||
await database.matchDao.addMatch(match: testMatch1);
|
||||
print(await database.matchDao.getAllMatches());
|
||||
|
||||
matches = await database.matchDao.getGroupMatches(groupId: testGroup1.id);
|
||||
|
||||
expect(matches, isNotEmpty);
|
||||
|
||||
final match = matches.first;
|
||||
expect(match.id, testMatch1.id);
|
||||
expect(match.group, isNotNull);
|
||||
expect(match.group!.id, testGroup1.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user