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-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-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...'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
// 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',
|
|
|
|
|
builder: (context, state) => const SupportPage(),
|
|
|
|
|
),
|
|
|
|
|
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-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,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|