hesabixArc/hesabixUI/hesabix_ui/lib/pages/profile/profile_shell.dart

294 lines
11 KiB
Dart
Raw Normal View History

2025-09-15 13:53:54 +03:30
import 'package:flutter/material.dart';
2025-09-16 00:10:20 +03:30
import 'package:go_router/go_router.dart';
import '../../core/auth_store.dart';
import '../../core/locale_controller.dart';
2025-09-18 10:44:23 +03:30
import '../../core/calendar_controller.dart';
2025-09-16 00:10:20 +03:30
import '../../theme/theme_controller.dart';
import '../../widgets/language_switcher.dart';
2025-09-18 10:44:23 +03:30
import '../../widgets/calendar_switcher.dart';
2025-09-16 00:10:20 +03:30
import '../../widgets/theme_mode_switcher.dart';
import '../../widgets/logout_button.dart';
import 'package:hesabix_ui/l10n/app_localizations.dart';
2025-09-15 13:53:54 +03:30
2025-09-16 00:44:44 +03:30
class ProfileShell extends StatefulWidget {
2025-09-15 13:53:54 +03:30
final Widget child;
2025-09-16 00:10:20 +03:30
final AuthStore authStore;
final LocaleController? localeController;
2025-09-18 10:44:23 +03:30
final CalendarController? calendarController;
2025-09-16 00:10:20 +03:30
final ThemeController? themeController;
2025-09-18 10:44:23 +03:30
const ProfileShell({super.key, required this.child, required this.authStore, this.localeController, this.calendarController, this.themeController});
2025-09-15 13:53:54 +03:30
2025-09-16 00:44:44 +03:30
@override
State<ProfileShell> createState() => _ProfileShellState();
}
class _ProfileShellState extends State<ProfileShell> {
int _hoverIndex = -1;
2025-09-19 16:40:05 +03:30
@override
void initState() {
super.initState();
// اضافه کردن listener برای AuthStore
widget.authStore.addListener(() {
if (mounted) {
setState(() {});
}
});
}
2025-09-15 13:53:54 +03:30
@override
Widget build(BuildContext context) {
2025-09-16 00:10:20 +03:30
final width = MediaQuery.of(context).size.width;
final bool useRail = width >= 700;
final bool railExtended = width >= 1100;
final ColorScheme scheme = Theme.of(context).colorScheme;
2025-09-25 01:01:27 +03:30
String location = '/user/profile/dashboard'; // default location
try {
location = GoRouterState.of(context).uri.toString();
} catch (e) {
// اگر GoRouterState در دسترس نیست، از default استفاده کن
}
2025-09-16 00:44:44 +03:30
final bool isDark = Theme.of(context).brightness == Brightness.dark;
final String logoAsset = isDark
? 'assets/images/logo-light.png'
: 'assets/images/logo-light.png';
2025-09-16 00:10:20 +03:30
final t = AppLocalizations.of(context);
final destinations = <_Dest>[
_Dest(t.dashboard, Icons.dashboard_outlined, Icons.dashboard, '/user/profile/dashboard'),
2025-09-16 00:44:44 +03:30
_Dest(t.newBusiness, Icons.add_business, Icons.add_business, '/user/profile/new-business'),
_Dest(t.businesses, Icons.business, Icons.business, '/user/profile/businesses'),
_Dest(t.support, Icons.support_agent, Icons.support_agent, '/user/profile/support'),
_Dest(t.marketing, Icons.campaign, Icons.campaign, '/user/profile/marketing'),
_Dest(t.changePassword, Icons.password, Icons.password, '/user/profile/change-password'),
2025-09-16 00:10:20 +03:30
];
2025-09-20 22:46:06 +03:30
// اضافه کردن منوی اپراتور پشتیبانی
final operatorDestinations = <_Dest>[
_Dest(t.operatorPanel, Icons.support_agent, Icons.support_agent, '/user/profile/operator'),
];
2025-09-19 16:40:05 +03:30
// اضافه کردن منوی تنظیمات سیستم برای ادمین‌ها
final adminDestinations = <_Dest>[
_Dest(t.systemSettings, Icons.admin_panel_settings, Icons.admin_panel_settings, '/user/profile/system-settings'),
];
2025-09-20 22:46:06 +03:30
// ترکیب منوهای عادی، اپراتور و ادمین
2025-09-19 16:40:05 +03:30
final allDestinations = <_Dest>[
...destinations,
2025-09-20 22:46:06 +03:30
if (widget.authStore.canAccessSupportOperator) ...operatorDestinations,
2025-11-09 08:46:37 +03:30
if (widget.authStore.isSuperAdmin || widget.authStore.hasAppPermission('system_settings')) ...adminDestinations,
2025-09-19 16:40:05 +03:30
];
2025-09-16 00:10:20 +03:30
int selectedIndex = 0;
2025-09-19 16:40:05 +03:30
for (int i = 0; i < allDestinations.length; i++) {
if (location.startsWith(allDestinations[i].path)) {
2025-09-16 00:10:20 +03:30
selectedIndex = i;
break;
}
}
Future<void> onSelect(int index) async {
2025-09-19 16:40:05 +03:30
final path = allDestinations[index].path;
2025-09-25 01:01:27 +03:30
try {
if (GoRouterState.of(context).uri.toString() != path) {
context.go(path);
}
} catch (e) {
// اگر GoRouterState در دسترس نیست، مستقیماً به مسیر برود
2025-09-16 00:10:20 +03:30
context.go(path);
}
}
Future<void> onLogout() async {
2025-09-16 00:44:44 +03:30
await widget.authStore.saveApiKey(null);
2025-09-16 00:10:20 +03:30
if (!context.mounted) return;
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
2025-09-25 01:18:59 +03:30
..showSnackBar(SnackBar(content: Text(t.logoutDone)));
2025-09-16 00:10:20 +03:30
context.go('/login');
}
// Brand top bar with contrast color
final Color appBarBg = Theme.of(context).brightness == Brightness.dark
2025-09-18 10:44:23 +03:30
? scheme.surfaceContainerHighest
2025-09-16 00:10:20 +03:30
: scheme.primary;
final Color appBarFg = Theme.of(context).brightness == Brightness.dark
? scheme.onSurfaceVariant
: scheme.onPrimary;
final appBar = AppBar(
backgroundColor: appBarBg,
foregroundColor: appBarFg,
titleSpacing: 0,
title: Row(
2025-09-15 13:53:54 +03:30
children: [
2025-09-16 00:10:20 +03:30
const SizedBox(width: 12),
2025-09-16 00:44:44 +03:30
Image.asset(logoAsset, height: 28),
2025-09-16 00:10:20 +03:30
const SizedBox(width: 12),
Text(t.appTitle, style: TextStyle(color: appBarFg, fontWeight: FontWeight.w700)),
],
),
leading: useRail
? null
: Builder(
builder: (ctx) => IconButton(
icon: Icon(Icons.menu, color: appBarFg),
onPressed: () => Scaffold.of(ctx).openDrawer(),
tooltip: t.menu,
),
),
actions: [
2025-09-18 10:44:23 +03:30
if (widget.calendarController != null) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: CalendarSwitcher(controller: widget.calendarController!),
),
2025-09-16 00:10:20 +03:30
],
2025-09-16 00:44:44 +03:30
if (widget.localeController != null) ...[
2025-09-18 10:44:23 +03:30
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: LanguageSwitcher(controller: widget.localeController!),
),
],
if (widget.themeController != null) ...[
ThemeModeSwitcher(controller: widget.themeController!),
2025-09-16 00:10:20 +03:30
const SizedBox(width: 8),
],
2025-09-16 00:44:44 +03:30
LogoutButton(authStore: widget.authStore),
2025-09-16 00:10:20 +03:30
],
);
final content = Container(
color: scheme.surface,
child: SafeArea(
child: widget.child,
2025-09-16 00:10:20 +03:30
),
);
2025-09-16 00:44:44 +03:30
// Side colors and styles
final Color sideBg = Theme.of(context).brightness == Brightness.dark
? scheme.surfaceContainerHighest
: scheme.surfaceContainerLow;
final Color sideFg = scheme.onSurfaceVariant;
final Color activeBg = scheme.primaryContainer;
final Color activeFg = scheme.onPrimaryContainer;
2025-09-16 00:10:20 +03:30
if (useRail) {
return Scaffold(
appBar: appBar,
body: Row(
children: [
2025-09-16 00:44:44 +03:30
Container(
width: railExtended ? 240 : 88,
height: double.infinity,
color: sideBg,
child: ListView.builder(
padding: EdgeInsets.zero,
2025-09-19 16:40:05 +03:30
itemCount: allDestinations.length,
2025-09-16 00:44:44 +03:30
itemBuilder: (ctx, i) {
2025-09-19 16:40:05 +03:30
final d = allDestinations[i];
2025-09-16 00:44:44 +03:30
final bool isHovered = i == _hoverIndex;
final bool isSelected = i == selectedIndex;
final bool active = isSelected || isHovered;
2025-09-16 00:53:51 +03:30
final BorderRadius br = (isSelected && useRail)
? BorderRadius.zero
: (isHovered ? BorderRadius.zero : BorderRadius.circular(8));
final Color bgColor = active
? (isHovered && !isSelected ? activeBg.withValues(alpha: 0.85) : activeBg)
: Colors.transparent;
2025-09-16 00:44:44 +03:30
return MouseRegion(
onEnter: (_) => setState(() => _hoverIndex = i),
onExit: (_) => setState(() => _hoverIndex = -1),
child: InkWell(
2025-09-16 00:53:51 +03:30
borderRadius: br,
2025-09-16 00:44:44 +03:30
onTap: () => onSelect(i),
child: Container(
margin: EdgeInsets.zero,
padding: EdgeInsets.symmetric(horizontal: railExtended ? 12 : 0, vertical: 10),
decoration: BoxDecoration(
2025-09-16 00:53:51 +03:30
color: bgColor,
borderRadius: br,
2025-09-16 00:44:44 +03:30
),
child: Row(
mainAxisAlignment: railExtended ? MainAxisAlignment.start : MainAxisAlignment.center,
children: [
Icon(d.icon, color: active ? activeFg : sideFg),
if (railExtended) ...[
const SizedBox(width: 12),
Expanded(
child: Text(
d.label,
style: TextStyle(
color: active ? activeFg : sideFg,
fontWeight: active ? FontWeight.w600 : FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
],
),
),
),
);
},
2025-09-16 00:10:20 +03:30
),
),
const VerticalDivider(width: 1),
Expanded(child: content),
],
),
);
}
return Scaffold(
appBar: appBar,
drawer: Drawer(
2025-09-16 00:44:44 +03:30
backgroundColor: sideBg,
2025-09-16 00:10:20 +03:30
child: SafeArea(
2025-09-16 00:44:44 +03:30
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
2025-09-16 00:10:20 +03:30
children: [
2025-09-19 16:40:05 +03:30
for (int i = 0; i < allDestinations.length; i++) ...[
2025-09-16 00:44:44 +03:30
Builder(builder: (ctx) {
2025-09-19 16:40:05 +03:30
final d = allDestinations[i];
2025-09-16 00:44:44 +03:30
final bool active = i == selectedIndex;
return ListTile(
leading: Icon(d.selectedIcon, color: active ? activeFg : sideFg),
title: Text(d.label, style: TextStyle(color: active ? activeFg : sideFg, fontWeight: active ? FontWeight.w600 : FontWeight.w400)),
selected: active,
selectedTileColor: activeBg,
onTap: () {
2025-09-25 01:01:27 +03:30
context.pop();
2025-09-16 00:44:44 +03:30
onSelect(i);
},
);
}),
],
const Divider(),
2025-09-16 00:10:20 +03:30
ListTile(
leading: const Icon(Icons.logout),
2025-09-16 00:44:44 +03:30
title: Text(t.logout),
2025-09-16 00:10:20 +03:30
onTap: onLogout,
),
2025-09-15 13:53:54 +03:30
],
),
2025-09-16 00:10:20 +03:30
),
2025-09-15 13:53:54 +03:30
),
2025-09-16 00:10:20 +03:30
body: content,
2025-09-15 13:53:54 +03:30
);
}
}
2025-09-16 00:10:20 +03:30
class _Dest {
final String label;
final IconData icon;
final IconData selectedIcon;
final String path;
const _Dest(this.label, this.icon, this.selectedIcon, this.path);
}
2025-09-15 13:53:54 +03:30