Files
monisuo/flutter_monisuo/lib/main.dart
2026-04-21 08:09:45 +08:00

188 lines
6.0 KiB
Dart
Raw Permalink 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 'dart:ui';
import 'package:flutter/material.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'core/network/dio_client.dart';
import 'core/network/domain_navigator.dart';
import 'core/storage/local_storage.dart';
import 'core/constants/api_endpoints.dart';
import 'core/theme/app_theme.dart';
import 'core/event/app_event_bus.dart';
import 'data/services/user_service.dart';
import 'data/services/market_service.dart';
import 'data/services/trade_service.dart';
import 'data/services/asset_service.dart';
import 'data/services/fund_service.dart';
import 'data/services/bonus_service.dart';
import 'data/services/config_service.dart';
import 'providers/auth_provider.dart';
import 'providers/market_provider.dart';
import 'providers/asset_provider.dart';
import 'providers/trade_provider.dart';
import 'providers/theme_provider.dart';
import 'ui/pages/auth/login_page.dart';
import 'ui/pages/main/main_page.dart';
import 'ui/pages/onboarding/onboarding_page.dart';
/// 全局導航 Key用於從任意位置跳轉到登錄頁
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
// 確保 Flutter 綁定初始化
WidgetsFlutterBinding.ensureInitialized();
// 防截图Android 端通过 FLAG_SECURE在 MainActivity 中已设置)
// Web 端通过 index.html 中的 JS/CSS 防护
// iOS 端暂无原生防截图能力Web 模式下同样由 JS 保护)
// 全局錯誤處理 - Flutter 框架錯誤
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
};
// 全局錯誤處理 - 異步未捕獲錯誤
PlatformDispatcher.instance.onError = (error, stack) {
return true;
};
Provider.debugCheckInvalidValueType = null;
try {
await SharedPreferences.getInstance();
await LocalStorage.init();
// 域名導航Debug 用 localhostRelease 競速 CF Workers
final resolvedUrl = await DomainNavigator.init();
ApiEndpoints.init(resolvedUrl);
} catch (e, stack) {
// 域名解析失敗時使用兜底地址
ApiEndpoints.init(DomainNavigator.activeUrl);
}
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: _buildProviders(),
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, _) {
return _buildMaterialApp(context, themeProvider.themeMode);
},
),
);
}
List<SingleChildWidget> _buildProviders() {
final dioClient = DioClient();
return [
// Theme Provider (必須放在最前面)
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider()..init(),
),
// Services
Provider<DioClient>.value(value: dioClient),
Provider<AppEventBus>(create: (_) => AppEventBus()),
Provider<UserService>(create: (_) => UserService(dioClient)),
Provider<MarketService>(create: (_) => MarketService(dioClient)),
Provider<TradeService>(create: (_) => TradeService(dioClient)),
Provider<AssetService>(create: (_) => AssetService(dioClient)),
Provider<FundService>(create: (_) => FundService(dioClient)),
Provider<BonusService>(create: (_) => BonusService(dioClient)),
Provider<ConfigService>(create: (_) => ConfigService(dioClient)),
// State Management
ChangeNotifierProvider<AuthProvider>(
create: (ctx) {
final authProvider = AuthProvider(ctx.read<UserService>());
// token 過期時DioClient 回調 AuthProvider 強制登出
dioClient.onUnauthorized = authProvider.forceLogout;
return authProvider;
},
),
ChangeNotifierProvider<MarketProvider>(
create: (ctx) => MarketProvider(ctx.read<MarketService>()),
),
ChangeNotifierProvider<AssetProvider>(
create: (ctx) => AssetProvider(
ctx.read<AssetService>(),
ctx.read<FundService>(),
ctx.read<TradeService>(),
ctx.read<AppEventBus>(),
),
),
ChangeNotifierProvider<TradeProvider>(
create: (ctx) => TradeProvider(
ctx.read<MarketService>(),
ctx.read<TradeService>(),
),
),
];
}
Widget _buildMaterialApp(BuildContext context, ThemeMode themeMode) {
return MaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeMode,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
builder: (context, child) {
// 配置 BotToast 確保顯示在所有內容之上
final botToastBuilder = BotToastInit();
child = botToastBuilder(context, child);
return child;
},
navigatorObservers: [BotToastNavigatorObserver()],
initialRoute: '/',
routes: {
'/': (context) => const RootPage(),
'/login': (context) => const LoginPage(),
'/main': (context) => const MainPage(),
},
);
}
}
/// 根頁面 - 決定顯示引導頁還是主頁面
class RootPage extends StatelessWidget {
const RootPage({super.key});
@override
Widget build(BuildContext context) {
// 檢查是否需要顯示引導頁
if (!LocalStorage.isOnboardingCompleted) {
return OnboardingPage(
onComplete: () {
Navigator.of(context).pushReplacementNamed('/login');
},
);
}
return Consumer<AuthProvider>(
builder: (context, auth, _) {
if (auth.isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return auth.isLoggedIn ? const MainPage() : const LoginPage();
},
);
}
}