import 'dart:async'; import 'dart:developer' as dev; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:puzzleweave/ads/applovin_ads_controller.dart'; import 'package:puzzleweave/app_lifecycle/app_lifecycle.dart'; import 'package:puzzleweave/audio/jc_audio_controller.dart'; import 'package:puzzleweave/firebase/firebase_options.dart'; import 'package:puzzleweave/homepage/home_screen.dart'; import 'package:puzzleweave/l10n/app_localizations.dart'; import 'package:puzzleweave/models/data.dart'; import 'package:puzzleweave/models/items.dart'; import 'package:puzzleweave/persistence/persistence.dart'; import 'package:puzzleweave/play/board_play.dart'; import 'package:puzzleweave/remote_config/remote_config.dart'; import 'package:puzzleweave/settings/settings_controller.dart'; import 'package:puzzleweave/utils/utils.dart'; import 'config/config.dart' as cfg; import 'config/device.dart'; Logger _log = Logger('main.dart'); final RouteObserver routeObserver = RouteObserver(); void main() async { // Subscribe to log messages. Logger.root.onRecord.listen((record) { dev.log( record.message, time: record.time, level: record.level.value, name: record.loggerName, zone: record.zone, error: record.error, stackTrace: record.stackTrace, ); }); WidgetsFlutterBinding.ensureInitialized(); // 强制竖屏 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 进入全屏沉浸式, 隐藏底部导航以及状态栏 if (Platform.isAndroid) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, // <-- SEE HERE statusBarIconBrightness: Brightness.dark, //<-- For Android SEE HERE (dark icons) statusBarBrightness: Brightness.light, //<-- For iOS SEE HERE (dark icons) ), ); ////////////////////// firebase relate /////////////////////////////// if (!kIsWeb && (Platform.isAndroid)) { try { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); FlutterError.onError = (errorDetails) { FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails); }; // Pass all uncaught asynchronous errors // that aren't handled by the Flutter framework to Crashlytics. PlatformDispatcher.instance.onError = (error, stack) { final errorStr = error.toString(); // 1. 扩展网络/环境类异常的拦截范围 final bool isEnvironmentIssue = error is SocketException || error is HttpException || error is HandshakeException || errorStr.contains('ClientException') || errorStr.contains('SocketException') || errorStr.contains('HandshakeException') || errorStr.contains('Failed host lookup') || errorStr.contains('Network is unreachable') || errorStr.contains('Connection timed out') || errorStr.contains('Connection closed') || // 补充:上个日志提到的错误 errorStr.contains('Connection reset') || // 补充:被对方重置 errorStr.contains('Unable to connect'); if (isEnvironmentIssue) { _log.warning('已拦截环境异常: $errorStr'); FirebaseCrashlytics.instance.log('Env Error (Ignored): $errorStr'); return true; } // 2. 拦截插件缺失类异常 if (error is MissingPluginException) { _log.warning('插件未找到: $errorStr'); return true; } // 3. 剩下的才是逻辑错误(如 Null Check, Range Error) // 改为 fatal: false,避免降低 Google Play 的“崩溃率评分” _log.severe('未处理的逻辑错误', error, stack); FirebaseCrashlytics.instance.recordError(error, stack, fatal: false); return true; }; } catch (e) { debugPrint("Firebase couldn't be initialized: $e"); } } //本地参数存储初始化 await Persistence().initialize(); // 远程参数初始化 await RemoteConfig().initialize(); // 记录程序运行时间 Persistence().lastRunTime = DateTime.now(); Directory baseDir = await getApplicationDocumentsDirectory(); // 首次运行, 将json写入 if (Persistence().firstRun) { final json = await loadJSONFromAsset('assets/builtin/${cfg.Config.firstId}.json'); await saveJson('work/${cfg.Config.firstId}.json', json); } runApp(MyApp(baseDir: baseDir)); } class MyApp extends StatelessWidget { final Directory baseDir; const MyApp({super.key, required this.baseDir}); // This widget is the root of your application. @override Widget build(BuildContext context) { cfg.Config config = cfg.Config(context, baseDir); return AppLifecycleObserver( child: MultiProvider( providers: [ Provider(lazy: false, create: (context) => Data(persistence: Persistence())..loadDataFromPersistence()), Provider(lazy: false, create: (context) => SettingsController(persistence: Persistence())..loadStateFromPersistence()), // ProxyProvider2, AudioController>( // lazy: false, // create: (context) => AudioController()..initialize(), // update: (context, settings, lifecycleNotifier, audio) { // if (audio == null) throw ArgumentError.notNull(); // audio.attachSettings(settings); // audio.attachLifecycleNotifier(lifecycleNotifier); // return audio; // }, // dispose: (context, audio) => audio.dispose(), // ), ProxyProvider2, JcAudioController>( lazy: false, create: (context) => JcAudioController()..initialize(), update: (context, settings, lifecycleNotifier, audio) { if (audio == null) throw ArgumentError.notNull(); audio.attachSettings(settings); audio.attachLifecycleNotifier(lifecycleNotifier); return audio; }, dispose: (context, audio) => audio.dispose(), ), Provider(create: (context) => ApplovinAdsController(context)), Provider(lazy: false, create: (context) => config), Provider(lazy: false, create: (context) => config.device), ], child: Prepare( child: MaterialApp( key: GlobalKey(), title: 'Jigsort Solitaire', // initialRoute: firstRun ? '/play' : '/', // 首次游戏直接进入游戏页面,而不是合集页 initialRoute: '/', // 统一先到HomeScreen, 再根据情况跳转到相应页面 navigatorObservers: [routeObserver], routes: { '/': (context) => const HomeScreen(), // '/': (context) => const GalleryScreen(), '/play': (context) => BoardPlay( item: AssetItem( cfg.Config.firstId, '', 2000, 3000, 3, false, 'assets/builtin/${cfg.Config.firstId}.jpeg', 'assets/builtin/${cfg.Config.firstId}.jpeg', ), firstRun: true, ), }, theme: ThemeData( // textTheme: GoogleFonts.nunitoSansTextTheme(Theme.of(context).textTheme), brightness: Brightness.light, primaryColor: Colors.green, primarySwatch: Colors.blue, ), ///多语言设置 localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, /// 设置默认语言为英语 localeResolutionCallback: (Locale? locale, Iterable supportedLocales) { var result = supportedLocales.where((element) => element.languageCode == locale?.languageCode); if (result.isNotEmpty) { Device.locale = locale!; _log.info('当前语言(支持):${Device.locale!.countryCode}-${Device.locale!.languageCode}'); return locale; } Device.locale = const Locale('en'); _log.info('当前语言(默认):${Device.locale!.countryCode}-${Device.locale!.languageCode}'); return Device.locale; }, ), ), ), ); } } class Prepare extends StatefulWidget { final Widget child; const Prepare({super.key, required this.child}); @override State createState() => _PrepareState(); } class _PrepareState extends State { @override void initState() { super.initState(); loadDeviceInfo(); } /// 获取android平台信息,用户判断是否低端机 loadDeviceInfo() async { DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); if (Platform.isAndroid) { try { context.read().androidDeviceInfo = await deviceInfoPlugin.androidInfo; } catch (e) {} } } @override Widget build(BuildContext context) { // applovin max 没有类似的api可以获取banner高度,以下代码注释掉 //Update ad banner size // AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize( // MediaQuery.of(context).size.width.truncate(), // ).then((value) { // if (value != null) { // context.read().bannerHeight = value.height.toDouble(); // } // }).catchError((err) { // //todo // }); return widget.child; } }