202 lines
5.5 KiB
Dart
202 lines
5.5 KiB
Dart
/// Bottom navigation: products, lists, add, and shop log.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'products_screen.dart';
|
|
import 'scan_screen.dart';
|
|
import 'services/app_settings.dart';
|
|
import 'services/product_repository.dart';
|
|
import 'shop_log_screen.dart';
|
|
import 'shopping_lists_screen.dart';
|
|
|
|
class HomeShell extends StatefulWidget {
|
|
const HomeShell({super.key, required this.repository});
|
|
|
|
final ProductRepository repository;
|
|
|
|
@override
|
|
State<HomeShell> createState() => _HomeShellState();
|
|
}
|
|
|
|
class _HomeShellState extends State<HomeShell> {
|
|
static const _addIndex = 2;
|
|
static const _shopLogIndex = 3;
|
|
static const _fabGap = 56.0;
|
|
|
|
int _index = _addIndex;
|
|
int _revision = 0;
|
|
AppSettings? _settings;
|
|
int _seenGeneration = 0;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
final settings = SettingsScope.maybeOf(context);
|
|
if (settings != _settings) {
|
|
_settings?.removeListener(_onSettings);
|
|
_settings = settings;
|
|
_seenGeneration = settings?.catalogGeneration ?? 0;
|
|
_settings?.addListener(_onSettings);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_settings?.removeListener(_onSettings);
|
|
super.dispose();
|
|
}
|
|
|
|
void _onSettings() {
|
|
final generation = _settings?.catalogGeneration ?? 0;
|
|
if (generation == _seenGeneration) return;
|
|
_seenGeneration = generation;
|
|
if (mounted) setState(() => _revision++);
|
|
}
|
|
|
|
void _onTripSaved() {
|
|
setState(() {
|
|
_index = _shopLogIndex;
|
|
_revision++;
|
|
});
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(const SnackBar(content: Text('Shopping trip saved.')));
|
|
});
|
|
}
|
|
|
|
void _select(int index) {
|
|
if (index == _index) return;
|
|
setState(() => _index = index);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
|
|
return Scaffold(
|
|
body: switch (_index) {
|
|
0 => ProductsScreen(
|
|
key: ValueKey('products-$_revision'),
|
|
repository: widget.repository,
|
|
),
|
|
1 => ShoppingListsScreen(
|
|
key: ValueKey('lists-$_revision'),
|
|
repository: widget.repository,
|
|
),
|
|
2 => ScanScreen(
|
|
repository: widget.repository,
|
|
onTripSaved: _onTripSaved,
|
|
),
|
|
_ => ShopLogScreen(
|
|
key: ValueKey('shops-$_revision'),
|
|
repository: widget.repository,
|
|
),
|
|
},
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
|
floatingActionButton: FloatingActionButton.small(
|
|
onPressed: () => _select(_addIndex),
|
|
tooltip: 'Add',
|
|
elevation: _index == _addIndex ? 4 : 1,
|
|
shape: const CircleBorder(),
|
|
backgroundColor: scheme.primary,
|
|
foregroundColor: scheme.onPrimary,
|
|
child: const Icon(Icons.add, size: 24),
|
|
),
|
|
bottomNavigationBar: BottomAppBar(
|
|
shape: const CircularNotchedRectangle(),
|
|
notchMargin: 6,
|
|
elevation: 2,
|
|
height: 52,
|
|
padding: EdgeInsets.zero,
|
|
color: scheme.surfaceContainer,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: _NavButton(
|
|
icon: Icons.inventory_2_outlined,
|
|
selectedIcon: Icons.inventory_2,
|
|
label: 'Products',
|
|
selected: _index == 0,
|
|
onTap: () => _select(0),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: _NavButton(
|
|
icon: Icons.checklist_outlined,
|
|
selectedIcon: Icons.checklist,
|
|
label: 'Lists',
|
|
selected: _index == 1,
|
|
onTap: () => _select(1),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: _fabGap),
|
|
Expanded(
|
|
child: _NavButton(
|
|
icon: Icons.receipt_long_outlined,
|
|
selectedIcon: Icons.receipt_long,
|
|
label: 'Shop log',
|
|
selected: _index == _shopLogIndex,
|
|
onTap: () => _select(_shopLogIndex),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavButton extends StatelessWidget {
|
|
const _NavButton({
|
|
required this.icon,
|
|
required this.selectedIcon,
|
|
required this.label,
|
|
required this.selected,
|
|
required this.onTap,
|
|
});
|
|
|
|
final IconData icon;
|
|
final IconData selectedIcon;
|
|
final String label;
|
|
final bool selected;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final scheme = theme.colorScheme;
|
|
final color = selected ? scheme.primary : scheme.onSurfaceVariant;
|
|
|
|
return InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(selected ? selectedIcon : icon, color: color, size: 20),
|
|
Text(
|
|
label,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.textTheme.labelSmall?.copyWith(
|
|
color: color,
|
|
height: 1.1,
|
|
fontSize: 10,
|
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|