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 showEditTabDialog(BuildContext context, BarTab tab) async { final l10n = AppLocalizations.of(context); final name = await showDialog( context: context, builder: (_) => _EditTabDialog(initialName: tab.customerName), ); final trimmed = name?.trim() ?? ''; if (trimmed.isEmpty || trimmed == tab.customerName || !context.mounted) { return; } final viewModel = context.read(); if (viewModel.hasOpenTabNamed(trimmed, exceptTabId: tab.id) && !await confirmDuplicateTabName(context, trimmed)) { return; } try { await viewModel.renameTab(tab.id, trimmed); } catch (_) { if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.couldNotRenameTab))); } } class _EditTabDialog extends StatefulWidget { final String initialName; const _EditTabDialog({required this.initialName}); @override State<_EditTabDialog> createState() => _EditTabDialogState(); } class _EditTabDialogState extends State<_EditTabDialog> { late final TextEditingController _controller; @override void initState() { super.initState(); _controller = TextEditingController(text: widget.initialName); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return AlertDialog( title: Text(l10n.editTab), content: TextField( controller: _controller, autofocus: true, textInputAction: TextInputAction.done, decoration: InputDecoration(labelText: l10n.customerGroupName), onSubmitted: (value) { Navigator.of(context).pop(value); }, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(l10n.cancel), ), FilledButton( onPressed: () => Navigator.of(context).pop(_controller.text), child: Text(l10n.confirm), ), ], ); } } /// Deleting a tab with drinks on it leaves no trace in history, so it needs /// the admin PIN when one is set. Future confirmDeleteTab(BuildContext context, BarTab tab) async { final l10n = AppLocalizations.of(context); final adminPinViewModel = context.read(); final needsAdminPin = tab.items.isNotEmpty && adminPinViewModel.isPinSet; final result = await showDialog<_DeleteTabResult>( context: context, builder: (_) => _DeleteTabDialog( description: tab.items.isEmpty ? l10n.deleteTabDescription : l10n.deleteTabWithItemsDescription, needsAdminPin: needsAdminPin, ), ); 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().deleteTab(tab.id); } catch (_) { if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.couldNotDeleteTab))); } } Future _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), ), ], ); } }