Showing
8 changed files
with
74 additions
and
33 deletions
| @@ -335,7 +335,7 @@ class TodayController extends GetMaterialController { | @@ -335,7 +335,7 @@ class TodayController extends GetMaterialController { | ||
| 335 | 335 | ||
| 336 | Future<void> onAuthorizeTap() async { | 336 | Future<void> onAuthorizeTap() async { |
| 337 | ta.track('click_doublefeel_today_status', properties: {'ita': '授权访问健康数据'}); | 337 | ta.track('click_doublefeel_today_status', properties: {'ita': '授权访问健康数据'}); |
| 338 | - if (showHealthDataAuthCardStatus.value == 1) { | 338 | + if (showHealthDataAuthCardStatus.value == 1 && hrvChartData.isNotEmpty) { |
| 339 | return; | 339 | return; |
| 340 | } else if (showHealthDataAuthCardStatus.value == 0) { | 340 | } else if (showHealthDataAuthCardStatus.value == 0) { |
| 341 | await requestHealthAuthorization(isFromNoPermissionPage: true); | 341 | await requestHealthAuthorization(isFromNoPermissionPage: true); |
| @@ -344,6 +344,9 @@ class TodayController extends GetMaterialController { | @@ -344,6 +344,9 @@ class TodayController extends GetMaterialController { | ||
| 344 | await Get.to(NoHealthDataPage( | 344 | await Get.to(NoHealthDataPage( |
| 345 | onRefresh: () { | 345 | onRefresh: () { |
| 346 | refreshTab(); | 346 | refreshTab(); |
| 347 | + Future.delayed(const Duration(milliseconds: 800), () { | ||
| 348 | + AppToast.show(l10n.refreshComplete); | ||
| 349 | + }); | ||
| 347 | }, | 350 | }, |
| 348 | onHelp: () { | 351 | onHelp: () { |
| 349 | Get.toNamed(Routes.HELP); | 352 | Get.toNamed(Routes.HELP); |
| @@ -61,24 +61,27 @@ class LoginController extends GetxController { | @@ -61,24 +61,27 @@ class LoginController extends GetxController { | ||
| 61 | 61 | ||
| 62 | final newValue = !termsChecked.value; | 62 | final newValue = !termsChecked.value; |
| 63 | termsChecked.value = newValue; | 63 | termsChecked.value = newValue; |
| 64 | + | ||
| 65 | + // ① 写盘不 await,避免阻塞(SharedPrefs 写失败影响极小) | ||
| 66 | + unawaited(Get.find<LocalStorage>().setTermsAgreed(newValue).catchError( | ||
| 67 | + (e) => AppLogger.e('setTermsAgreed error: $e'), | ||
| 68 | + )); | ||
| 69 | + | ||
| 70 | + if (newValue && !_userStateService.sdksInitialized) { | ||
| 71 | + // ② SDK 初始化完全异步,不阻塞任何点击 | ||
| 72 | + unawaited(_initSdksAndTrack()); | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + Future<void> _initSdksAndTrack() async { | ||
| 64 | try { | 77 | try { |
| 65 | - final local = Get.find<LocalStorage>(); | ||
| 66 | - await local.setTermsAgreed(newValue); | ||
| 67 | - if (newValue) { | ||
| 68 | - unawaited(() async { | ||
| 69 | - // 等待 Flutter 完成当前帧渲染,让勾选图标先显示 | ||
| 70 | - await WidgetsBinding.instance.endOfFrame; | ||
| 71 | - if (!_userStateService.sdksInitialized) { | ||
| 72 | - await _userStateService.initializeSdks(); | ||
| 73 | - } | ||
| 74 | - if (needTrackWelcomepage) { | ||
| 75 | - ta.track('enter_doublefeel_welcome_page'); | ||
| 76 | - needTrackWelcomepage = false; | ||
| 77 | - } | ||
| 78 | - }()); | 78 | + await _userStateService.initializeSdks(); |
| 79 | + if (needTrackWelcomepage) { | ||
| 80 | + ta.track('enter_doublefeel_welcome_page'); | ||
| 81 | + needTrackWelcomepage = false; | ||
| 79 | } | 82 | } |
| 80 | } catch (e) { | 83 | } catch (e) { |
| 81 | - AppLogger.e(e.toString()); | 84 | + AppLogger.e('initializeSdks error: $e'); |
| 82 | } | 85 | } |
| 83 | } | 86 | } |
| 84 | 87 | ||
| @@ -99,7 +102,7 @@ class LoginController extends GetxController { | @@ -99,7 +102,7 @@ class LoginController extends GetxController { | ||
| 99 | try { | 102 | try { |
| 100 | final result = await LoadingService.instance.run(() async { | 103 | final result = await LoadingService.instance.run(() async { |
| 101 | return await PlatformHostApi().requestAppleSignIn(); | 104 | return await PlatformHostApi().requestAppleSignIn(); |
| 102 | - }); | 105 | + }, type: LoadingType.circular); |
| 103 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 | 106 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 |
| 104 | if (result == null || | 107 | if (result == null || |
| 105 | result.identityToken?.isNotEmpty != true || | 108 | result.identityToken?.isNotEmpty != true || |
| @@ -2,6 +2,11 @@ import 'package:flutter/material.dart'; | @@ -2,6 +2,11 @@ import 'package:flutter/material.dart'; | ||
| 2 | import 'package:get/get.dart'; | 2 | import 'package:get/get.dart'; |
| 3 | import 'package:lottie/lottie.dart'; | 3 | import 'package:lottie/lottie.dart'; |
| 4 | 4 | ||
| 5 | +/// Loading 动画类型 | ||
| 6 | +/// - [lottie] 默认,使用 Lottie 动画(现有行为,保持不变) | ||
| 7 | +/// - [circular] 使用系统 CircularProgressIndicator | ||
| 8 | +enum LoadingType { lottie, circular } | ||
| 9 | + | ||
| 5 | class LoadingService { | 10 | class LoadingService { |
| 6 | OverlayEntry? _entry; | 11 | OverlayEntry? _entry; |
| 7 | int _counter = 0; | 12 | int _counter = 0; |
| @@ -13,8 +18,9 @@ class LoadingService { | @@ -13,8 +18,9 @@ class LoadingService { | ||
| 13 | static LoadingService get instance => Get.find(); | 18 | static LoadingService get instance => Get.find(); |
| 14 | 19 | ||
| 15 | /// 包装一个异步操作,执行期间显示 Loading,完成后自动隐藏。 | 20 | /// 包装一个异步操作,执行期间显示 Loading,完成后自动隐藏。 |
| 16 | - Future<T> run<T>(Future<T> Function() block) async { | ||
| 17 | - show(); | 21 | + Future<T> run<T>(Future<T> Function() block, |
| 22 | + {LoadingType type = LoadingType.lottie}) async { | ||
| 23 | + show(type: type); | ||
| 18 | try { | 24 | try { |
| 19 | return await block(); | 25 | return await block(); |
| 20 | } finally { | 26 | } finally { |
| @@ -22,7 +28,7 @@ class LoadingService { | @@ -22,7 +28,7 @@ class LoadingService { | ||
| 22 | } | 28 | } |
| 23 | } | 29 | } |
| 24 | 30 | ||
| 25 | - void show() { | 31 | + void show({LoadingType type = LoadingType.lottie}) { |
| 26 | _counter++; | 32 | _counter++; |
| 27 | if (_counter > 1) return; | 33 | if (_counter > 1) return; |
| 28 | 34 | ||
| @@ -35,7 +41,9 @@ class LoadingService { | @@ -35,7 +41,9 @@ class LoadingService { | ||
| 35 | 41 | ||
| 36 | // 防御:清理可能残留的旧 entry | 42 | // 防御:清理可能残留的旧 entry |
| 37 | _entry?.remove(); | 43 | _entry?.remove(); |
| 38 | - _entry = OverlayEntry(builder: (_) => const _GlobalLoadingWidget()); | 44 | + _entry = OverlayEntry( |
| 45 | + builder: (_) => _GlobalLoadingWidget(type: type), | ||
| 46 | + ); | ||
| 39 | overlay.insert(_entry!); | 47 | overlay.insert(_entry!); |
| 40 | } | 48 | } |
| 41 | 49 | ||
| @@ -50,7 +58,9 @@ class LoadingService { | @@ -50,7 +58,9 @@ class LoadingService { | ||
| 50 | } | 58 | } |
| 51 | 59 | ||
| 52 | class _GlobalLoadingWidget extends StatelessWidget { | 60 | class _GlobalLoadingWidget extends StatelessWidget { |
| 53 | - const _GlobalLoadingWidget(); | 61 | + const _GlobalLoadingWidget({this.type = LoadingType.lottie}); |
| 62 | + | ||
| 63 | + final LoadingType type; | ||
| 54 | 64 | ||
| 55 | @override | 65 | @override |
| 56 | Widget build(BuildContext context) { | 66 | Widget build(BuildContext context) { |
| @@ -63,16 +73,27 @@ class _GlobalLoadingWidget extends StatelessWidget { | @@ -63,16 +73,27 @@ class _GlobalLoadingWidget extends StatelessWidget { | ||
| 63 | color: Colors.black.withValues(alpha: 0.3), | 73 | color: Colors.black.withValues(alpha: 0.3), |
| 64 | child: Align( | 74 | child: Align( |
| 65 | alignment: const Alignment(0.0, -0.1), | 75 | alignment: const Alignment(0.0, -0.1), |
| 66 | - child: Lottie.asset( | ||
| 67 | - 'assets/lottie/loading.json', | ||
| 68 | - width: 130, | ||
| 69 | - height: 90, | ||
| 70 | - repeat: true, | ||
| 71 | - animate: true, | ||
| 72 | - ), | 76 | + child: _buildIndicator(), |
| 73 | ), | 77 | ), |
| 74 | ), | 78 | ), |
| 75 | ), | 79 | ), |
| 76 | ); | 80 | ); |
| 77 | } | 81 | } |
| 82 | + | ||
| 83 | + Widget _buildIndicator() { | ||
| 84 | + switch (type) { | ||
| 85 | + case LoadingType.lottie: | ||
| 86 | + return Lottie.asset( | ||
| 87 | + 'assets/lottie/loading.json', | ||
| 88 | + width: 130, | ||
| 89 | + height: 90, | ||
| 90 | + repeat: true, | ||
| 91 | + animate: true, | ||
| 92 | + ); | ||
| 93 | + case LoadingType.circular: | ||
| 94 | + return const CircularProgressIndicator( | ||
| 95 | + strokeWidth: 3, | ||
| 96 | + ); | ||
| 97 | + } | ||
| 98 | + } | ||
| 78 | } | 99 | } |
| @@ -771,5 +771,6 @@ | @@ -771,5 +771,6 @@ | ||
| 771 | "healthLocalNotificationRealtimeStressAttentionContent": "Your stress was elevated over the past 60 minutes. Consider relaxing and making time for rest and recovery.", | 771 | "healthLocalNotificationRealtimeStressAttentionContent": "Your stress was elevated over the past 60 minutes. Consider relaxing and making time for rest and recovery.", |
| 772 | "healthLocalNotificationRealtimeStressOverloadContent": "You stayed in a high-stress state over the past 60 minutes. Reduce exertion and prioritize rest and sleep.", | 772 | "healthLocalNotificationRealtimeStressOverloadContent": "You stayed in a high-stress state over the past 60 minutes. Reduce exertion and prioritize rest and sleep.", |
| 773 | "turnOnNotifications": "Turn on Notifications", | 773 | "turnOnNotifications": "Turn on Notifications", |
| 774 | - "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "Stay up to date on changes in your own and your friends' health" | ||
| 775 | -} | 774 | + "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "Stay up to date on changes in your own and your friends' health", |
| 775 | + "refreshComplete": "Refresh Complete" | ||
| 776 | +} |
| @@ -1164,5 +1164,6 @@ | @@ -1164,5 +1164,6 @@ | ||
| 1164 | "healthLocalNotificationRealtimeStressAttentionContent": "你过去60分钟压力偏高,建议适当放松,并注意休息与恢复。", | 1164 | "healthLocalNotificationRealtimeStressAttentionContent": "你过去60分钟压力偏高,建议适当放松,并注意休息与恢复。", |
| 1165 | "healthLocalNotificationRealtimeStressOverloadContent": "你过去60分钟持续处于高压力状态,建议减少消耗,并优先保证休息与睡眠。", | 1165 | "healthLocalNotificationRealtimeStressOverloadContent": "你过去60分钟持续处于高压力状态,建议减少消耗,并优先保证休息与睡眠。", |
| 1166 | "turnOnNotifications": "开启通知", | 1166 | "turnOnNotifications": "开启通知", |
| 1167 | - "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "及时了解自己和好友的健康波动" | ||
| 1168 | -} | 1167 | + "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "及时了解自己和好友的健康波动", |
| 1168 | + "refreshComplete": "刷新完成" | ||
| 1169 | +} |
| @@ -4149,6 +4149,12 @@ abstract class AppLocalizations { | @@ -4149,6 +4149,12 @@ abstract class AppLocalizations { | ||
| 4149 | /// In zh, this message translates to: | 4149 | /// In zh, this message translates to: |
| 4150 | /// **'及时了解自己和好友的健康波动'** | 4150 | /// **'及时了解自己和好友的健康波动'** |
| 4151 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth; | 4151 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth; |
| 4152 | + | ||
| 4153 | + /// No description provided for @refreshComplete. | ||
| 4154 | + /// | ||
| 4155 | + /// In zh, this message translates to: | ||
| 4156 | + /// **'刷新完成'** | ||
| 4157 | + String get refreshComplete; | ||
| 4152 | } | 4158 | } |
| 4153 | 4159 | ||
| 4154 | class _AppLocalizationsDelegate | 4160 | class _AppLocalizationsDelegate |
| @@ -2336,4 +2336,7 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -2336,4 +2336,7 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 2336 | @override | 2336 | @override |
| 2337 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth => | 2337 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth => |
| 2338 | 'Stay up to date on changes in your own and your friends\' health'; | 2338 | 'Stay up to date on changes in your own and your friends\' health'; |
| 2339 | + | ||
| 2340 | + @override | ||
| 2341 | + String get refreshComplete => 'Refresh Complete'; | ||
| 2339 | } | 2342 | } |
| @@ -2232,4 +2232,7 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -2232,4 +2232,7 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 2232 | @override | 2232 | @override |
| 2233 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth => | 2233 | String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth => |
| 2234 | '及时了解自己和好友的健康波动'; | 2234 | '及时了解自己和好友的健康波动'; |
| 2235 | + | ||
| 2236 | + @override | ||
| 2237 | + String get refreshComplete => '刷新完成'; | ||
| 2235 | } | 2238 | } |
-
Please register or login to post a comment