2025-09-15 13:53:54 +03:30
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
2025-09-18 10:44:23 +03:30
|
|
|
import 'package:flutter_web_plugins/url_strategy.dart';
|
2025-09-15 13:53:54 +03:30
|
|
|
|
|
|
|
|
import 'pages/login_page.dart';
|
|
|
|
|
import 'pages/profile/profile_shell.dart';
|
|
|
|
|
import 'pages/profile/profile_dashboard_page.dart';
|
2025-09-16 00:44:44 +03:30
|
|
|
import 'pages/profile/new_business_page.dart';
|
|
|
|
|
import 'pages/profile/businesses_page.dart';
|
|
|
|
|
import 'pages/profile/support_page.dart';
|
|
|
|
|
import 'pages/profile/change_password_page.dart';
|
|
|
|
|
import 'pages/profile/marketing_page.dart';
|
2025-09-20 22:46:06 +03:30
|
|
|
import 'pages/profile/operator/operator_tickets_page.dart';
|
2025-09-19 16:40:05 +03:30
|
|
|
import 'pages/system_settings_page.dart';
|
2025-09-22 11:00:18 +03:30
|
|
|
import 'pages/admin/storage_management_page.dart';
|
|
|
|
|
import 'pages/admin/system_configuration_page.dart';
|
|
|
|
|
import 'pages/admin/user_management_page.dart';
|
|
|
|
|
import 'pages/admin/system_logs_page.dart';
|
2025-09-15 13:53:54 +03:30
|
|
|
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
|
|
|
|
import 'core/locale_controller.dart';
|
2025-09-18 10:44:23 +03:30
|
|
|
import 'core/calendar_controller.dart';
|
2025-09-15 13:53:54 +03:30
|
|
|
import 'core/api_client.dart';
|
|
|
|
|
import 'theme/theme_controller.dart';
|
|
|
|
|
import 'theme/app_theme.dart';
|
2025-09-15 21:50:09 +03:30
|
|
|
import 'core/auth_store.dart';
|
2025-09-19 16:40:05 +03:30
|
|
|
import 'core/permission_guard.dart';
|
2025-09-15 13:53:54 +03:30
|
|
|
|
|
|
|
|
void main() {
|
2025-09-18 10:44:23 +03:30
|
|
|
// Use path-based routing instead of hash routing
|
|
|
|
|
usePathUrlStrategy();
|
2025-09-15 13:53:54 +03:30
|
|
|
runApp(const MyApp());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MyApp extends StatefulWidget {
|
|
|
|
|
const MyApp({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<MyApp> createState() => _MyAppState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _MyAppState extends State<MyApp> {
|
|
|
|
|
LocaleController? _controller;
|
2025-09-18 10:44:23 +03:30
|
|
|
CalendarController? _calendarController;
|
2025-09-15 13:53:54 +03:30
|
|
|
ThemeController? _themeController;
|
2025-09-15 21:50:09 +03:30
|
|
|
AuthStore? _authStore;
|
2025-09-15 13:53:54 +03:30
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
LocaleController.load().then((c) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_controller = c
|
|
|
|
|
..addListener(() {
|
|
|
|
|
// Update ApiClient language header on change
|
|
|
|
|
ApiClient.setCurrentLocale(c.locale);
|
|
|
|
|
setState(() {});
|
|
|
|
|
});
|
|
|
|
|
ApiClient.setCurrentLocale(c.locale);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-18 10:44:23 +03:30
|
|
|
CalendarController.load().then((cc) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_calendarController = cc
|
|
|
|
|
..addListener(() {
|
|
|
|
|
setState(() {});
|
|
|
|
|
});
|
|
|
|
|
ApiClient.bindCalendarController(cc);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-15 13:53:54 +03:30
|
|
|
final tc = ThemeController();
|
|
|
|
|
tc.load().then((_) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_themeController = tc
|
|
|
|
|
..addListener(() {
|
|
|
|
|
setState(() {});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-09-15 21:50:09 +03:30
|
|
|
|
|
|
|
|
final store = AuthStore();
|
|
|
|
|
store.load().then((_) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_authStore = store
|
|
|
|
|
..addListener(() {
|
|
|
|
|
setState(() {});
|
|
|
|
|
});
|
|
|
|
|
ApiClient.bindAuthStore(store);
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-09-15 13:53:54 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Root of application with GoRouter
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-09-19 04:35:13 +03:30
|
|
|
// اگر هنوز loading است، یک router ساده با loading page بساز
|
2025-09-18 10:44:23 +03:30
|
|
|
if (_controller == null || _calendarController == null || _themeController == null || _authStore == null) {
|
2025-09-19 04:35:13 +03:30
|
|
|
final loadingRouter = GoRouter(
|
|
|
|
|
redirect: (context, state) {
|
|
|
|
|
// در حین loading، هیچ redirect نکن - URL را حفظ کن
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
routes: <RouteBase>[
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-18 10:44:23 +03:30
|
|
|
),
|
|
|
|
|
),
|
2025-09-19 04:35:13 +03:30
|
|
|
// برای سایر مسیرها هم loading page نمایش بده
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/login',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/dashboard',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/marketing',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/new-business',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/businesses',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/support',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/change-password',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-19 16:40:05 +03:30
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/system-settings',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-19 04:35:13 +03:30
|
|
|
// Catch-all route برای هر URL دیگر
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/:path(.*)',
|
|
|
|
|
builder: (context, state) => const Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
CircularProgressIndicator(),
|
|
|
|
|
SizedBox(height: 16),
|
|
|
|
|
Text('Loading...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return MaterialApp.router(
|
|
|
|
|
title: 'Hesabix',
|
|
|
|
|
routerConfig: loadingRouter,
|
|
|
|
|
locale: const Locale('en'),
|
|
|
|
|
supportedLocales: const [Locale('en'), Locale('fa')],
|
|
|
|
|
localizationsDelegates: const [
|
|
|
|
|
GlobalMaterialLocalizations.delegate,
|
|
|
|
|
GlobalCupertinoLocalizations.delegate,
|
|
|
|
|
GlobalWidgetsLocalizations.delegate,
|
|
|
|
|
],
|
2025-09-15 13:53:54 +03:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final controller = _controller!;
|
|
|
|
|
final themeController = _themeController!;
|
|
|
|
|
|
|
|
|
|
final router = GoRouter(
|
2025-09-18 10:44:23 +03:30
|
|
|
initialLocation: '/',
|
2025-09-16 00:10:20 +03:30
|
|
|
redirect: (context, state) {
|
2025-09-18 10:44:23 +03:30
|
|
|
final currentPath = state.uri.path;
|
|
|
|
|
|
|
|
|
|
// اگر authStore هنوز load نشده، منتظر بمان
|
|
|
|
|
if (_authStore == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-16 00:10:20 +03:30
|
|
|
final hasKey = _authStore!.apiKey != null && _authStore!.apiKey!.isNotEmpty;
|
2025-09-18 10:44:23 +03:30
|
|
|
|
|
|
|
|
// اگر API key ندارد
|
|
|
|
|
if (!hasKey) {
|
|
|
|
|
// اگر در login نیست، به login برود
|
|
|
|
|
if (currentPath != '/login') {
|
|
|
|
|
return '/login';
|
|
|
|
|
}
|
|
|
|
|
// اگر در login است، بماند
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// اگر API key دارد
|
|
|
|
|
// اگر در login است، به dashboard برود
|
|
|
|
|
if (currentPath == '/login') {
|
|
|
|
|
return '/user/profile/dashboard';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// اگر در root است، به dashboard برود
|
|
|
|
|
if (currentPath == '/') {
|
|
|
|
|
return '/user/profile/dashboard';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// برای سایر صفحات (شامل صفحات profile)، redirect نکن (بماند)
|
2025-09-19 04:35:13 +03:30
|
|
|
// این مهم است: اگر کاربر در صفحات profile است، بماند
|
2025-09-16 00:10:20 +03:30
|
|
|
return null;
|
|
|
|
|
},
|
2025-09-15 13:53:54 +03:30
|
|
|
routes: <RouteBase>[
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/login',
|
|
|
|
|
name: 'login',
|
|
|
|
|
builder: (context, state) => LoginPage(
|
|
|
|
|
localeController: controller,
|
2025-09-18 10:44:23 +03:30
|
|
|
calendarController: _calendarController!,
|
2025-09-15 13:53:54 +03:30
|
|
|
themeController: themeController,
|
2025-09-15 21:50:09 +03:30
|
|
|
authStore: _authStore!,
|
2025-09-15 13:53:54 +03:30
|
|
|
),
|
|
|
|
|
),
|
2025-09-18 10:44:23 +03:30
|
|
|
ShellRoute(
|
|
|
|
|
builder: (context, state, child) => ProfileShell(
|
|
|
|
|
authStore: _authStore!,
|
2025-09-15 13:53:54 +03:30
|
|
|
localeController: controller,
|
2025-09-18 10:44:23 +03:30
|
|
|
calendarController: _calendarController!,
|
2025-09-15 13:53:54 +03:30
|
|
|
themeController: themeController,
|
2025-09-18 10:44:23 +03:30
|
|
|
child: child,
|
2025-09-15 13:53:54 +03:30
|
|
|
),
|
|
|
|
|
routes: [
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/dashboard',
|
|
|
|
|
name: 'profile_dashboard',
|
|
|
|
|
builder: (context, state) => const ProfileDashboardPage(),
|
|
|
|
|
),
|
2025-09-16 00:44:44 +03:30
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/new-business',
|
|
|
|
|
name: 'profile_new_business',
|
|
|
|
|
builder: (context, state) => const NewBusinessPage(),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/businesses',
|
|
|
|
|
name: 'profile_businesses',
|
|
|
|
|
builder: (context, state) => const BusinessesPage(),
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/support',
|
|
|
|
|
name: 'profile_support',
|
2025-09-20 22:46:06 +03:30
|
|
|
builder: (context, state) => SupportPage(calendarController: _calendarController),
|
2025-09-16 00:44:44 +03:30
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/marketing',
|
|
|
|
|
name: 'profile_marketing',
|
2025-09-18 10:44:23 +03:30
|
|
|
builder: (context, state) => MarketingPage(calendarController: _calendarController!),
|
2025-09-16 00:44:44 +03:30
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/change-password',
|
|
|
|
|
name: 'profile_change_password',
|
|
|
|
|
builder: (context, state) => const ChangePasswordPage(),
|
|
|
|
|
),
|
2025-09-20 22:46:06 +03:30
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/operator',
|
|
|
|
|
name: 'profile_operator',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
// بررسی دسترسی اپراتور پشتیبانی
|
|
|
|
|
if (_authStore == null) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_authStore!.canAccessSupportOperator) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return OperatorTicketsPage(calendarController: _calendarController);
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-09-19 16:40:05 +03:30
|
|
|
GoRoute(
|
|
|
|
|
path: '/user/profile/system-settings',
|
|
|
|
|
name: 'profile_system_settings',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
// بررسی دسترسی SuperAdmin
|
|
|
|
|
if (_authStore == null) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_authStore!.isSuperAdmin) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return const SystemSettingsPage();
|
|
|
|
|
},
|
2025-09-22 11:00:18 +03:30
|
|
|
routes: [
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: 'storage',
|
|
|
|
|
name: 'system_settings_storage',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
if (_authStore == null || !_authStore!.isSuperAdmin) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return const AdminStorageManagementPage();
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: 'configuration',
|
|
|
|
|
name: 'system_settings_configuration',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
if (_authStore == null || !_authStore!.isSuperAdmin) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return const SystemConfigurationPage();
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: 'users',
|
|
|
|
|
name: 'system_settings_users',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
if (_authStore == null || !_authStore!.isSuperAdmin) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return const UserManagementPage();
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
GoRoute(
|
|
|
|
|
path: 'logs',
|
|
|
|
|
name: 'system_settings_logs',
|
|
|
|
|
builder: (context, state) {
|
|
|
|
|
if (_authStore == null || !_authStore!.isSuperAdmin) {
|
|
|
|
|
return PermissionGuard.buildAccessDeniedPage();
|
|
|
|
|
}
|
|
|
|
|
return const SystemLogsPage();
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
],
|
2025-09-19 16:40:05 +03:30
|
|
|
),
|
2025-09-15 13:53:54 +03:30
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return AnimatedBuilder(
|
|
|
|
|
animation: Listenable.merge([controller, themeController]),
|
|
|
|
|
builder: (context, _) {
|
|
|
|
|
return MaterialApp.router(
|
|
|
|
|
title: 'Hesabix',
|
|
|
|
|
theme: AppTheme.build(
|
|
|
|
|
isDark: false,
|
|
|
|
|
locale: controller.locale,
|
|
|
|
|
seed: themeController.seedColor,
|
|
|
|
|
),
|
|
|
|
|
darkTheme: AppTheme.build(
|
|
|
|
|
isDark: true,
|
|
|
|
|
locale: controller.locale,
|
|
|
|
|
seed: themeController.seedColor,
|
|
|
|
|
),
|
|
|
|
|
themeMode: themeController.mode,
|
|
|
|
|
routerConfig: router,
|
|
|
|
|
locale: controller.locale,
|
|
|
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
|
|
|
localizationsDelegates: const [
|
|
|
|
|
AppLocalizations.delegate,
|
|
|
|
|
GlobalMaterialLocalizations.delegate,
|
|
|
|
|
GlobalCupertinoLocalizations.delegate,
|
|
|
|
|
GlobalWidgetsLocalizations.delegate,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|