Files
monisuo/flutter_monisuo/lib/main.dart
2026-04-18 10:02:47 +08:00

177 lines
5.6 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 '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 'providers/auth_provider.dart';
import 'providers/market_provider.dart';
import 'providers/asset_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';
void main() async {
// 確保 Flutter 綁定初始化
WidgetsFlutterBinding.ensureInitialized();
// 全局錯誤處理 - Flutter 框架錯誤
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
debugPrint('Flutter Error: ${details.exception}');
debugPrint('Stack trace: ${details.stack}');
};
// 全局錯誤處理 - 異步未捕獲錯誤
PlatformDispatcher.instance.onError = (error, stack) {
debugPrint('Uncaught error: $error');
debugPrint('Stack: $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);
debugPrint('App initialized with domain: $resolvedUrl');
} catch (e, stack) {
// 域名解析失敗時使用兜底地址
ApiEndpoints.init(DomainNavigator.activeUrl);
debugPrint('Initialization error: $e');
debugPrint('Stack: $stack');
}
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)),
// 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<AppEventBus>(),
),
),
];
}
Widget _buildMaterialApp(BuildContext context, ThemeMode themeMode) {
return MaterialApp(
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();
},
);
}
}