Files
kooltab/lib/views/bar/selected_tab_details.dart

266 lines
8.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<void> 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<TabOrder<TabItem>> 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, bool>{};
String _key(TabOrder<TabItem> order) =>
order.orderId ?? order.startedAt.toIso8601String();
@override
Widget build(BuildContext context) {
final orders = widget.orders;
final children = <Widget>[];
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<int>(
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<void> _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),
),
),
],
),
);
}
}