feat: games with match associations cant be deleted
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 46s
Pull Request Pipeline / lint (pull_request) Successful in 48s

This commit is contained in:
2026-05-02 16:32:25 +02:00
parent 2e1314ccd4
commit 92bf74683f
3 changed files with 58 additions and 14 deletions

View File

@@ -176,4 +176,25 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Retrieves all games with their respective match counts.
/// Returns a list of tuples (Game, matchCount).
Future<List<(Game, int)>> getGameUsage() async {
final games = await getAllGames();
final results = <(Game, int)>[];
for (final game in games) {
final matchCount =
await (selectOnly(db.matchTable)
..where(db.matchTable.gameId.equals(game.id))
..addColumns([db.matchTable.id.count()]))
.map((row) => row.read(db.matchTable.id.count()))
.getSingle();
results.add((game, matchCount ?? 0));
}
return results;
}
}

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
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/data/db/database.dart';
import 'package:tallee/data/models/game.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game/create_game_view.dart';
@@ -34,6 +36,10 @@ class ChooseGameView extends StatefulWidget {
}
class _ChooseGameViewState extends State<ChooseGameView> {
late final AppDatabase db;
late List<(Game, int)> gameCounts = [];
/// Controller for the search bar
final TextEditingController searchBarController = TextEditingController();
@@ -45,6 +51,9 @@ class _ChooseGameViewState extends State<ChooseGameView> {
@override
void initState() {
db = Provider.of<AppDatabase>(context, listen: false);
fetchGameCounts();
selectedGameId = widget.initialGameId;
// Start with all games visible
@@ -150,6 +159,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
adaptivePageRoute(
builder: (context) => CreateGameView(
gameToEdit: game,
canDelete: canDeleteGame(game),
onGameChanged: () {
widget.onGamesUpdated?.call();
},
@@ -209,4 +219,16 @@ class _ChooseGameViewState extends State<ChooseGameView> {
void _refreshFromSource() {
_applySearchFilter(searchBarController.text);
}
Future<void> fetchGameCounts() async {
gameCounts = await db.gameDao.getGameUsage();
}
// A game can only be deleted if there are no matches using it
bool canDeleteGame(Game game) {
final count = gameCounts
.firstWhere((gc) => gc.$1.id == game.id, orElse: () => (game, 0))
.$2;
return count == 0;
}
}

View File

@@ -23,15 +23,18 @@ import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
class CreateGameView extends StatefulWidget {
const CreateGameView({
super.key,
this.gameToEdit,
required this.onGameChanged,
this.gameToEdit,
this.canDelete = false,
});
/// Callback to invoke when the game is created or edited
final VoidCallback onGameChanged;
/// An optional game to prefill the fields
final Game? gameToEdit;
/// Callback to invoke when the game is created or edited
final VoidCallback onGameChanged;
final bool canDelete;
@override
State<CreateGameView> createState() => _CreateGameViewState();
@@ -41,7 +44,6 @@ class _CreateGameViewState extends State<CreateGameView> {
/// GlobalKey for ScaffoldMessenger to show snackbars
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
/// The database instance for accessing game data.
late final AppDatabase db;
/// The currently selected ruleset for the game.
@@ -133,13 +135,12 @@ class _CreateGameViewState extends State<CreateGameView> {
backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(
title: Text(isEditing ? loc.edit_game : loc.create_game),
actions: widget.gameToEdit == null
? []
: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
if (widget.gameToEdit != null) {
actions: [
if (isEditMode())
IconButton(
icon: const Icon(Icons.delete),
onPressed: widget.canDelete
? () async {
showDialog<bool>(
context: context,
builder: (context) => CustomAlertDialog(
@@ -176,9 +177,9 @@ class _CreateGameViewState extends State<CreateGameView> {
}
});
}
},
),
],
: null,
),
],
),
body: SafeArea(
child: Column(