224 lines
6.7 KiB
Dart
224 lines
6.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../core/config/dev_config.dart';
|
|
import '../../core/constants/app_constants.dart';
|
|
import '../../core/constants/route_constants.dart';
|
|
import '../../core/theme/app_colors.dart';
|
|
import '../../core/utils/responsive_utils.dart';
|
|
import '../../core/constants/enums.dart';
|
|
import '../../modules/settings/presentation/providers/settings_provider.dart';
|
|
import '../models/user_model.dart';
|
|
import '../providers/auth_provider.dart';
|
|
import '../routes/menu_config.dart' as menu;
|
|
import '../utils/navigation_utils.dart';
|
|
import 'app_sidebar.dart';
|
|
import 'app_top_nav.dart';
|
|
import 'theme_keyed_subtree.dart';
|
|
|
|
class AppShell extends ConsumerStatefulWidget {
|
|
const AppShell({super.key, required this.child});
|
|
|
|
final Widget child;
|
|
|
|
@override
|
|
ConsumerState<AppShell> createState() => _AppShellState();
|
|
}
|
|
|
|
class _AppShellState extends ConsumerState<AppShell> {
|
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
|
bool _sidebarCollapsed = false;
|
|
|
|
Widget _pageContent(BuildContext context, String currentRoute, Widget child) {
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: Material(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
surfaceTintColor: Colors.transparent,
|
|
child: ThemeKeyedSubtree(
|
|
pageKey: currentRoute,
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final authState = ref.watch(authStateProvider);
|
|
final user = authState.user;
|
|
final currentRoute = GoRouterState.of(context).matchedLocation;
|
|
|
|
final menuItems = user != null
|
|
? menu.getVisibleMenuItems(
|
|
permissions: user.permissions,
|
|
role: user.userRole,
|
|
)
|
|
: <menu.MenuItem>[];
|
|
|
|
if (context.isMobile) {
|
|
return Scaffold(
|
|
key: _scaffoldKey,
|
|
appBar: AppBar(
|
|
title: const Text(AppConstants.appName),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.menu),
|
|
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
|
|
),
|
|
actions: [
|
|
if (DevConfig.screenPreviewEnabled)
|
|
IconButton(
|
|
icon: const Icon(Icons.apps_outlined),
|
|
tooltip: 'All Screens',
|
|
onPressed: () => context.go(RouteConstants.screenGallery),
|
|
),
|
|
_UserMenu(userName: user?.name),
|
|
],
|
|
),
|
|
drawer: _AppDrawer(
|
|
menuItems: menuItems,
|
|
currentRoute: currentRoute,
|
|
user: user,
|
|
onItemTap: (route) {
|
|
goAndDismissOverlays(context, route);
|
|
_scaffoldKey.currentState?.closeDrawer();
|
|
},
|
|
),
|
|
body: _pageContent(context, currentRoute, widget.child),
|
|
);
|
|
}
|
|
|
|
final navigationLayout = NavigationLayout.fromValue(
|
|
ref.watch(appSettingsProvider).uiPreferences.navigationLayout,
|
|
);
|
|
final useTopNav = navigationLayout == NavigationLayout.top;
|
|
|
|
if (useTopNav) {
|
|
return Scaffold(
|
|
backgroundColor: Theme.of(context).brightness == Brightness.dark
|
|
? AppColors.darkBackground
|
|
: AppColors.lightBackground,
|
|
body: Column(
|
|
children: [
|
|
AppTopNav(
|
|
menuItems: menuItems,
|
|
currentRoute: currentRoute,
|
|
user: user,
|
|
onItemTap: (route) => goAndDismissOverlays(context, route),
|
|
),
|
|
Expanded(
|
|
child: _pageContent(context, currentRoute, widget.child),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
backgroundColor: Theme.of(context).brightness == Brightness.dark
|
|
? AppColors.darkBackground
|
|
: AppColors.lightBackground,
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
AppSidebar(
|
|
menuItems: menuItems,
|
|
currentRoute: currentRoute,
|
|
user: user,
|
|
collapsed: _sidebarCollapsed,
|
|
onToggleCollapse: () => setState(() => _sidebarCollapsed = !_sidebarCollapsed),
|
|
onItemTap: (route) => goAndDismissOverlays(context, route),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _pageContent(context, currentRoute, widget.child),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AppDrawer extends ConsumerWidget {
|
|
const _AppDrawer({
|
|
required this.menuItems,
|
|
required this.currentRoute,
|
|
required this.onItemTap,
|
|
this.user,
|
|
});
|
|
|
|
final List<menu.MenuItem> menuItems;
|
|
final String currentRoute;
|
|
final void Function(String route) onItemTap;
|
|
final UserModel? user;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return Drawer(
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.only(
|
|
topRight: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: AppSidebar(
|
|
menuItems: menuItems,
|
|
currentRoute: currentRoute,
|
|
user: user,
|
|
isDrawer: true,
|
|
onItemTap: onItemTap,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _UserMenu extends ConsumerWidget {
|
|
const _UserMenu({this.userName});
|
|
|
|
final String? userName;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return PopupMenuButton<String>(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 16,
|
|
child: Text((userName ?? 'U')[0].toUpperCase()),
|
|
),
|
|
const SizedBox(width: 8),
|
|
if (!context.isMobile) Text(userName ?? 'User'),
|
|
const Icon(Icons.arrow_drop_down),
|
|
],
|
|
),
|
|
),
|
|
onSelected: (value) async {
|
|
switch (value) {
|
|
case 'password':
|
|
context.push(RouteConstants.changePassword);
|
|
case 'settings':
|
|
goAndDismissOverlays(context, RouteConstants.settings);
|
|
case 'logout':
|
|
await ref.read(authStateProvider.notifier).logout();
|
|
if (context.mounted) context.go(RouteConstants.login);
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
const PopupMenuItem(value: 'password', child: Text('Change Password')),
|
|
const PopupMenuItem(value: 'settings', child: Text('Settings')),
|
|
const PopupMenuDivider(),
|
|
const PopupMenuItem(value: 'logout', child: Text('Logout')),
|
|
],
|
|
);
|
|
}
|
|
}
|