gcx/frontend/genex-mobile/lib/main.dart

278 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/telemetry/telemetry_route_observer.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();
// 全局 Flutter 框架错误上报(如 Widget build 异常)
// 仅在 release 模式下上报debug 模式保留默认的 red-screen
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.dumpErrorToConsole(details);
TelemetryService().logError(
details.exceptionAsString(),
error: details.exception,
stackTrace: details.stack,
extra: {
'source': 'flutter_framework',
'library': details.library ?? 'unknown',
},
);
};
// ── 微信 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();
// 恢复用户语言偏好(无选择时跟随系统语言)
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();
});
}
// 遥测服务 + 通知徽章管理器:首帧渲染后初始化,避免冷启动 DNS 竞争
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);
}
NotificationBadgeManager().initialize();
});
}
@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);
} else if (legacyResult != null) {
// 登录成功后立即刷新通知徽章
NotificationBadgeManager().refresh();
}
}
@override
Widget build(BuildContext context) {
final isAuthenticated = ref.watch(isAuthenticatedProvider);
return MaterialApp(
title: 'Genex',
theme: AppTheme.light,
debugShowCheckedModeBanner: false,
navigatorKey: _navigatorKey,
// 路由观察器:自动上报所有页面的 page_view 事件,支持 DAU/页面漏斗分析
navigatorObservers: [TelemetryRouteObserver.instance],
// 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}')),
),
);
}
}
}