home_screen.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'dart:math';
  4. import 'package:advertising_id/advertising_id.dart';
  5. import 'package:app_tracking_transparency/app_tracking_transparency.dart';
  6. import 'package:firebase_crashlytics/firebase_crashlytics.dart';
  7. import 'package:firebase_messaging/firebase_messaging.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:flutter/services.dart';
  11. import 'package:flutter_svg/svg.dart';
  12. import 'package:fluttertoast/fluttertoast.dart';
  13. import 'package:logging/logging.dart';
  14. import 'package:lottie/lottie.dart';
  15. import 'package:provider/provider.dart';
  16. import 'package:puzzleweave/ads/applovin_ads_controller.dart';
  17. import 'package:puzzleweave/audio/jc_audio_controller.dart';
  18. import 'package:puzzleweave/collection/collection_screen.dart';
  19. import 'package:puzzleweave/config/device.dart';
  20. import 'package:puzzleweave/homepage/home_board_play.dart';
  21. import 'package:puzzleweave/l10n/app_localizations.dart';
  22. import 'package:puzzleweave/models/cached_request.dart';
  23. import 'package:puzzleweave/models/data.dart';
  24. import 'package:puzzleweave/models/download.dart';
  25. import 'package:puzzleweave/models/items.dart';
  26. import 'package:puzzleweave/platform/my_method_channel.dart';
  27. import 'package:puzzleweave/play/board_play.dart';
  28. import 'package:puzzleweave/settings/settings_screen.dart';
  29. import 'package:puzzleweave/skin/skin.dart';
  30. import 'package:puzzleweave/utils/mybutton.dart';
  31. final Logger _log = Logger('home_screen');
  32. class HomeScreen extends StatefulWidget {
  33. const HomeScreen({super.key});
  34. @override
  35. State<StatefulWidget> createState() => _HomeScreen();
  36. }
  37. const int minimumRemoteLoadCount = 30; // 假设加载到 30 张图才算网络畅通
  38. class _HomeScreen extends State<HomeScreen> with TickerProviderStateMixin {
  39. late Device device;
  40. late JcAudioController audio;
  41. late Data data;
  42. List<ListItem>? latest;
  43. late CachedRequest latestCachedRequest;
  44. late StreamSubscription? latestSubscription;
  45. // 自定义画布控制器(可选,用于控制画布绘制逻辑)
  46. final _canvasKey = GlobalKey<HomeBoardPlayState>();
  47. // !!! 新增:用于定位 Collection 按钮的 GlobalKey
  48. final GlobalKey _collectionKey = GlobalKey();
  49. bool isLoading = true;
  50. // !!! 新增:Collection 按钮的动画控制器和动画
  51. late AnimationController _collectionController; // 左上角 collection button 的动画控制器
  52. late Animation<double> _collectionAnimation; // 放大/缩小动画
  53. @override
  54. void initState() {
  55. super.initState();
  56. _log.info("首页初始化");
  57. device = context.read<Device>();
  58. audio = context.read<JcAudioController>();
  59. data = context.read<Data>();
  60. latestCachedRequest = data.latest;
  61. // 主动获取缓存数据(关键)
  62. final cachedData = latestCachedRequest.cachedData;
  63. if (cachedData != null) {
  64. _onLatestDataUpdate(cachedData);
  65. }
  66. latestSubscription = latestCachedRequest.stream.listen(_onLatestDataUpdate, onError: _onLatestDataError);
  67. // !!! 改造点 1: 初始化 Collection 按钮动画
  68. _collectionController =
  69. AnimationController(
  70. // 设定总时长
  71. duration: const Duration(milliseconds: 300),
  72. vsync: this,
  73. )..addStatusListener((status) {
  74. if (status == AnimationStatus.completed) {
  75. audio.playSfx(SfxType.pop);
  76. }
  77. });
  78. // !!! 改造点 2: 使用 TweenSequence 实现平滑的放大和缩小
  79. _collectionAnimation = TweenSequence<double>([
  80. // 阶段 1: 放大到 1.3 (占总时长的 50%)
  81. TweenSequenceItem(tween: Tween<double>(begin: 1.0, end: 1.4).chain(CurveTween(curve: Curves.easeOut)), weight: 40.0),
  82. // 阶段 2: 缩小回 1.0 (占总时长的 50%)
  83. TweenSequenceItem(tween: Tween<double>(begin: 1.4, end: 1.0).chain(CurveTween(curve: Curves.easeIn)), weight: 60.0),
  84. ]).animate(_collectionController);
  85. audio.startMusic();
  86. }
  87. @override
  88. void dispose() {
  89. latestSubscription?.cancel();
  90. _collectionController.dispose();
  91. super.dispose();
  92. }
  93. _onLatestDataUpdate(data) {
  94. _log.info('_onLatestDataUpdate.... ');
  95. if (data != null) {
  96. latest = data as List<ListItem>;
  97. isLoading = false;
  98. setState(() {});
  99. // 1. 检查数据量是否达到最低要求 (>= 30)
  100. final bool hasSufficientData = data.length >= minimumRemoteLoadCount;
  101. // 2. 检查数据是否来自最近一次成功的网络请求
  102. final bool isNetworkActive = latestCachedRequest.hasRecentSuccessfulFetch; // !!! 关键检查点
  103. if (hasSufficientData) {
  104. // 如果数据完整,无论是否是缓存数据,都尝试初始化第三方服务(因为主页已经可以显示了)
  105. if (!hasInit) {
  106. initThird();
  107. }
  108. // !!! 核心修改:只有在数据完整且最近网络请求成功时,才启动预加载
  109. if (isNetworkActive) {
  110. _log.info('Data sufficient AND Network Active. Starting preload.');
  111. Future.delayed(const Duration(seconds: 3), () => _preloadNextImages());
  112. } else {
  113. // 数据完整,但来自缓存,网络状态未知,3秒后尝试刷新(refresh)
  114. _log.info('Data sufficient BUT Network status unknown/inactive. Attempting refresh in 3s.');
  115. Future.delayed(Duration(seconds: 3), () => refresh());
  116. }
  117. } else {
  118. // 数据不足 (例如,只有内置图),无论是缓存还是远程失败,都需要重试
  119. _log.info('Data insufficient (only ${data.length} items). Attempting refresh in 3s.');
  120. Future.delayed(Duration(seconds: 3), () => refresh());
  121. }
  122. }
  123. }
  124. _onLatestDataError(error) {
  125. _log.info('_onLatestDataError.... $error');
  126. if (latest == null || latest!.isEmpty || latest!.length < 20) {
  127. // 列表数据如果少于20,说明只是内置图,仍然刷新远程请求
  128. _log.warning("_onLatestDataError, retry again");
  129. // refresh();
  130. Future.delayed(Duration(seconds: 3), () => refresh());
  131. }
  132. }
  133. Future<void> refresh() async {
  134. _log.info('refresh...');
  135. await latestCachedRequest.refresh();
  136. }
  137. // ListItem? get currentItem {
  138. // if (latest != null && latest!.isNotEmpty && data.currentLevel < latest!.length) {
  139. // // return latest![data.currentLevel]; // 原来的逻辑,太过简单,如果后台图片有调整顺序变了,用户可能会遇到重复的图
  140. // // todo... 改成从latest列表中查找首个 data.completedWorks 中不存在的图(即首个未完成图)
  141. // }
  142. // return null;
  143. // }
  144. ListItem? get currentItem {
  145. // 1. 确保 latest 数据已加载
  146. if (latest == null || latest!.isEmpty) {
  147. return null;
  148. }
  149. // 2. 获取已完成作品的唯一标识符集合,方便快速查找
  150. // 假设 ListItem 的 id/url/name 等属性是其唯一标识。
  151. // 我们使用 id 作为唯一标识符。
  152. final Set<String> completedIds = data.completedWorks.value.map((work) => work.id).toSet();
  153. // 3. 遍历 latest 列表,查找第一个未完成的 Item
  154. for (final item in latest!) {
  155. // 假设 ListItem 有一个唯一的 id 属性。
  156. // 如果 ListItem 没有 id,您需要使用其 URL 或其他唯一标识。
  157. // 这里我们假设 ListItem 是 RemoteItem/AssetItem 的基类,它们有一个 String 类型的 id 属性。
  158. final String itemId = item.id;
  159. // 检查这个 id 是否在已完成集合中
  160. if (!completedIds.contains(itemId)) {
  161. _log.info('Found current item: $itemId');
  162. return item; // 返回找到的第一个未完成的 Item
  163. }
  164. }
  165. // 4. 如果所有图片都完成了
  166. _log.info('All items in the latest list have been completed.');
  167. return null;
  168. }
  169. /// 预加载未来 N 张图片到磁盘,并最后触发当前关卡下载以最大化内存缓存命中率。
  170. void _preloadNextImages() {
  171. // 预加载数量 (包括当前关卡在内,共 20 个)
  172. const int totalPreloadCount = 20;
  173. // 1. 确保 latest 数据已加载
  174. if (latest == null || latest!.isEmpty || latest!.length < minimumRemoteLoadCount) {
  175. _log.info('Preload failed: latest list is empty.');
  176. return;
  177. }
  178. // 2. 查找当前未完成的第一张图片的索引 (Index of currentItem)
  179. final Set<String> completedIds = data.completedWorks.value.map((work) => work.id).toSet();
  180. int startIndex = -1;
  181. for (int i = 0; i < latest!.length; i++) {
  182. if (!completedIds.contains(latest![i].id)) {
  183. startIndex = i;
  184. break;
  185. }
  186. }
  187. if (startIndex == -1) {
  188. _log.info('Preload: All images completed, nothing to preload.');
  189. return;
  190. }
  191. // 确定预加载范围 (从当前图片startIndex到 totalPreloadCount 个图片)
  192. final int endPreloadIndex = min(startIndex + totalPreloadCount, latest!.length);
  193. // 3. 准备要加载的列表 (从 startIndex 开始)
  194. final List<ListItem> itemsToLoad = latest!.sublist(startIndex, endPreloadIndex);
  195. if (itemsToLoad.isEmpty) {
  196. _log.info('Preload: No items found in the range.');
  197. return;
  198. }
  199. // 4. 将当前关卡 (第一个元素) 移动到列表的末尾
  200. final ListItem currentItemToLoad = itemsToLoad.removeAt(0);
  201. itemsToLoad.add(currentItemToLoad);
  202. _log.info('Preloading ${itemsToLoad.length} images. Current item: ${currentItemToLoad.id} will be loaded last.');
  203. // 5. 循环触发 ItemLoader 加载
  204. int preloadCount = 0;
  205. for (final itemToLoad in itemsToLoad) {
  206. // 对远程图片进行预加载
  207. // 调用 ItemLoader.load,它会使用 Download 单例进行下载和缓存
  208. // 我们不关心返回值或 Future,只是触发下载
  209. if (itemToLoad is RemoteItem) {
  210. try {
  211. // 触发下载。对于非当前关卡,下载器会完成下载并写入磁盘,然后可能释放内存。
  212. // 对于当前关卡 (最后一个被调用的),它留在内存中的可能性最大。
  213. ItemLoader.load(itemToLoad);
  214. preloadCount++;
  215. } catch (e) {
  216. _log.warning('Failed to load item for preloading: ${itemToLoad.id}, error: $e');
  217. }
  218. }
  219. }
  220. _log.info('Preload initiated for $preloadCount remote images, current item was last.');
  221. }
  222. @override
  223. Widget build(BuildContext context) {
  224. if (isLoading) return scrollableDummy;
  225. // 2. 计算画布尺寸(宽=屏幕宽-60,高=宽×3/2)
  226. // final canvasWidth = device.screenSize.width - 30 * 2; // 左右各30px
  227. // final canvasHeight = canvasWidth * 3 / 2;
  228. final double availableHeight = device.screenSize.height - device.appBarHeight - device.bannerHeight - 120;
  229. final double paddedWidth = device.screenSize.width - 2 * 30; // padding width 30
  230. final double paddedHeight = availableHeight;
  231. final double targetWidth = paddedWidth;
  232. final double targetHeight = targetWidth * device.aspectRatio;
  233. final double canvasWidth;
  234. final double canvasHeight;
  235. if (targetHeight > paddedHeight) {
  236. canvasHeight = paddedHeight;
  237. canvasWidth = paddedHeight / device.aspectRatio;
  238. } else {
  239. canvasWidth = targetWidth;
  240. canvasHeight = targetHeight;
  241. }
  242. return Scaffold(
  243. appBar: AppBar(
  244. backgroundColor: Colors.white,
  245. elevation: 1,
  246. centerTitle: true,
  247. leading: RepaintBoundary(
  248. // !!! 改造点 3: 添加 ScaleTransition
  249. key: _collectionKey, // 关联 GlobalKey
  250. child: ScaleTransition(
  251. scale: _collectionAnimation, // 使用定义的放大/缩小动画
  252. child: IconButton(
  253. onPressed: () {
  254. audio.playSfx(SfxType.click);
  255. Navigator.push(context, CollectionScreen.buildRoute());
  256. },
  257. icon: const Icon(Icons.collections, color: Colors.black87),
  258. ),
  259. ),
  260. ),
  261. // title: const Text(
  262. // 'Jigsort Solitaire',
  263. // style: TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 24),
  264. // ),
  265. // 🚀 改造点:将 Text 标题替换为 SvgPicture
  266. title: SvgPicture.asset(
  267. 'assets/images/title.svg', // 替换为您的 SVG 文件路径
  268. height: 32, // 根据您的设计调整高度,确保它在 AppBar 中显示良好
  269. // colorFilter: const ColorFilter.mode(Colors.black87, BlendMode.srcIn), // 如果SVG是单色,可以设置颜色
  270. placeholderBuilder: (BuildContext context) => const Text(
  271. // 占位符,以防SVG加载失败
  272. 'Jigsort Solitaire',
  273. style: TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 24),
  274. ),
  275. ),
  276. actions: [
  277. IconButton(
  278. onPressed: () {
  279. audio.playSfx(SfxType.click);
  280. // Navigator.push(context, SettingsDialog.buildRoute());
  281. Navigator.push(context, SettingScreen.buildRoute());
  282. },
  283. icon: const Icon(Icons.settings, color: Colors.black87),
  284. ),
  285. ],
  286. ),
  287. body: Column(
  288. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  289. children: [
  290. Expanded(
  291. child: Column(
  292. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  293. children: [
  294. // 2. 画布区域(固定尺寸)
  295. Padding(
  296. padding: const EdgeInsets.symmetric(horizontal: 30), // 左右30px
  297. child: SizedBox(
  298. width: canvasWidth,
  299. height: canvasHeight,
  300. child: ValueListenableBuilder(
  301. valueListenable: data.completedWorks,
  302. builder: (context, value, child) {
  303. return HomeBoardPlay(
  304. key: _canvasKey,
  305. canvasWidth: canvasWidth,
  306. canvasHeight: canvasHeight,
  307. collectionKey: _collectionKey,
  308. onCollectionDone: () {
  309. // collection unlocking 动画结束,启动collection button 的接收反馈动画
  310. _log.info('onCollectionDone, 启动合集收纳反馈动画');
  311. audio.playSfx(SfxType.appear);
  312. _collectionController.forward(from: 0.0);
  313. },
  314. );
  315. },
  316. ),
  317. ),
  318. ),
  319. playButton,
  320. ],
  321. ),
  322. ),
  323. Container(),
  324. // SafeArea(
  325. // child: SizedBox(
  326. // // 始终预留一个固定的高度,防止布局跳变
  327. // height: context.read<Device>().bannerHeight,
  328. // width: double.infinity,
  329. // child: FutureBuilder<bool>(
  330. // future: _bannerReadyAndShouldShow(),
  331. // builder: (context, snapshot) {
  332. // if (snapshot.hasData && snapshot.data == true) {
  333. // return adBanner;
  334. // }
  335. // return Container(
  336. // // color: Colors.grey.shade100,
  337. // );
  338. // },
  339. // ),
  340. // ),
  341. // ),
  342. ],
  343. ),
  344. );
  345. }
  346. Widget get playButton {
  347. return MyElevatedButton(
  348. width: device.isTablet ? 300 : 200,
  349. height: 70,
  350. borderRadius: BorderRadius.circular(20),
  351. gradient: LinearGradient(colors: [SkinHelper.coreBgColor, SkinHelper.slotBorderColor]),
  352. onPressed: () async {
  353. audio.playSfx(SfxType.click);
  354. // _canvasKey.currentState?.startFlipAnimation(); // for test
  355. // _canvasKey.currentState?.testAnimation(); // for test;
  356. if (currentItem != null) {
  357. PageRouteBuilder? pageRouteBuilder = BoardPlay.buildRoute(currentItem!);
  358. final result = await Navigator.push(context, pageRouteBuilder);
  359. if (result == true) {
  360. _canvasKey.currentState?.startFlipAnimation();
  361. final bool hasSufficientData = latest != null && latest!.length >= minimumRemoteLoadCount;
  362. final bool isNetworkActive = latestCachedRequest.hasRecentSuccessfulFetch;
  363. if (hasSufficientData) {
  364. // 1. 数据完整:如果网络活跃,立即顺延预加载。
  365. if (isNetworkActive) {
  366. _log.info('Game finished, data complete & Network Active. Triggering sequential preloading...');
  367. _preloadNextImages();
  368. } else {
  369. // 2. 数据完整但网络不活跃/状态未知:尝试刷新,让 _onLatestDataUpdate 负责后续处理
  370. _log.info('Game finished, data complete but Network inactive. Attempting refresh.');
  371. refresh();
  372. }
  373. } else {
  374. // 3. 数据不完整:无论如何都需要刷新,让 _onLatestDataUpdate 重新处理
  375. _log.info('Game finished, remote data incomplete. Attempting refresh...');
  376. refresh();
  377. }
  378. }
  379. } else {
  380. Fluttertoast.showToast(
  381. msg: AppLocalizations.of(context)!.noMorePicture,
  382. toastLength: Toast.LENGTH_SHORT,
  383. gravity: ToastGravity.CENTER,
  384. timeInSecForIosWeb: 1,
  385. backgroundColor: SkinHelper.slotBorderColor,
  386. textColor: Colors.white,
  387. fontSize: 16.0,
  388. );
  389. }
  390. },
  391. child: Column(
  392. mainAxisAlignment: MainAxisAlignment.center,
  393. children: [
  394. Text(
  395. AppLocalizations.of(context)!.play,
  396. style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
  397. ),
  398. ValueListenableBuilder<List<Work>>(
  399. valueListenable: data.completedWorks,
  400. builder: (context, isSoundOn, child) {
  401. return Text('${AppLocalizations.of(context)!.level} ${data.currentLevel + 1}', style: const TextStyle(color: Colors.white, fontSize: 16));
  402. },
  403. ),
  404. ],
  405. ),
  406. );
  407. }
  408. Widget get scrollableDummy => Scaffold(
  409. body: LayoutBuilder(
  410. builder: (p0, p1) {
  411. return SingleChildScrollView(
  412. physics: const AlwaysScrollableScrollPhysics(),
  413. child: SizedBox(
  414. height: p1.maxHeight,
  415. child: Center(child: ListView(shrinkWrap: true, children: [Lottie.asset('assets/lottie/loading.json', height: 100)])),
  416. ),
  417. );
  418. },
  419. ),
  420. );
  421. ///////////////////////// 初始化相关 /////////////////////////
  422. static bool hasInit = false;
  423. static MyMethodChannel platform = MyMethodChannel();
  424. // 在列表刷出来后才正式初始化admod等组件
  425. void initThird() async {
  426. if (hasInit) return;
  427. hasInit = true;
  428. // 有了UMP后, 这里的ATT就不需要了
  429. // bool auth = await initATT();
  430. // if (auth) {
  431. // await platform.setHasUserConsent(true);
  432. // await platform.setAdvertiserTrackingEnabled(true);
  433. // }
  434. // await initUMP(); // 征询欧洲用户同意 // applovin max 已经可以自动处理,这里不需要了
  435. TrackingStatus attStatus = await AppTrackingTransparency.trackingAuthorizationStatus;
  436. if (attStatus == TrackingStatus.authorized && Platform.isIOS) {
  437. // ATT 通过之后,ios需要调用相关的原生sdk接口做进一步的初始化
  438. // await platform.setHasUserConsent(true);
  439. // await platform.setAdvertiserTrackingEnabled(true);
  440. }
  441. initFCM(); // 消息推送许可弹窗
  442. // initAd(); // admod 的广告加载安排在iOS ATT 之后,以便能够加载到个性化广告
  443. final idfa = await AppTrackingTransparency.getAdvertisingIdentifier();
  444. _log.info("idfa: $idfa");
  445. if (kDebugMode) {
  446. _printInfo();
  447. }
  448. }
  449. _printInfo() async {
  450. String? advertisingId;
  451. // Platform messages may fail, so we use a try/catch PlatformException.
  452. try {
  453. advertisingId = await AdvertisingId.id(true);
  454. } on PlatformException {
  455. advertisingId = null;
  456. }
  457. bool? isLimitAdTrackingEnabled;
  458. // Platform messages may fail, so we use a try/catch PlatformException.
  459. try {
  460. isLimitAdTrackingEnabled = await AdvertisingId.isLimitAdTrackingEnabled;
  461. } on PlatformException {
  462. isLimitAdTrackingEnabled = false;
  463. }
  464. _log.info('advertisingId: $advertisingId, isLimitAdTrackingEnabled: $isLimitAdTrackingEnabled');
  465. }
  466. /////////////////////////// ATT ///////////////////////////
  467. // Platform messages are asynchronous, so we initialize in an async method.
  468. Future<bool> initATT() async {
  469. TrackingStatus status = await AppTrackingTransparency.trackingAuthorizationStatus;
  470. _log.info('initATT111 $status');
  471. // If the system can show an authorization request dialog
  472. if (status == TrackingStatus.notDetermined) {
  473. // Show a custom explainer dialog before the system dialog
  474. // await showCustomTrackingDialog(context);
  475. // Wait for dialog popping animation
  476. // await Future.delayed(const Duration(milliseconds: 200));
  477. // Request system's tracking authorization dialog
  478. status = await AppTrackingTransparency.requestTrackingAuthorization();
  479. _log.info('initATT222 $status');
  480. }
  481. if (status == TrackingStatus.authorized) {
  482. return true;
  483. }
  484. return false;
  485. }
  486. // no need
  487. Future<void> showCustomTrackingDialog(BuildContext context) async => await showDialog<void>(
  488. context: context,
  489. builder: (context) => AlertDialog(
  490. title: const Text('Dear User'),
  491. content: const Text(
  492. 'We care about your privacy and data security. We keep this app free by showing ads. '
  493. 'Can we continue to use your data to tailor ads for you?\n\nYou can change your choice anytime in the app settings. '
  494. 'Our partners will collect data and use a unique identifier on your device to show you ads.',
  495. ),
  496. actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Continue'))],
  497. ),
  498. );
  499. /////////////////////////////////////////////////////////
  500. /// 初始化广告模块
  501. initAd() {
  502. _log.info('initAd');
  503. // AdsController adsController = context.read<AdsController>();
  504. // adsController.initialize();
  505. ApplovinAdsController applovinAdsController = context.read<ApplovinAdsController>();
  506. applovinAdsController.initialize();
  507. }
  508. /// gallery页面加载的时候,可能广告模块还没有初始化完毕
  509. // Future<bool> _bannerReadyAndShouldShow() async {
  510. // bool ready = await adSDKReady();
  511. // return ready && shouldShowBannerAd(data.currentLevel);
  512. // }
  513. /////////////////////////// FCM ///////////////////////////
  514. // 消息推送许可弹框
  515. initFCM() async {
  516. try {
  517. final fcmToken = await FirebaseMessaging.instance.getToken();
  518. _log.info("FCM Token: $fcmToken");
  519. FirebaseMessaging messaging = FirebaseMessaging.instance;
  520. NotificationSettings settings = await messaging.requestPermission(
  521. alert: true,
  522. announcement: false,
  523. badge: true,
  524. carPlay: false,
  525. criticalAlert: false,
  526. provisional: false,
  527. sound: true,
  528. );
  529. _log.warning('User granted permission: ${settings.authorizationStatus}');
  530. } catch (e) {
  531. FirebaseCrashlytics.instance.log("FCM FirebaseMessaging.instance.getToken error: $e");
  532. _log.warning(e);
  533. }
  534. }
  535. }