Files
receipity/lib/home_shell.dart
T
2026-08-31 12:57:17 +02:00

229 lines
6.6 KiB
Dart

/// Bottom navigation: home, products, lists, add, and shop log.
library;
import 'package:flutter/material.dart';
import 'home_screen.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 _homeIndex = 0;
static const _productsIndex = 1;
static const _addIndex = 2;
static const _listsIndex = 3;
static const _shopLogIndex = 4;
static const _fabGap = 56.0;
int _index = _homeIndex;
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) {
_homeIndex => HomeScreen(
key: ValueKey('home-$_revision'),
repository: widget.repository,
onOpenProducts: () => _select(_productsIndex),
onOpenLists: () => _select(_listsIndex),
onOpenShopLog: () => _select(_shopLogIndex),
onOpenAdd: () => _select(_addIndex),
),
_productsIndex => ProductsScreen(
key: ValueKey('products-$_revision'),
repository: widget.repository,
),
_listsIndex => ShoppingListsScreen(
key: ValueKey('lists-$_revision'),
repository: widget.repository,
),
_addIndex => 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.home_outlined,
selectedIcon: Icons.home,
label: 'Home',
selected: _index == _homeIndex,
onTap: () => _select(_homeIndex),
),
),
Expanded(
child: _NavButton(
icon: Icons.inventory_2_outlined,
selectedIcon: Icons.inventory_2,
label: 'Products',
selected: _index == _productsIndex,
onTap: () => _select(_productsIndex),
),
),
],
),
),
const SizedBox(width: _fabGap),
Expanded(
child: Row(
children: [
Expanded(
child: _NavButton(
icon: Icons.checklist_outlined,
selectedIcon: Icons.checklist,
label: 'Lists',
selected: _index == _listsIndex,
onTap: () => _select(_listsIndex),
),
),
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,
),
),
],
),
);
}
}