Commit e74b78ecc6ba26b00fa673679f096047c226ec46

Authored by 常守达
1 parent 6e4ca7db

fix(today):自动更新今天,通知banner

@@ -104,8 +104,11 @@ class TodayController extends GetMaterialController { @@ -104,8 +104,11 @@ class TodayController extends GetMaterialController {
104 // ── HRV 表盘引导 Banner ─────────────────── 104 // ── HRV 表盘引导 Banner ───────────────────
105 final showHrvAdBanner = false.obs; 105 final showHrvAdBanner = false.obs;
106 final showPartnerAdBanner = false.obs; 106 final showPartnerAdBanner = false.obs;
  107 + final showNotificationAuthorizationBanner = false.obs;
107 108
108 void dismissHrvAdBanner() => showHrvAdBanner.value = false; 109 void dismissHrvAdBanner() => showHrvAdBanner.value = false;
  110 + void dismissNotificationAuthorizationBanner() =>
  111 + showNotificationAuthorizationBanner.value = false;
109 112
110 Future<void> dismissPartnerAdBanner() async { 113 Future<void> dismissPartnerAdBanner() async {
111 final userId = 114 final userId =
@@ -160,6 +163,7 @@ class TodayController extends GetMaterialController { @@ -160,6 +163,7 @@ class TodayController extends GetMaterialController {
160 checkAddFriendVisible(); 163 checkAddFriendVisible();
161 checkHrvAdBannerVisible(); 164 checkHrvAdBannerVisible();
162 checkHealthDataAuthCardVisible(); 165 checkHealthDataAuthCardVisible();
  166 + checkNotificationAuthorizationBannerVisible();
163 checkNotificationCardVisible(); 167 checkNotificationCardVisible();
164 if (!Get.find<UserStateService>().isVip) { 168 if (!Get.find<UserStateService>().isVip) {
165 getProductList(); 169 getProductList();
@@ -196,6 +200,24 @@ class TodayController extends GetMaterialController { @@ -196,6 +200,24 @@ class TodayController extends GetMaterialController {
196 } 200 }
197 } 201 }
198 202
  203 + Future<void> checkNotificationAuthorizationBannerVisible() async {
  204 + if (isFriend) {
  205 + showNotificationAuthorizationBanner.value = false;
  206 + } else {
  207 + try {
  208 + Permission.notification.status.then((status) {
  209 + if (status.isGranted) {
  210 + showNotificationAuthorizationBanner.value = false;
  211 + } else {
  212 + showNotificationAuthorizationBanner.value = true;
  213 + }
  214 + });
  215 + } on Exception catch (e) {
  216 + AppLogger.e(e);
  217 + }
  218 + }
  219 + }
  220 +
199 void checkAddFriendVisible() { 221 void checkAddFriendVisible() {
200 if (isFriend) { 222 if (isFriend) {
201 showPartnerAdBanner.value = false; 223 showPartnerAdBanner.value = false;
@@ -504,6 +526,7 @@ class TodayController extends GetMaterialController { @@ -504,6 +526,7 @@ class TodayController extends GetMaterialController {
504 hrvAnnotations.clear(); 526 hrvAnnotations.clear();
505 527
506 v2HealthData.value = null; 528 v2HealthData.value = null;
  529 + v2ActivityTarget.value = null;
507 // v2StressScore.value = null; 530 // v2StressScore.value = null;
508 } 531 }
509 532
@@ -697,12 +720,13 @@ class TodayController extends GetMaterialController { @@ -697,12 +720,13 @@ class TodayController extends GetMaterialController {
697 super.didChangeAppLifecycleState(state); 720 super.didChangeAppLifecycleState(state);
698 721
699 if (state == AppLifecycleState.resumed) { 722 if (state == AppLifecycleState.resumed) {
700 - // var today = DateUtils.dateOnly(DateTime.now());  
701 - // lastSelectableDay.value = today;  
702 - // changeDate(today); 723 + var today = DateUtils.dateOnly(DateTime.now());
  724 + lastSelectableDay.value = today;
  725 + changeDate(today);
703 checkAddFriendVisible(); 726 checkAddFriendVisible();
704 checkHrvAdBannerVisible(); 727 checkHrvAdBannerVisible();
705 checkHealthDataAuthCardVisible(); 728 checkHealthDataAuthCardVisible();
  729 + checkNotificationAuthorizationBannerVisible();
706 _performHealthDataUpload(); 730 _performHealthDataUpload();
707 // _startCoreCaculate(); 731 // _startCoreCaculate();
708 // unawaited(loadDataForDate(selectedDate.value)); 732 // unawaited(loadDataForDate(selectedDate.value));
@@ -720,7 +744,33 @@ class TodayController extends GetMaterialController { @@ -720,7 +744,33 @@ class TodayController extends GetMaterialController {
720 checkAddFriendVisible(); 744 checkAddFriendVisible();
721 checkHrvAdBannerVisible(); 745 checkHrvAdBannerVisible();
722 checkHealthDataAuthCardVisible(isPullToRefresh: isPullToRefresh); 746 checkHealthDataAuthCardVisible(isPullToRefresh: isPullToRefresh);
  747 + checkNotificationAuthorizationBannerVisible();
723 _performHealthDataUpload(); 748 _performHealthDataUpload();
724 // await loadDataForDate(selectedDate.value); 749 // await loadDataForDate(selectedDate.value);
725 } 750 }
  751 +
  752 + Future<void> requestNotificationAuthorization() async {
  753 + try {
  754 + final status = await Permission.notification.status;
  755 +
  756 + if (status.isGranted) {
  757 + // 已授权,无需再请求
  758 + return;
  759 + }
  760 +
  761 + if (status.isPermanentlyDenied) {
  762 + // 用户永久拒绝(Android),引导去设置
  763 + await openAppSettings();
  764 + return;
  765 + }
  766 +
  767 + // 未决定 → 弹出系统授权弹窗
  768 + final result = await Permission.notification.request();
  769 + AppLogger.d(result.isGranted
  770 + ? 'Notification authorization granted'
  771 + : 'Notification authorization skipped');
  772 + } catch (error) {
  773 + AppLogger.e('Notification authorization failed: $error');
  774 + }
  775 + }
726 } 776 }
@@ -55,6 +55,54 @@ class TodayHrvAdBanner extends StatelessWidget { @@ -55,6 +55,54 @@ class TodayHrvAdBanner extends StatelessWidget {
55 } 55 }
56 } 56 }
57 57
  58 +class TodayNotificationAuthorizationBanner extends StatelessWidget {
  59 + const TodayNotificationAuthorizationBanner({
  60 + super.key,
  61 + required this.controller,
  62 + this.showMargin = true,
  63 + });
  64 +
  65 + final TodayController controller;
  66 + final bool showMargin;
  67 +
  68 + @override
  69 + Widget build(BuildContext context) {
  70 + return Obx(() {
  71 + if (!controller.showHrvAdBanner.value) return const SizedBox.shrink();
  72 +
  73 + return GestureDetector(
  74 + onTap: () async {
  75 + ta.track('click_doublefeel_today_status',
  76 + properties: {'ita': '点击开启通知权限'});
  77 + await controller.requestNotificationAuthorization();
  78 + controller.checkNotificationAuthorizationBannerVisible();
  79 + },
  80 + child: _TodayGuideBanner(
  81 + title: context.l10n.turnOnNotifications,
  82 + subtitle:
  83 + context.l10n.stayUpToDateOnChangesInYourOwnAndYourFriendsHealth,
  84 + right: Container(
  85 + padding: const EdgeInsets.all(4),
  86 + decoration: const BoxDecoration(
  87 + color: Colors.white,
  88 + shape: BoxShape.circle,
  89 + ),
  90 + child: Image.asset(
  91 + 'assets/images/common/ic_arrow_forward.png',
  92 + width: 20,
  93 + height: 20,
  94 + color: context.colors.primary,
  95 + ),
  96 + ),
  97 + leftImagePath: 'assets/images/today/ic_banner_notify_permission.png',
  98 + onClose: controller.dismissHrvAdBanner,
  99 + showMargin: showMargin,
  100 + ),
  101 + );
  102 + });
  103 + }
  104 +}
  105 +
58 class TodayPartnerAdBanner extends StatelessWidget { 106 class TodayPartnerAdBanner extends StatelessWidget {
59 const TodayPartnerAdBanner({ 107 const TodayPartnerAdBanner({
60 super.key, 108 super.key,
@@ -213,6 +261,8 @@ class TodayAdCarousel extends StatelessWidget { @@ -213,6 +261,8 @@ class TodayAdCarousel extends StatelessWidget {
213 Widget build(BuildContext context) { 261 Widget build(BuildContext context) {
214 return Obx(() { 262 return Obx(() {
215 final showHrv = controller.showHrvAdBanner.value; 263 final showHrv = controller.showHrvAdBanner.value;
  264 + final showNotificationAuthorization =
  265 + controller.showNotificationAuthorizationBanner.value;
216 final showPartner = controller.showPartnerAdBanner.value && 266 final showPartner = controller.showPartnerAdBanner.value &&
217 !controller.userStateService.isBound; 267 !controller.userStateService.isBound;
218 268
@@ -225,6 +275,14 @@ class TodayAdCarousel extends StatelessWidget { @@ -225,6 +275,14 @@ class TodayAdCarousel extends StatelessWidget {
225 ), 275 ),
226 ); 276 );
227 } 277 }
  278 + if (showNotificationAuthorization) {
  279 + activeBanners.add(
  280 + TodayNotificationAuthorizationBanner(
  281 + controller: controller,
  282 + showMargin: false,
  283 + ),
  284 + );
  285 + }
228 if (showPartner) { 286 if (showPartner) {
229 activeBanners.add( 287 activeBanners.add(
230 TodayPartnerAdBanner( 288 TodayPartnerAdBanner(
@@ -201,7 +201,7 @@ class V2StressScore { @@ -201,7 +201,7 @@ class V2StressScore {
201 201
202 factory V2StressScore.fromJson(Map<String, dynamic> json) { 202 factory V2StressScore.fromJson(Map<String, dynamic> json) {
203 return V2StressScore( 203 return V2StressScore(
204 - state: _parseInt(json['state']), 204 + state: _parseInt(json['state']) ?? 0,
205 hrvScore: _parseInt(json['hrv_score']), 205 hrvScore: _parseInt(json['hrv_score']),
206 hrScore: _parseInt(json['hr_score']), 206 hrScore: _parseInt(json['hr_score']),
207 latestHr: _parseInt(json['latest_hr']), 207 latestHr: _parseInt(json['latest_hr']),
@@ -710,5 +710,7 @@ @@ -710,5 +710,7 @@
710 "latestHrvTipAttentionAboveBaseline": "Your HRV is on the low side. Consider relaxing, keeping regular rest, and paying attention to nutrition and recovery.", 710 "latestHrvTipAttentionAboveBaseline": "Your HRV is on the low side. Consider relaxing, keeping regular rest, and paying attention to nutrition and recovery.",
711 "latestHrvTipAttentionBelowBaseline": "Your HRV is clearly below your usual level. Recent stress may be elevated, so try to rest and adjust your state.", 711 "latestHrvTipAttentionBelowBaseline": "Your HRV is clearly below your usual level. Recent stress may be elevated, so try to rest and adjust your state.",
712 "latestHrvTipOverloadAboveBaseline": "Your HRV is at a relatively low level. Your body may be under higher stress. If this is after exercise, a lower HRV can be normal. Rest and recover in time.", 712 "latestHrvTipOverloadAboveBaseline": "Your HRV is at a relatively low level. Your body may be under higher stress. If this is after exercise, a lower HRV can be normal. Rest and recover in time.",
713 - "latestHrvTipOverloadBelowBaseline": "Your HRV is clearly below your usual level. Your body may be under high stress. If this is after exercise, a lower HRV can be normal. Reduce exertion, rest in time, and support sleep recovery."  
714 -} 713 + "latestHrvTipOverloadBelowBaseline": "Your HRV is clearly below your usual level. Your body may be under high stress. If this is after exercise, a lower HRV can be normal. Reduce exertion, rest in time, and support sleep recovery.",
  714 + "turnOnNotifications": "Turn on Notifications",
  715 + "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "Stay up to date on changes in your own and your friends' health"
  716 +}
@@ -1103,5 +1103,7 @@ @@ -1103,5 +1103,7 @@
1103 "latestHrvTipAttentionAboveBaseline": "你的 HRV 偏低,建议适当放松、规律休息,并注意饮食与恢复。", 1103 "latestHrvTipAttentionAboveBaseline": "你的 HRV 偏低,建议适当放松、规律休息,并注意饮食与恢复。",
1104 "latestHrvTipAttentionBelowBaseline": "你的 HRV 明显低于日常水平,近期可能压力偏高,建议尽量休息与调整状态。", 1104 "latestHrvTipAttentionBelowBaseline": "你的 HRV 明显低于日常水平,近期可能压力偏高,建议尽量休息与调整状态。",
1105 "latestHrvTipOverloadAboveBaseline": "你的 HRV 处于较低水平,身体可能正在承受较高压力(刚运动完 HRV 降低则属于正常情况),建议及时休息与恢复。", 1105 "latestHrvTipOverloadAboveBaseline": "你的 HRV 处于较低水平,身体可能正在承受较高压力(刚运动完 HRV 降低则属于正常情况),建议及时休息与恢复。",
1106 - "latestHrvTipOverloadBelowBaseline": "你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。"  
1107 -} 1106 + "latestHrvTipOverloadBelowBaseline": "你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。",
  1107 + "turnOnNotifications": "开启通知",
  1108 + "stayUpToDateOnChangesInYourOwnAndYourFriendsHealth": "及时了解自己和好友的健康波动"
  1109 +}
@@ -4082,6 +4082,18 @@ abstract class AppLocalizations { @@ -4082,6 +4082,18 @@ abstract class AppLocalizations {
4082 /// In zh, this message translates to: 4082 /// In zh, this message translates to:
4083 /// **'你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。'** 4083 /// **'你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。'**
4084 String get latestHrvTipOverloadBelowBaseline; 4084 String get latestHrvTipOverloadBelowBaseline;
  4085 +
  4086 + /// No description provided for @turnOnNotifications.
  4087 + ///
  4088 + /// In zh, this message translates to:
  4089 + /// **'开启通知'**
  4090 + String get turnOnNotifications;
  4091 +
  4092 + /// No description provided for @stayUpToDateOnChangesInYourOwnAndYourFriendsHealth.
  4093 + ///
  4094 + /// In zh, this message translates to:
  4095 + /// **'及时了解自己和好友的健康波动'**
  4096 + String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth;
4085 } 4097 }
4086 4098
4087 class _AppLocalizationsDelegate 4099 class _AppLocalizationsDelegate
@@ -2288,4 +2288,11 @@ class AppLocalizationsEn extends AppLocalizations { @@ -2288,4 +2288,11 @@ class AppLocalizationsEn extends AppLocalizations {
2288 @override 2288 @override
2289 String get latestHrvTipOverloadBelowBaseline => 2289 String get latestHrvTipOverloadBelowBaseline =>
2290 'Your HRV is clearly below your usual level. Your body may be under high stress. If this is after exercise, a lower HRV can be normal. Reduce exertion, rest in time, and support sleep recovery.'; 2290 'Your HRV is clearly below your usual level. Your body may be under high stress. If this is after exercise, a lower HRV can be normal. Reduce exertion, rest in time, and support sleep recovery.';
  2291 +
  2292 + @override
  2293 + String get turnOnNotifications => 'Turn on Notifications';
  2294 +
  2295 + @override
  2296 + String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth =>
  2297 + 'Stay up to date on changes in your own and your friends\' health';
2291 } 2298 }
@@ -2184,4 +2184,11 @@ class AppLocalizationsZh extends AppLocalizations { @@ -2184,4 +2184,11 @@ class AppLocalizationsZh extends AppLocalizations {
2184 @override 2184 @override
2185 String get latestHrvTipOverloadBelowBaseline => 2185 String get latestHrvTipOverloadBelowBaseline =>
2186 '你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。'; 2186 '你的 HRV 明显低于平时水平,身体可能处于高压力状态(刚运动完 HRV 降低则属于正常情况),建议减少消耗、及时休息,并保证睡眠恢复。';
  2187 +
  2188 + @override
  2189 + String get turnOnNotifications => '开启通知';
  2190 +
  2191 + @override
  2192 + String get stayUpToDateOnChangesInYourOwnAndYourFriendsHealth =>
  2193 + '及时了解自己和好友的健康波动';
2187 } 2194 }