main.dart 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import 'dart:developer' as dev;
  2. import 'dart:io';
  3. import 'package:device_info_plus/device_info_plus.dart';
  4. import 'package:firebase_core/firebase_core.dart';
  5. import 'package:firebase_crashlytics/firebase_crashlytics.dart';
  6. import 'package:flutter/foundation.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:logging/logging.dart';
  10. import 'package:path_provider/path_provider.dart';
  11. import 'package:provider/provider.dart';
  12. import 'package:puzzleweave/ads/applovin_ads_controller.dart';
  13. import 'package:puzzleweave/app_lifecycle/app_lifecycle.dart';
  14. import 'package:puzzleweave/audio/jc_audio_controller.dart';
  15. import 'package:puzzleweave/firebase/firebase_options.dart';
  16. import 'package:puzzleweave/homepage/home_screen.dart';
  17. import 'package:puzzleweave/l10n/app_localizations.dart';
  18. import 'package:puzzleweave/models/data.dart';
  19. import 'package:puzzleweave/models/items.dart';
  20. import 'package:puzzleweave/persistence/persistence.dart';
  21. import 'package:puzzleweave/play/board_play.dart';
  22. import 'package:puzzleweave/remote_config/remote_config.dart';
  23. import 'package:puzzleweave/settings/settings_controller.dart';
  24. import 'package:shared_preferences/shared_preferences.dart';
  25. import 'config/config.dart' as cfg;
  26. import 'config/device.dart';
  27. Logger _log = Logger('main.dart');
  28. final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
  29. void main() async {
  30. // Subscribe to log messages.
  31. Logger.root.onRecord.listen((record) {
  32. dev.log(
  33. record.message,
  34. time: record.time,
  35. level: record.level.value,
  36. name: record.loggerName,
  37. zone: record.zone,
  38. error: record.error,
  39. stackTrace: record.stackTrace,
  40. );
  41. });
  42. WidgetsFlutterBinding.ensureInitialized();
  43. // 强制竖屏
  44. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  45. // 进入全屏沉浸式, 隐藏底部导航以及状态栏
  46. if (Platform.isAndroid) {
  47. SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  48. }
  49. SystemChrome.setSystemUIOverlayStyle(
  50. const SystemUiOverlayStyle(
  51. statusBarColor: Colors.transparent, // <-- SEE HERE
  52. statusBarIconBrightness: Brightness.dark, //<-- For Android SEE HERE (dark icons)
  53. statusBarBrightness: Brightness.light, //<-- For iOS SEE HERE (dark icons)
  54. ),
  55. );
  56. ////////////////////// firebase relate ///////////////////////////////
  57. if (!kIsWeb && (Platform.isAndroid)) {
  58. try {
  59. await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  60. FlutterError.onError = (errorDetails) {
  61. FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
  62. };
  63. // Pass all uncaught asynchronous errors
  64. // that aren't handled by the Flutter framework to Crashlytics.
  65. PlatformDispatcher.instance.onError = (error, stack) {
  66. if (error.runtimeType == MissingPluginException) {
  67. _log.warning('error=[$error],stack=\n$stack');
  68. } else if (error.toString().contains('LoadAdError') ||
  69. error.toString().contains('Failed host lookup') ||
  70. error.toString().contains('Unable to connect to the server')) {
  71. _log.warning('network error: $error[${error.runtimeType}]');
  72. // FirebaseCrashlytics.instance.log(error.toString());
  73. } else {
  74. FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
  75. }
  76. return true;
  77. };
  78. } catch (e) {
  79. debugPrint("Firebase couldn't be initialized: $e");
  80. }
  81. }
  82. // 检查是否是首次进入,首次进入直接进入引导游戏界面
  83. bool firstRun = false;
  84. SharedPreferences prefs = await SharedPreferences.getInstance();
  85. int? timestamp = prefs.getInt('first_run_time');
  86. if (timestamp == null) {
  87. firstRun = true;
  88. }
  89. _log.info('firstRun = $firstRun');
  90. //本地参数存储初始化
  91. await Persistence().initialize();
  92. // 远程参数初始化
  93. await RemoteConfig().initialize();
  94. // 程序首次运行时间
  95. DateTime firstRunTime = Persistence().firstRunTime;
  96. _log.info("first_run_time: $firstRunTime, now: ${DateTime.now()}");
  97. // 记录程序运行时间
  98. Persistence().lastRunTime = DateTime.now();
  99. Directory baseDir = await getApplicationDocumentsDirectory();
  100. runApp(MyApp(baseDir: baseDir, firstRun: firstRun));
  101. }
  102. class MyApp extends StatelessWidget {
  103. final bool firstRun;
  104. final Directory baseDir;
  105. const MyApp({super.key, required this.baseDir, required this.firstRun});
  106. // This widget is the root of your application.
  107. @override
  108. Widget build(BuildContext context) {
  109. cfg.Config config = cfg.Config(context, baseDir);
  110. return AppLifecycleObserver(
  111. child: MultiProvider(
  112. providers: [
  113. Provider<Data>(lazy: false, create: (context) => Data(persistence: Persistence())..loadDataFromPersistence()),
  114. Provider<SettingsController>(lazy: false, create: (context) => SettingsController(persistence: Persistence())..loadStateFromPersistence()),
  115. // ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, AudioController>(
  116. // lazy: false,
  117. // create: (context) => AudioController()..initialize(),
  118. // update: (context, settings, lifecycleNotifier, audio) {
  119. // if (audio == null) throw ArgumentError.notNull();
  120. // audio.attachSettings(settings);
  121. // audio.attachLifecycleNotifier(lifecycleNotifier);
  122. // return audio;
  123. // },
  124. // dispose: (context, audio) => audio.dispose(),
  125. // ),
  126. ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, JcAudioController>(
  127. lazy: false,
  128. create: (context) => JcAudioController()..initialize(),
  129. update: (context, settings, lifecycleNotifier, audio) {
  130. if (audio == null) throw ArgumentError.notNull();
  131. audio.attachSettings(settings);
  132. audio.attachLifecycleNotifier(lifecycleNotifier);
  133. return audio;
  134. },
  135. dispose: (context, audio) => audio.dispose(),
  136. ),
  137. Provider<ApplovinAdsController>(create: (context) => ApplovinAdsController(context)),
  138. Provider<cfg.Config>(lazy: false, create: (context) => config),
  139. Provider<Device>(lazy: false, create: (context) => config.device),
  140. ],
  141. child: Prepare(
  142. child: MaterialApp(
  143. key: GlobalKey(),
  144. title: 'PuzzleWeave',
  145. initialRoute: firstRun ? '/play' : '/', // 首次游戏直接进入游戏页面,而不是合集页
  146. navigatorObservers: [routeObserver],
  147. routes: {
  148. '/': (context) => const HomeScreen(),
  149. '/play': (context) => BoardPlay(
  150. item: AssetItem(
  151. '6915869d4b99f02d1db82cf2',
  152. '',
  153. 2000,
  154. 3000,
  155. 3,
  156. false,
  157. 'assets/builtin/6915869d4b99f02d1db82cf2.jpeg',
  158. 'assets/builtin/6915869d4b99f02d1db82cf2.jpeg',
  159. ),
  160. firstRun: firstRun,
  161. ),
  162. },
  163. theme: ThemeData(
  164. // textTheme: GoogleFonts.nunitoSansTextTheme(Theme.of(context).textTheme),
  165. brightness: Brightness.light,
  166. primaryColor: Colors.green,
  167. primarySwatch: Colors.blue,
  168. ),
  169. ///多语言设置
  170. localizationsDelegates: AppLocalizations.localizationsDelegates,
  171. supportedLocales: AppLocalizations.supportedLocales,
  172. /// 设置默认语言为英语
  173. localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) {
  174. var result = supportedLocales.where((element) => element.languageCode == locale?.languageCode);
  175. if (result.isNotEmpty) {
  176. Device.locale = locale!;
  177. _log.info('当前语言(支持):${Device.locale!.countryCode}-${Device.locale!.languageCode}');
  178. return locale;
  179. }
  180. Device.locale = const Locale('en');
  181. _log.info('当前语言(默认):${Device.locale!.countryCode}-${Device.locale!.languageCode}');
  182. return Device.locale;
  183. },
  184. ),
  185. ),
  186. ),
  187. );
  188. }
  189. }
  190. class Prepare extends StatefulWidget {
  191. final Widget child;
  192. const Prepare({super.key, required this.child});
  193. @override
  194. State<Prepare> createState() => _PrepareState();
  195. }
  196. class _PrepareState extends State<Prepare> {
  197. @override
  198. void initState() {
  199. super.initState();
  200. loadDeviceInfo();
  201. }
  202. /// 获取android平台信息,用户判断是否低端机
  203. loadDeviceInfo() async {
  204. DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
  205. if (Platform.isAndroid) {
  206. try {
  207. context.read<Device>().androidDeviceInfo = await deviceInfoPlugin.androidInfo;
  208. } catch (e) {}
  209. }
  210. }
  211. @override
  212. Widget build(BuildContext context) {
  213. // applovin max 没有类似的api可以获取banner高度,以下代码注释掉
  214. //Update ad banner size
  215. // AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
  216. // MediaQuery.of(context).size.width.truncate(),
  217. // ).then((value) {
  218. // if (value != null) {
  219. // context.read<Device>().bannerHeight = value.height.toDouble();
  220. // }
  221. // }).catchError((err) {
  222. // //todo
  223. // });
  224. return widget.child;
  225. }
  226. }