Files
recipe-app/flutter/lib/core/ui/app_shell.dart
T
Nils-Johan Gynther 84dbe8490d
Test Suite / test (24.15.0) (push) Has been cancelled
feat: remove unused methods and improve widget styling in various screens
2026-05-08 15:28:54 +02:00

251 lines
7.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../features/auth/data/auth_providers.dart';
import '../../features/recipes/data/recipes_grid_provider.dart';
const _profileHeaderDestination = _AppDestination(
path: '/profile',
title: 'Profil',
icon: Icons.person,
label: 'Profil',
);
const _adminHeaderDestination = _AppDestination(
path: '/admin',
title: 'Admin',
icon: Icons.admin_panel_settings_outlined,
label: 'Admin',
);
class AppShell extends ConsumerWidget {
final String location;
final ValueChanged<String> onNavigateToPath;
final Widget child;
const AppShell({
super.key,
required this.location,
required this.onNavigateToPath,
required this.child,
});
static const _baseDestinations = [
_AppDestination(
path: '/recipes',
title: 'Recept',
icon: Icons.restaurant_menu,
label: 'Recept',
),
_AppDestination(
path: '/inventory',
title: 'Inventarie',
icon: Icons.inventory_2_outlined,
label: 'Inventarie',
),
_AppDestination(
path: '/matsedel',
title: 'Matsedel',
icon: Icons.calendar_month_outlined,
label: 'Matsedel',
),
_AppDestination(
path: '/baslager',
title: 'Baslager',
icon: Icons.storefront_outlined,
label: 'Baslager',
),
_AppDestination(
path: '/import',
title: 'Importera',
icon: Icons.upload_file_outlined,
label: 'Importera',
),
];
List<_AppDestination> _destinations(bool isAdmin) => isAdmin
? [..._baseDestinations, _adminHeaderDestination]
: _baseDestinations;
int? _selectedIndex(List<_AppDestination> destinations) {
final index = destinations.indexWhere(
(destination) => location.startsWith(destination.path),
);
return index < 0 ? null : index;
}
_AppDestination _selectedHeaderDestination(
List<_AppDestination> destinations,
bool isAdmin,
) {
if (location.startsWith('/profile')) {
return _profileHeaderDestination;
}
if (location.startsWith('/admin') && isAdmin) {
return _adminHeaderDestination;
}
final selectedIndex = _selectedIndex(destinations);
if (selectedIndex != null) {
return destinations[selectedIndex];
}
return destinations.first;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final isAdmin = ref.watch(isAdminProvider);
final dests = _destinations(isAdmin);
final selectedIndex = _selectedIndex(dests);
final selectedDestination = _selectedHeaderDestination(dests, isAdmin);
final isWide = MediaQuery.of(context).size.width >= 900;
void navigateTo(int index) {
final target = dests[index].path;
if (target != location && context.mounted) {
onNavigateToPath(target);
}
}
final isRecipesRoute = location.startsWith('/recipes') &&
!location.startsWith('/recipes/');
final isImportRoute = location == '/import';
Widget shell = Scaffold(
appBar: AppBar(
title: Text(selectedDestination.title),
bottom: isImportRoute
? const TabBar(
tabs: [
Tab(
icon: Icon(Icons.restaurant_menu_outlined),
text: 'Recept',
),
Tab(
icon: Icon(Icons.receipt_long_outlined),
text: 'Kvitto',
),
],
)
: null,
actions: [
if (isRecipesRoute)
Consumer(
builder: (context, ref, child) {
final view = ref.watch(recipesViewProvider).maybeWhen(
data: (v) => v,
orElse: () => (mode: RecipesViewMode.grid, columns: 2),
);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
tooltip: view.mode == RecipesViewMode.grid
? 'Visa som lista'
: 'Visa som grid',
icon: Icon(view.mode == RecipesViewMode.grid
? Icons.view_list
: Icons.grid_view),
onPressed: () =>
ref.read(recipesViewProvider.notifier).toggleMode(),
),
if (view.mode == RecipesViewMode.grid)
PopupMenuButton<int>(
icon: const Icon(Icons.grid_view),
tooltip: 'Välj antal kolumner',
onSelected: (columns) =>
ref.read(recipesViewProvider.notifier).setColumns(columns),
itemBuilder: (context) => const [
PopupMenuItem(value: 2, child: Text('2 kolumner')),
PopupMenuItem(value: 4, child: Text('4 kolumner')),
PopupMenuItem(value: 6, child: Text('6 kolumner')),
PopupMenuItem(value: 8, child: Text('8 kolumner')),
],
),
],
);
},
),
PopupMenuButton<String>(
tooltip: 'Profil och konto',
icon: const Icon(Icons.account_circle_outlined),
onSelected: (value) {
switch (value) {
case 'profile':
if (location != '/profile' && context.mounted) {
onNavigateToPath('/profile');
}
}
},
itemBuilder: (context) => const [
PopupMenuItem<String>(
value: 'profile',
child: ListTile(
leading: Icon(Icons.person_outline),
title: Text('Profil'),
contentPadding: EdgeInsets.zero,
),
),
],
),
],
),
body: isWide
? Row(
children: [
NavigationRail(
selectedIndex: selectedIndex ?? 0,
onDestinationSelected: navigateTo,
labelType: NavigationRailLabelType.all,
destinations: dests
.map(
(destination) => NavigationRailDestination(
icon: Icon(destination.icon),
label: Text(destination.label),
),
)
.toList(),
),
const VerticalDivider(width: 1),
Expanded(child: child),
],
)
: child,
bottomNavigationBar: isWide
? null
: NavigationBar(
selectedIndex: selectedIndex ?? 0,
onDestinationSelected: navigateTo,
destinations: dests
.map(
(destination) => NavigationDestination(
icon: Icon(destination.icon),
label: destination.label,
),
)
.toList(),
),
);
if (isImportRoute) {
shell = DefaultTabController(length: 2, child: shell);
}
return shell;
}
}
class _AppDestination {
final String path;
final String title;
final IconData icon;
final String label;
const _AppDestination({
required this.path,
required this.title,
required this.icon,
required this.label,
});
}