Files
kooltab/lib/views/bar/bar_screen_view.dart
T
2026-10-08 04:12:36 +02:00

264 lines
7.6 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../l10n/app_localizations.dart';
import '../../l10n/app_localizations_helpers.dart';
import '../../models/product.dart';
import '../../models/remote_command.dart';
import '../../services/product_service.dart';
import '../../utils/app_updater.dart';
import '../../viewmodels/admin_pin_view_model.dart';
import '../../viewmodels/bar_screen_view_model.dart';
import '../../viewmodels/pin_lock_view_model.dart';
import '../../viewmodels/product_list_view_model.dart';
import '../../viewmodels/remote_sync_view_model.dart';
import '../../viewmodels/settings_view_model.dart';
import '../dialogs/close_tab_dialog.dart';
import '../dialogs/edit_tab_dialog.dart';
import '../dialogs/new_tab_dialog.dart';
import 'product_grid.dart';
import 'tab_panel.dart';
class BarScreenView extends StatefulWidget {
const BarScreenView({super.key});
@override
State<BarScreenView> createState() => _BarScreenViewState();
}
class _BarScreenViewState extends State<BarScreenView> {
late final StreamSubscription<RemoteSyncEvent> _remoteCloseSubscription;
@override
void initState() {
super.initState();
_remoteCloseSubscription = context
.read<RemoteSyncViewModel>()
.tabsClosedRemotely
.listen(_showRemoteClose);
WidgetsBinding.instance.addPostFrameCallback((_) {
UpdateChecker.check(context);
});
}
@override
void dispose() {
_remoteCloseSubscription.cancel();
super.dispose();
}
void _showRemoteClose(RemoteSyncEvent event) {
if (!mounted) return;
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
l10n.tabClosedRemotely(
event.tabCustomerName ?? '',
event.requestedBy ?? '',
),
),
behavior: SnackBarBehavior.floating,
),
);
}
Future<void> _addProduct(Product product) async {
final l10n = AppLocalizations.of(context);
final viewModel = context.read<BarScreenViewModel>();
if (viewModel.selectedTab == null) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.openOrSelectTab)));
return;
}
try {
await viewModel.addProductToSelectedTab(product);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e is InsufficientStockException
? l10n.productOutOfStock
: l10n.couldNotAddProductToTab,
),
),
);
}
}
void _logout() {
context.read<PinLockViewModel>().lock();
context.read<AdminPinViewModel>().lock();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final viewModel = context.watch<BarScreenViewModel>();
final productsViewModel = context.watch<ProductListViewModel>();
final settings = context.watch<SettingsViewModel>().settings;
final stockByProductId = {
for (final product in productsViewModel.products)
product.id: product.stockQuantity,
};
return Scaffold(
appBar: AppBar(
title: Text(l10n.barTabs),
actions: [
IconButton(
tooltip: l10n.admin,
onPressed: () => context.push('/admin'),
icon: const Icon(Icons.admin_panel_settings_outlined),
),
if (kDebugMode) ...[
const SizedBox(width: 6),
_DebugShortcut(
tooltip: l10n.manageProducts,
icon: Icons.inventory_2_outlined,
location: '/products',
),
const SizedBox(width: 6),
_DebugShortcut(
tooltip: l10n.tabHistory,
icon: Icons.history_rounded,
location: '/history',
),
],
const SizedBox(width: 6),
IconButton(
tooltip: l10n.refresh,
onPressed: () => context.read<RemoteSyncViewModel>().syncNow(),
icon: const Icon(Icons.refresh_rounded),
),
const SizedBox(width: 6),
IconButton(
tooltip: l10n.settings,
onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings),
),
if (context.watch<PinLockViewModel>().isPinSet) ...[
const SizedBox(width: 6),
IconButton(
tooltip: l10n.logout,
onPressed: _logout,
icon: const Icon(Icons.logout),
),
],
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
),
resizeToAvoidBottomInset: false,
body: Builder(
builder: (context) {
if (viewModel.isLoading) {
return const Center(
child: CircularProgressIndicator(strokeWidth: 2.5),
);
}
if (viewModel.loadError != null) {
return _LoadError(message: l10n.errorText(viewModel.loadError));
}
return Row(
children: [
Expanded(
flex: 2,
child: ProductGrid(
products: productsViewModel.products,
rows: settings.barGridRows,
hasSelectedTab: viewModel.selectedTab != null,
onProductTap: _addProduct,
),
),
Container(width: 1, color: Theme.of(context).dividerColor),
Expanded(
child: TabPanel(
tabs: viewModel.tabs,
selectedTab: viewModel.selectedTab,
selectedTabId: viewModel.selectedTabId,
stockByProductId: stockByProductId,
onNewTabPressed: () => showNewTabDialog(context),
onTabSelected: viewModel.selectTab,
onItemQuantityChanged: viewModel.changeItemQuantity,
onCloseTabPressed: () => confirmCloseTab(context),
onTabEdited: (tab) => showEditTabDialog(context, tab),
onTabDeleted: (tab) => confirmDeleteTab(context, tab),
),
),
],
);
},
),
);
}
}
class _DebugShortcut extends StatelessWidget {
final String tooltip;
final IconData icon;
final String location;
const _DebugShortcut({
required this.tooltip,
required this.icon,
required this.location,
});
@override
Widget build(BuildContext context) {
return IconButton(
tooltip: tooltip,
style: IconButton.styleFrom(
backgroundColor: Colors.yellow,
foregroundColor: Colors.black,
),
onPressed: () => context.push(location),
icon: Icon(icon),
);
}
}
class _LoadError extends StatelessWidget {
final String message;
const _LoadError({required this.message});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline_rounded,
size: 40,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 12),
Text(
message,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
);
}
}