258 lines
10 KiB
Dart
258 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:fluwx/fluwx.dart';
|
||
import 'app/theme/app_theme.dart';
|
||
import 'app/main_shell.dart';
|
||
import 'app/i18n/app_localizations.dart';
|
||
import 'app/i18n/locale_manager.dart';
|
||
import 'core/services/auth_service.dart';
|
||
import 'core/updater/update_service.dart';
|
||
import 'core/telemetry/telemetry_service.dart';
|
||
import 'core/updater/models/update_config.dart';
|
||
import 'core/push/push_service.dart';
|
||
import 'core/providers/notification_badge_manager.dart';
|
||
import 'features/auth/presentation/pages/login_page.dart';
|
||
import 'features/auth/presentation/pages/welcome_page.dart';
|
||
import 'features/auth/presentation/pages/register_page.dart';
|
||
import 'features/auth/presentation/pages/forgot_password_page.dart';
|
||
import 'features/auth/presentation/providers/auth_provider.dart';
|
||
import 'features/coupons/presentation/pages/coupon_detail_page.dart';
|
||
import 'features/coupons/presentation/pages/order_confirm_page.dart';
|
||
import 'features/coupons/presentation/pages/payment_page.dart';
|
||
import 'features/coupons/presentation/pages/payment_success_page.dart';
|
||
import 'features/coupons/presentation/pages/search_page.dart';
|
||
import 'features/coupons/presentation/pages/my_coupon_detail_page.dart';
|
||
import 'features/coupons/presentation/pages/redeem_qr_page.dart';
|
||
import 'features/trading/presentation/pages/trading_page.dart';
|
||
import 'features/trading/presentation/pages/transfer_page.dart';
|
||
import 'features/trading/presentation/pages/sell_order_page.dart';
|
||
import 'features/wallet/presentation/pages/wallet_page.dart';
|
||
import 'features/wallet/presentation/pages/deposit_page.dart';
|
||
import 'features/wallet/presentation/pages/withdraw_page.dart';
|
||
import 'features/wallet/presentation/pages/transaction_records_page.dart';
|
||
import 'features/profile/presentation/pages/kyc_page.dart';
|
||
import 'features/profile/presentation/pages/settings_page.dart';
|
||
import 'features/profile/presentation/pages/payment_management_page.dart';
|
||
import 'features/profile/presentation/pages/pro_mode_page.dart';
|
||
import 'features/ai_agent/presentation/pages/agent_chat_page.dart';
|
||
import 'features/message/presentation/pages/message_detail_page.dart';
|
||
import 'features/issuer/presentation/pages/issuer_main_page.dart';
|
||
import 'features/merchant/presentation/pages/merchant_home_page.dart';
|
||
import 'features/trading/presentation/pages/trading_detail_page.dart';
|
||
import 'features/coupons/presentation/pages/wallet_coupons_page.dart';
|
||
import 'features/profile/presentation/pages/share_page.dart';
|
||
|
||
Future<void> main() async {
|
||
WidgetsFlutterBinding.ensureInitialized();
|
||
|
||
// ── 微信 SDK 初始化 (fluwx 5.x) ──────────────────────────────────────
|
||
const wechatAppId = String.fromEnvironment('WECHAT_APP_ID', defaultValue: '');
|
||
if (wechatAppId.isNotEmpty) {
|
||
await Fluwx().registerApi(
|
||
appId: wechatAppId,
|
||
universalLink: 'https://www.gogenex.com/wechat/',
|
||
);
|
||
}
|
||
|
||
// 初始化升级服务(走 Nginx 反向代理 → Kong 网关)
|
||
UpdateService().initialize(UpdateConfig.selfHosted(
|
||
apiBaseUrl: 'https://api.gogenex.com',
|
||
enabled: true,
|
||
));
|
||
|
||
// 初始化推送服务(无 Firebase 配置时静默失败)
|
||
await PushService().initialize();
|
||
|
||
// 初始化通知徽章管理器
|
||
NotificationBadgeManager().initialize();
|
||
|
||
// 恢复用户语言偏好(无选择时跟随系统语言)
|
||
await LocaleManager.init();
|
||
|
||
// 从安全存储恢复上次登录的 Token
|
||
// ProviderScope 外提前恢复,确保 initialRoute 能正确判断
|
||
final isLoggedIn = await AuthService.instance.restoreSession();
|
||
|
||
runApp(
|
||
ProviderScope(
|
||
child: GenexConsumerApp(initiallyLoggedIn: isLoggedIn),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Genex Mobile - 券的生命周期管理平台
|
||
class GenexConsumerApp extends ConsumerStatefulWidget {
|
||
final bool initiallyLoggedIn;
|
||
|
||
const GenexConsumerApp({super.key, required this.initiallyLoggedIn});
|
||
|
||
@override
|
||
ConsumerState<GenexConsumerApp> createState() => _GenexConsumerAppState();
|
||
}
|
||
|
||
class _GenexConsumerAppState extends ConsumerState<GenexConsumerApp> {
|
||
final _navigatorKey = GlobalKey<NavigatorState>();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
LocaleManager.userLocale.addListener(_onLocaleChanged);
|
||
|
||
// 监听 AuthService ValueNotifier → 同步到 Riverpod authProvider
|
||
// ApiClient 的 Token 刷新/过期回调仍走 AuthService,此处桥接到 Riverpod
|
||
AuthService.instance.authState.addListener(_onLegacyAuthChanged);
|
||
|
||
// 启动时已登录,同步初始状态到 Riverpod authProvider
|
||
if (widget.initiallyLoggedIn) {
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
ref.read(authProvider.notifier).restoreSession();
|
||
});
|
||
}
|
||
|
||
// 遥测服务初始化(需 BuildContext 采集设备信息,在首帧渲染后执行)
|
||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||
final auth = AuthService.instance.authState.value;
|
||
await TelemetryService().initialize(
|
||
apiBaseUrl: 'https://api.gogenex.com',
|
||
context: context,
|
||
userId: auth?.user['id'] as String?,
|
||
);
|
||
if (auth != null) {
|
||
TelemetryService().setAccessToken(auth.accessToken);
|
||
}
|
||
});
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
LocaleManager.userLocale.removeListener(_onLocaleChanged);
|
||
AuthService.instance.authState.removeListener(_onLegacyAuthChanged);
|
||
super.dispose();
|
||
}
|
||
|
||
void _onLocaleChanged() => setState(() {});
|
||
|
||
/// AuthService ValueNotifier 变化时同步到 Riverpod authProvider
|
||
void _onLegacyAuthChanged() {
|
||
if (!mounted) return;
|
||
final legacyResult = AuthService.instance.authState.value;
|
||
final riverpodState = ref.read(authProvider);
|
||
|
||
if (legacyResult == null && riverpodState.isAuthenticated) {
|
||
// Token 双重过期:AuthService 已清空,通知 Riverpod 同步
|
||
ref.read(authProvider.notifier).onSessionExpired();
|
||
_navigatorKey.currentState?.pushNamedAndRemoveUntil('/', (_) => false);
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final isAuthenticated = ref.watch(isAuthenticatedProvider);
|
||
|
||
return MaterialApp(
|
||
title: 'Genex',
|
||
theme: AppTheme.light,
|
||
debugShowCheckedModeBanner: false,
|
||
navigatorKey: _navigatorKey,
|
||
|
||
// i18n
|
||
locale: LocaleManager.userLocale.value,
|
||
supportedLocales: LocaleManager.supportedLocales,
|
||
localizationsDelegates: const [
|
||
AppLocalizationsDelegate(),
|
||
GlobalMaterialLocalizations.delegate,
|
||
GlobalWidgetsLocalizations.delegate,
|
||
GlobalCupertinoLocalizations.delegate,
|
||
],
|
||
localeResolutionCallback: (systemLocale, supportedLocales) {
|
||
if (LocaleManager.userLocale.value == null) {
|
||
return LocaleManager.resolve(
|
||
systemLocale != null ? [systemLocale] : null,
|
||
supportedLocales,
|
||
);
|
||
}
|
||
return LocaleManager.userLocale.value;
|
||
},
|
||
|
||
// 启动路由:已有保存的 Token → 直接进主界面;否则欢迎页
|
||
initialRoute: (widget.initiallyLoggedIn || isAuthenticated) ? '/main' : '/',
|
||
onGenerateRoute: _generateRoute,
|
||
);
|
||
}
|
||
|
||
Route<dynamic> _generateRoute(RouteSettings settings) {
|
||
switch (settings.name) {
|
||
case '/':
|
||
return MaterialPageRoute(builder: (_) => const WelcomePage());
|
||
case '/login':
|
||
return MaterialPageRoute(builder: (_) => const LoginPage());
|
||
case '/register':
|
||
final regArgs = settings.arguments as Map?;
|
||
return MaterialPageRoute(
|
||
builder: (_) => RegisterPage(isEmail: regArgs?['isEmail'] == true),
|
||
);
|
||
case '/forgot-password':
|
||
return MaterialPageRoute(builder: (_) => const ForgotPasswordPage());
|
||
case '/main':
|
||
return MaterialPageRoute(builder: (_) => const MainShell());
|
||
case '/coupon/detail':
|
||
return MaterialPageRoute(builder: (_) => const CouponDetailPage());
|
||
case '/order/confirm':
|
||
return MaterialPageRoute(builder: (_) => const OrderConfirmPage());
|
||
case '/payment':
|
||
return MaterialPageRoute(builder: (_) => const PaymentPage());
|
||
case '/payment/success':
|
||
return MaterialPageRoute(builder: (_) => const PaymentSuccessPage());
|
||
case '/search':
|
||
return MaterialPageRoute(builder: (_) => const SearchPage());
|
||
case '/coupon/mine/detail':
|
||
return MaterialPageRoute(builder: (_) => const MyCouponDetailPage());
|
||
case '/redeem':
|
||
return MaterialPageRoute(builder: (_) => const RedeemQrPage());
|
||
case '/trading':
|
||
return MaterialPageRoute(builder: (_) => const TradingPage());
|
||
case '/transfer':
|
||
return MaterialPageRoute(builder: (_) => const TransferPage());
|
||
case '/sell':
|
||
return MaterialPageRoute(builder: (_) => const SellOrderPage());
|
||
case '/wallet':
|
||
return MaterialPageRoute(builder: (_) => const WalletPage());
|
||
case '/wallet/deposit':
|
||
return MaterialPageRoute(builder: (_) => const DepositPage());
|
||
case '/wallet/withdraw':
|
||
return MaterialPageRoute(builder: (_) => const WithdrawPage());
|
||
case '/wallet/records':
|
||
return MaterialPageRoute(builder: (_) => const TransactionRecordsPage());
|
||
case '/kyc':
|
||
return MaterialPageRoute(builder: (_) => const KycPage());
|
||
case '/settings':
|
||
return MaterialPageRoute(builder: (_) => const SettingsPage());
|
||
case '/payment/manage':
|
||
return MaterialPageRoute(builder: (_) => const PaymentManagementPage());
|
||
case '/pro-mode':
|
||
return MaterialPageRoute(builder: (_) => const ProModePage());
|
||
case '/ai-chat':
|
||
return MaterialPageRoute(builder: (_) => const AgentChatPage());
|
||
case '/message/detail':
|
||
return MaterialPageRoute(builder: (_) => const MessageDetailPage());
|
||
case '/issuer':
|
||
return MaterialPageRoute(builder: (_) => const IssuerMainPage());
|
||
case '/merchant':
|
||
return MaterialPageRoute(builder: (_) => const MerchantHomePage());
|
||
case '/trading/detail':
|
||
return MaterialPageRoute(builder: (_) => const TradingDetailPage());
|
||
case '/wallet/coupons':
|
||
return MaterialPageRoute(builder: (_) => const WalletCouponsPage());
|
||
case '/share':
|
||
return MaterialPageRoute(builder: (_) => const SharePage());
|
||
default:
|
||
return MaterialPageRoute(
|
||
builder: (_) => Scaffold(
|
||
body: Center(child: Text('Route not found: ${settings.name}')),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|