bharat_erp/lib/shared/widgets/app_shell.dart
2026-07-15 15:42:34 +05:30

227 lines
6.6 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';
import 'user_avatar.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,
avatarUrl: user?.avatarUrl,
),
],
),
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, this.avatarUrl});
final String? userName;
final String? avatarUrl;
@override
Widget build(BuildContext context, WidgetRef ref) {
return PopupMenuButton<String>(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
UserAvatar(
name: userName ?? 'User',
avatarUrl: avatarUrl,
radius: 16,
),
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 '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 PopupMenuDivider(),
const PopupMenuItem(value: 'logout', child: Text('Logout')),
],
);
}
}