import 'package:flutter/material.dart'; import '../../l10n/app_localizations.dart'; import '../../models/bar_tab.dart'; import '../../models/tab_item.dart'; import '../../utils/money.dart'; import '../../utils/tab_orders.dart'; import '../widgets/order_divider.dart'; /// Changes how many of [item] are on the tab: [delta] > 0 rings up more, /// [delta] < 0 takes drinks of order [orderId] off. typedef ItemQuantityChanged = Future Function(TabItem item, int delta, String? orderId); class SelectedTabDetails extends StatelessWidget { final BarTab tab; final ItemQuantityChanged onItemQuantityChanged; final VoidCallback onCloseTabPressed; const SelectedTabDetails({ super.key, required this.tab, required this.onItemQuantityChanged, required this.onCloseTabPressed, }); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: tab.items.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.local_bar_outlined, size: 32, color: scheme.onSurface.withValues(alpha: 0.25), ), const SizedBox(height: 8), Text( l10n.tapProductsToAdd, style: Theme.of(context).textTheme.bodyMedium, ), ], ), ) : _OrderList( // Another tab starts with its own orders folded key: ValueKey(tab.id), orders: ordersOfTab(tab), onItemQuantityChanged: onItemQuantityChanged, ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(16), border: Border.all( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.3), ), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.total, style: Theme.of(context).textTheme.bodySmall, ), Text( tab.totalInCents.asEuro, style: Theme.of( context, ).textTheme.headlineMedium?.copyWith(fontSize: 24), ), ], ), ), FilledButton( onPressed: tab.items.isEmpty ? null : onCloseTabPressed, child: Text(l10n.closeTab), ), ], ), ), ], ); } } /// The tab's orders, oldest first, each under the time it started. class _OrderList extends StatefulWidget { final List> orders; final ItemQuantityChanged onItemQuantityChanged; const _OrderList({ super.key, required this.orders, required this.onItemQuantityChanged, }); @override State<_OrderList> createState() => _OrderListState(); } /// The current (latest) order is open, earlier ones are folded away under /// their time and total; tapping a header flips it. class _OrderListState extends State<_OrderList> { /// Orders opened or closed by hand, by [_key]. final _toggled = {}; String _key(TabOrder order) => order.orderId ?? order.startedAt.toIso8601String(); @override Widget build(BuildContext context) { final orders = widget.orders; final children = []; for (final (orderIndex, order) in orders.indexed) { final key = _key(order); final expanded = _toggled[key] ?? orderIndex == orders.length - 1; children.add( OrderDivider( key: ValueKey('order-$key'), startedAt: order.startedAt, totalInCents: order.rows.fold( 0, (total, row) => total + row.quantity * row.line.unitPriceInCents, ), expanded: expanded, onTap: () => setState(() => _toggled[key] = !expanded), ), ); if (!expanded) continue; for (final (index, row) in order.rows.indexed) { if (index > 0) children.add(const Divider(height: 1)); children.add( TabItemRow( key: ValueKey('${order.orderId ?? order.startedAt}-${row.line.id}'), item: row.line, quantity: row.quantity, orderId: order.orderId, onQuantityChanged: widget.onItemQuantityChanged, ), ); } } return ListView(children: children); } } class TabItemRow extends StatelessWidget { final TabItem item; /// How many of [item] are in this order. final int quantity; final String? orderId; final ItemQuantityChanged onQuantityChanged; const TabItemRow({ super.key, required this.item, required this.quantity, required this.orderId, required this.onQuantityChanged, }); Future _changeQuantity(BuildContext context, int delta) async { final l10n = AppLocalizations.of(context); try { await onQuantityChanged(item, delta, orderId); } catch (e) { if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(l10n.couldNotUpdateQuantity))); } } @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.productName, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 2), Text( '$quantity × ${item.unitPriceInCents.asEuro}', style: Theme.of(context).textTheme.bodySmall, ), ], ), ), Container( decoration: BoxDecoration( color: scheme.onSurface.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(999), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( visualDensity: VisualDensity.compact, onPressed: () => _changeQuantity(context, -1), icon: const Icon(Icons.remove_rounded, size: 18), ), SizedBox( width: 22, child: Text( '$quantity', textAlign: TextAlign.center, style: const TextStyle(fontWeight: FontWeight.w700), ), ), IconButton( visualDensity: VisualDensity.compact, onPressed: () => _changeQuantity(context, 1), icon: const Icon(Icons.add_rounded, size: 18), ), ], ), ), SizedBox( width: 72, child: Text( (quantity * item.unitPriceInCents).asEuro, textAlign: TextAlign.end, style: const TextStyle(fontWeight: FontWeight.w700), ), ), ], ), ); } }