fix: alot

This commit is contained in:
2026-10-19 21:41:34 +02:00
parent 30dd74af90
commit b0074aeb3f
40 changed files with 1979 additions and 405 deletions
+132 -20
View File
@@ -2,8 +2,13 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../l10n/app_localizations.dart';
import '../../l10n/app_localizations_helpers.dart';
import '../../models/app_error.dart';
import '../../models/bar_tab.dart';
import '../../services/pin_lock_service.dart';
import '../../viewmodels/admin_pin_view_model.dart';
import '../../viewmodels/bar_screen_view_model.dart';
import 'duplicate_tab_name_dialog.dart';
Future<void> showEditTabDialog(BuildContext context, BarTab tab) async {
final l10n = AppLocalizations.of(context);
@@ -13,10 +18,19 @@ Future<void> showEditTabDialog(BuildContext context, BarTab tab) async {
builder: (_) => _EditTabDialog(initialName: tab.customerName),
);
if (name == null || name.trim().isEmpty || !context.mounted) return;
final trimmed = name?.trim() ?? '';
if (trimmed.isEmpty || trimmed == tab.customerName || !context.mounted) {
return;
}
final viewModel = context.read<BarScreenViewModel>();
if (viewModel.hasOpenTabNamed(trimmed, exceptTabId: tab.id) &&
!await confirmDuplicateTabName(context, trimmed)) {
return;
}
try {
await context.read<BarScreenViewModel>().renameTab(tab.id, name.trim());
await viewModel.renameTab(tab.id, trimmed);
} catch (_) {
if (!context.mounted) return;
ScaffoldMessenger.of(
@@ -78,30 +92,37 @@ class _EditTabDialogState extends State<_EditTabDialog> {
}
}
/// Deleting a tab with drinks on it leaves no trace in history, so it needs
/// the admin PIN when one is set.
Future<void> confirmDeleteTab(BuildContext context, BarTab tab) async {
final l10n = AppLocalizations.of(context);
final adminPinViewModel = context.read<AdminPinViewModel>();
final needsAdminPin = tab.items.isNotEmpty && adminPinViewModel.isPinSet;
final confirmed = await showDialog<bool>(
final result = await showDialog<_DeleteTabResult>(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: Text(l10n.deleteTab),
content: Text(l10n.deleteTabDescription),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: Text(l10n.cancel),
),
FilledButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
child: Text(l10n.delete),
),
],
);
},
builder: (_) => _DeleteTabDialog(
description: tab.items.isEmpty
? l10n.deleteTabDescription
: l10n.deleteTabWithItemsDescription,
needsAdminPin: needsAdminPin,
),
);
if (confirmed != true || !context.mounted) return;
if (result == null || !context.mounted) return;
if (needsAdminPin) {
final error = await _verifyAdminPin(adminPinViewModel, result.pin);
if (error != null) {
if (!context.mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.errorText(error))));
return;
}
}
if (!context.mounted) return;
try {
await context.read<BarScreenViewModel>().deleteTab(tab.id);
@@ -112,3 +133,94 @@ Future<void> confirmDeleteTab(BuildContext context, BarTab tab) async {
).showSnackBar(SnackBar(content: Text(l10n.couldNotDeleteTab)));
}
}
Future<AppError?> _verifyAdminPin(
AdminPinViewModel adminPinViewModel,
String pin,
) async {
try {
final matches = await adminPinViewModel.adminPinService.verifyPin(pin);
return matches ? null : AppError.incorrectPin;
} on PinLockedOutException {
return AppError.tooManyPinAttempts;
} catch (_) {
return AppError.verifyPin;
}
}
class _DeleteTabResult {
final String pin;
const _DeleteTabResult(this.pin);
}
class _DeleteTabDialog extends StatefulWidget {
final String description;
final bool needsAdminPin;
const _DeleteTabDialog({
required this.description,
required this.needsAdminPin,
});
@override
State<_DeleteTabDialog> createState() => _DeleteTabDialogState();
}
class _DeleteTabDialogState extends State<_DeleteTabDialog> {
final _pinController = TextEditingController();
@override
void dispose() {
_pinController.dispose();
super.dispose();
}
void _confirm() {
Navigator.of(context).pop(_DeleteTabResult(_pinController.text));
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return AlertDialog(
title: Text(l10n.deleteTab),
content: SizedBox(
width: 360,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.description),
if (widget.needsAdminPin) ...[
const SizedBox(height: 16),
TextField(
controller: _pinController,
autofocus: true,
obscureText: true,
keyboardType: TextInputType.number,
maxLength: 4,
decoration: InputDecoration(labelText: l10n.adminPin),
onSubmitted: (_) => _confirm(),
),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.cancel),
),
FilledButton(
onPressed: _confirm,
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
),
child: Text(l10n.delete),
),
],
);
}
}