| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- 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<PageRoute> routeObserver = RouteObserver<PageRoute>();
- 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<Data>(lazy: false, create: (context) => Data(persistence: Persistence())..loadDataFromPersistence()),
- Provider<SettingsController>(lazy: false, create: (context) => SettingsController(persistence: Persistence())..loadStateFromPersistence()),
- // ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, 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<SettingsController, ValueNotifier<AppLifecycleState>, 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<ApplovinAdsController>(create: (context) => ApplovinAdsController(context)),
- Provider<cfg.Config>(lazy: false, create: (context) => config),
- Provider<Device>(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<Locale> 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<Prepare> createState() => _PrepareState();
- }
- class _PrepareState extends State<Prepare> {
- @override
- void initState() {
- super.initState();
- loadDeviceInfo();
- }
- /// 获取android平台信息,用户判断是否低端机
- loadDeviceInfo() async {
- DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
- if (Platform.isAndroid) {
- try {
- context.read<Device>().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<Device>().bannerHeight = value.height.toDouble();
- // }
- // }).catchError((err) {
- // //todo
- // });
- return widget.child;
- }
- }
|