import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'core/storage/local_storage.dart'; import 'core/di/injection_container.dart'; import 'core/utils/logger.dart'; import 'core/updater/update_service.dart'; import 'core/updater/models/update_config.dart'; import 'core/telemetry/telemetry_service.dart'; import 'core/sentry/sentry_service.dart'; import 'core/sentry/sentry_config.dart'; /// API 基础地址 const String _apiBaseUrl = 'https://rwaapi.szaiai.com'; /// Sentry DSN (从 Sentry 后台获取) /// 格式: http://public_key@host:port/project_id /// 生产环境请替换为实际的 DSN const String _sentryDsn = String.fromEnvironment( 'SENTRY_DSN', defaultValue: '', // 未配置时禁用 Sentry ); Future bootstrap(FutureOr Function() builder) async { // Ensure Flutter bindings are initialized WidgetsFlutterBinding.ensureInitialized(); // Set preferred orientations await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); // Set system UI overlay style SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.dark, ), ); // Initialize Hive await Hive.initFlutter(); // Initialize LocalStorage final localStorage = await LocalStorage.init(); // Initialize UpdateService (自建服务器模式) UpdateService().initialize( UpdateConfig.selfHosted( apiBaseUrl: _apiBaseUrl, enabled: true, checkIntervalSeconds: 86400, // 24小时 ), ); // Create provider container with initialized dependencies final container = createProviderContainer(localStorage); // 配置 Sentry final sentryConfig = _sentryDsn.isNotEmpty ? (kReleaseMode ? SentryConfig.production(dsn: _sentryDsn) : SentryConfig.development(dsn: _sentryDsn)) : SentryConfig.disabled(); // 使用 Sentry 包装启动流程 await SentryService.init( config: sentryConfig, appRunner: () async { // 设置 Flutter 错误处理 FlutterError.onError = (details) { AppLogger.e('Flutter Error', details.exception, details.stack); // 上报到 Sentry if (SentryService().isInitialized) { Sentry.captureException( details.exception, stackTrace: details.stack, ); } // 同时上报到自建遥测服务 if (TelemetryService().isInitialized) { TelemetryService().logError( 'Flutter error', error: details.exception, stackTrace: details.stack, extra: {'context': details.context?.toString()}, ); } }; // 捕获异步错误 PlatformDispatcher.instance.onError = (error, stack) { AppLogger.e('Platform Error', error, stack); // 上报到 Sentry if (SentryService().isInitialized) { Sentry.captureException(error, stackTrace: stack); } // 同时上报到自建遥测服务 if (TelemetryService().isInitialized) { TelemetryService().logError( 'Platform error', error: error, stackTrace: stack, ); } return true; }; runApp( UncontrolledProviderScope( container: container, child: await builder(), ), ); }, ); } /// 初始化遥测服务(需要 BuildContext,在首屏加载后调用) Future initializeTelemetry(BuildContext context, {String? userId}) async { if (TelemetryService().isInitialized) return; await TelemetryService().initialize( apiBaseUrl: _apiBaseUrl, context: context, userId: userId, configSyncInterval: const Duration(hours: 1), ); // 同步设置 Sentry 用户信息 if (userId != null && SentryService().isInitialized) { SentryService().setUser(userId: userId); } // 设置设备上下文到 Sentry if (SentryService().isInitialized) { final deviceContext = TelemetryService().deviceContext; if (deviceContext != null) { SentryService().setDeviceContext( deviceId: TelemetryService().installId, deviceModel: '${deviceContext.brand} ${deviceContext.model}', osVersion: deviceContext.osVersion, appVersion: deviceContext.appVersion, ); } } } /// 检查应用更新 Future checkForAppUpdate(BuildContext context) async { if (!UpdateService().isInitialized) return; // 延迟几秒后检查更新,避免干扰用户 await Future.delayed(const Duration(seconds: 3)); if (!context.mounted) return; await UpdateService().checkForUpdate(context); }