feat: add alot
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user