Showing
18 changed files
with
270 additions
and
26 deletions
| 1 | +import 'package:doublefeel_flutter/core/network/api/click_event_api.dart'; | ||
| 1 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; | 2 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; |
| 2 | import 'package:get/get.dart'; | 3 | import 'package:get/get.dart'; |
| 3 | 4 | ||
| @@ -55,6 +56,7 @@ void registerUserSessionDeps() { | @@ -55,6 +56,7 @@ void registerUserSessionDeps() { | ||
| 55 | Get.put(UserApi(dioClient), permanent: true); | 56 | Get.put(UserApi(dioClient), permanent: true); |
| 56 | Get.put(VipApi(dioClient), permanent: true); | 57 | Get.put(VipApi(dioClient), permanent: true); |
| 57 | Get.put(FriendApi(dioClient), permanent: true); | 58 | Get.put(FriendApi(dioClient), permanent: true); |
| 59 | + Get.put(ClickEventApi(dioClient), permanent: true); | ||
| 58 | Get.put<ImService>(ImServiceStub(), permanent: true); | 60 | Get.put<ImService>(ImServiceStub(), permanent: true); |
| 59 | Get.put( | 61 | Get.put( |
| 60 | ThinkingDataService(Get.find<AppEnvironmentConfig>()), | 62 | ThinkingDataService(Get.find<AppEnvironmentConfig>()), |
| @@ -33,6 +33,16 @@ ActivityBurnDurationText activityBurnDurationText( | @@ -33,6 +33,16 @@ ActivityBurnDurationText activityBurnDurationText( | ||
| 33 | ); | 33 | ); |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | +ActivityBurnDurationText activityBurnMinutesText( | ||
| 37 | + BuildContext context, | ||
| 38 | + int? minutes, | ||
| 39 | +) { | ||
| 40 | + return ActivityBurnDurationText( | ||
| 41 | + value: minutes?.toString() ?? '-', | ||
| 42 | + unit: context.l10n.reportUnitMinute, | ||
| 43 | + ); | ||
| 44 | +} | ||
| 45 | + | ||
| 36 | ActivityBurnDurationText activityBurnHoursText( | 46 | ActivityBurnDurationText activityBurnHoursText( |
| 37 | BuildContext context, | 47 | BuildContext context, |
| 38 | int? hours, | 48 | int? hours, |
| @@ -19,7 +19,7 @@ class ActivityBurnSummaryCard extends StatelessWidget { | @@ -19,7 +19,7 @@ class ActivityBurnSummaryCard extends StatelessWidget { | ||
| 19 | 19 | ||
| 20 | @override | 20 | @override |
| 21 | Widget build(BuildContext context) { | 21 | Widget build(BuildContext context) { |
| 22 | - final exercise = activityBurnDurationText( | 22 | + final exercise = activityBurnMinutesText( |
| 23 | context, | 23 | context, |
| 24 | report?.exerciseMinutes?.value, | 24 | report?.exerciseMinutes?.value, |
| 25 | ); | 25 | ); |
| @@ -22,12 +22,16 @@ class AppReviewPromptLogic { | @@ -22,12 +22,16 @@ class AppReviewPromptLogic { | ||
| 22 | static const _likedCooldown = Duration(days: 90); | 22 | static const _likedCooldown = Duration(days: 90); |
| 23 | static const _feedbackCooldown = Duration(days: 30); | 23 | static const _feedbackCooldown = Duration(days: 30); |
| 24 | 24 | ||
| 25 | - Future<void> maybeShowPrompt(BuildContext context) async { | ||
| 26 | - if (_isShowing || !context.mounted) return; | 25 | + bool canShowPrompt(BuildContext context) { |
| 26 | + if (_isShowing || !context.mounted) return false; | ||
| 27 | 27 | ||
| 28 | final now = DateTime.now(); | 28 | final now = DateTime.now(); |
| 29 | final nextShowAt = _storage.appReviewPromptNextShowAt; | 29 | final nextShowAt = _storage.appReviewPromptNextShowAt; |
| 30 | - if (nextShowAt != null && now.isBefore(nextShowAt)) return; | 30 | + return nextShowAt == null || !now.isBefore(nextShowAt); |
| 31 | + } | ||
| 32 | + | ||
| 33 | + Future<void> maybeShowPrompt(BuildContext context) async { | ||
| 34 | + if (!canShowPrompt(context)) return; | ||
| 31 | 35 | ||
| 32 | _isShowing = true; | 36 | _isShowing = true; |
| 33 | try { | 37 | try { |
| @@ -18,6 +18,7 @@ class AppReviewPromptDialog extends StatelessWidget { | @@ -18,6 +18,7 @@ class AppReviewPromptDialog extends StatelessWidget { | ||
| 18 | illustrationAsset: R.assetsImagesAppReviewGoodBg, | 18 | illustrationAsset: R.assetsImagesAppReviewGoodBg, |
| 19 | title: context.l10n.appReviewPromptTitle, | 19 | title: context.l10n.appReviewPromptTitle, |
| 20 | message: context.l10n.appReviewPromptMessage, | 20 | message: context.l10n.appReviewPromptMessage, |
| 21 | + primaryEmoji: context.l10n.appReviewPromptLikeActionEmoji, | ||
| 21 | primaryText: context.l10n.appReviewPromptLikeAction, | 22 | primaryText: context.l10n.appReviewPromptLikeAction, |
| 22 | secondaryText: context.l10n.appReviewPromptFeedbackAction, | 23 | secondaryText: context.l10n.appReviewPromptFeedbackAction, |
| 23 | onPrimaryTap: () => | 24 | onPrimaryTap: () => |
| @@ -57,12 +58,14 @@ class _ReviewPromptCard extends StatelessWidget { | @@ -57,12 +58,14 @@ class _ReviewPromptCard extends StatelessWidget { | ||
| 57 | required this.secondaryText, | 58 | required this.secondaryText, |
| 58 | required this.onPrimaryTap, | 59 | required this.onPrimaryTap, |
| 59 | required this.onSecondaryTap, | 60 | required this.onSecondaryTap, |
| 61 | + this.primaryEmoji, | ||
| 60 | }); | 62 | }); |
| 61 | 63 | ||
| 62 | final double height; | 64 | final double height; |
| 63 | final String illustrationAsset; | 65 | final String illustrationAsset; |
| 64 | final String title; | 66 | final String title; |
| 65 | final String message; | 67 | final String message; |
| 68 | + final String? primaryEmoji; | ||
| 66 | final String primaryText; | 69 | final String primaryText; |
| 67 | final String secondaryText; | 70 | final String secondaryText; |
| 68 | final VoidCallback onPrimaryTap; | 71 | final VoidCallback onPrimaryTap; |
| @@ -149,6 +152,7 @@ class _ReviewPromptCard extends StatelessWidget { | @@ -149,6 +152,7 @@ class _ReviewPromptCard extends StatelessWidget { | ||
| 149 | left: 40, | 152 | left: 40, |
| 150 | right: 40, | 153 | right: 40, |
| 151 | child: _PromptButton( | 154 | child: _PromptButton( |
| 155 | + emoji: primaryEmoji, | ||
| 152 | text: primaryText, | 156 | text: primaryText, |
| 153 | textColor: Colors.white, | 157 | textColor: Colors.white, |
| 154 | fontWeight: FontWeight.w600, | 158 | fontWeight: FontWeight.w600, |
| @@ -182,15 +186,31 @@ class _PromptButton extends StatelessWidget { | @@ -182,15 +186,31 @@ class _PromptButton extends StatelessWidget { | ||
| 182 | required this.textColor, | 186 | required this.textColor, |
| 183 | required this.fontWeight, | 187 | required this.fontWeight, |
| 184 | required this.onTap, | 188 | required this.onTap, |
| 189 | + this.emoji, | ||
| 185 | this.backgroundColor, | 190 | this.backgroundColor, |
| 186 | }); | 191 | }); |
| 187 | 192 | ||
| 193 | + final String? emoji; | ||
| 188 | final String text; | 194 | final String text; |
| 189 | final Color textColor; | 195 | final Color textColor; |
| 190 | final FontWeight fontWeight; | 196 | final FontWeight fontWeight; |
| 191 | final VoidCallback onTap; | 197 | final VoidCallback onTap; |
| 192 | final Color? backgroundColor; | 198 | final Color? backgroundColor; |
| 193 | 199 | ||
| 200 | + TextStyle get _emojiStyle => TextStyle( | ||
| 201 | + color: textColor, | ||
| 202 | + fontSize: 18, | ||
| 203 | + height: 1.2, | ||
| 204 | + fontWeight: fontWeight, | ||
| 205 | + ); | ||
| 206 | + | ||
| 207 | + TextStyle get _textStyle => TextStyle( | ||
| 208 | + color: textColor, | ||
| 209 | + fontSize: 16, | ||
| 210 | + height: 1.2, | ||
| 211 | + fontWeight: fontWeight, | ||
| 212 | + ); | ||
| 213 | + | ||
| 194 | @override | 214 | @override |
| 195 | Widget build(BuildContext context) { | 215 | Widget build(BuildContext context) { |
| 196 | return GestureDetector( | 216 | return GestureDetector( |
| @@ -203,18 +223,42 @@ class _PromptButton extends StatelessWidget { | @@ -203,18 +223,42 @@ class _PromptButton extends StatelessWidget { | ||
| 203 | color: backgroundColor, | 223 | color: backgroundColor, |
| 204 | borderRadius: BorderRadius.circular(24), | 224 | borderRadius: BorderRadius.circular(24), |
| 205 | ), | 225 | ), |
| 206 | - child: Text( | ||
| 207 | - text, | 226 | + padding: const EdgeInsets.symmetric(horizontal: 16), |
| 227 | + child: _buildContent(), | ||
| 228 | + ), | ||
| 229 | + ); | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + Widget _buildContent() { | ||
| 233 | + final emojiText = emoji; | ||
| 234 | + if (emojiText == null) { | ||
| 235 | + return Text( | ||
| 236 | + text, | ||
| 237 | + maxLines: 1, | ||
| 238 | + overflow: TextOverflow.ellipsis, | ||
| 239 | + style: _textStyle, | ||
| 240 | + ); | ||
| 241 | + } | ||
| 242 | + | ||
| 243 | + return Row( | ||
| 244 | + mainAxisAlignment: MainAxisAlignment.center, | ||
| 245 | + crossAxisAlignment: CrossAxisAlignment.center, | ||
| 246 | + children: [ | ||
| 247 | + Text( | ||
| 248 | + emojiText, | ||
| 208 | maxLines: 1, | 249 | maxLines: 1, |
| 209 | - overflow: TextOverflow.ellipsis, | ||
| 210 | - style: TextStyle( | ||
| 211 | - color: textColor, | ||
| 212 | - fontSize: 16, | ||
| 213 | - height: 1.2, | ||
| 214 | - fontWeight: fontWeight, | 250 | + style: _emojiStyle, |
| 251 | + ), | ||
| 252 | + const SizedBox(width: 2), | ||
| 253 | + Flexible( | ||
| 254 | + child: Text( | ||
| 255 | + text, | ||
| 256 | + maxLines: 1, | ||
| 257 | + overflow: TextOverflow.ellipsis, | ||
| 258 | + style: _textStyle, | ||
| 215 | ), | 259 | ), |
| 216 | ), | 260 | ), |
| 217 | - ), | 261 | + ], |
| 218 | ); | 262 | ); |
| 219 | } | 263 | } |
| 220 | } | 264 | } |
| 1 | +import 'package:doublefeel_flutter/core/constants/event_const.dart'; | ||
| 1 | import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'; | 2 | import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'; |
| 2 | import 'package:get/get.dart'; | 3 | import 'package:get/get.dart'; |
| 3 | 4 | ||
| 5 | +import '../../../../core/network/api/click_event_api.dart'; | ||
| 4 | import '../../health_trend/controllers/health_trend_analytics.dart'; | 6 | import '../../health_trend/controllers/health_trend_analytics.dart'; |
| 5 | import '../../health_trend/controllers/health_trend_control.dart'; | 7 | import '../../health_trend/controllers/health_trend_control.dart'; |
| 6 | import '../../report_common/models/health_report_query.dart'; | 8 | import '../../report_common/models/health_report_query.dart'; |
| @@ -68,5 +70,12 @@ class FriendTrendController extends GetxController with HealthTrendControl { | @@ -68,5 +70,12 @@ class FriendTrendController extends GetxController with HealthTrendControl { | ||
| 68 | selectedTypeIndex.value, | 70 | selectedTypeIndex.value, |
| 69 | userRole: '好友的', | 71 | userRole: '好友的', |
| 70 | ); | 72 | ); |
| 73 | + _trackViewFriendSleepStatsIfNeeded(); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + void _trackViewFriendSleepStatsIfNeeded() { | ||
| 77 | + if (selectedTypeIndex.value != 2) return; | ||
| 78 | + if (!Get.isRegistered<ClickEventApi>()) return; | ||
| 79 | + Get.find<ClickEventApi>().postClickEvent(EventConst.viewFriendSleepStats); | ||
| 71 | } | 80 | } |
| 72 | } | 81 | } |
| 1 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart'; | 1 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart'; |
| 2 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friends_controller.dart'; | 2 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friends_controller.dart'; |
| 3 | +import 'package:doublefeel_flutter/core/constants/event_const.dart'; | ||
| 4 | +import 'package:doublefeel_flutter/core/constants/intent_keys.dart'; | ||
| 5 | +import 'package:doublefeel_flutter/core/network/api/click_event_api.dart'; | ||
| 3 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; | 6 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; |
| 4 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 7 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 5 | import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | 8 | import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; |
| @@ -7,14 +10,13 @@ import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | @@ -7,14 +10,13 @@ import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | ||
| 7 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; | 10 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; |
| 8 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; | 11 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; |
| 9 | import 'package:flutter/material.dart'; | 12 | import 'package:flutter/material.dart'; |
| 10 | -import 'package:flutter/services.dart'; | ||
| 11 | import 'package:flutter/scheduler.dart'; | 13 | import 'package:flutter/scheduler.dart'; |
| 12 | - | 14 | +import 'package:flutter/services.dart'; |
| 13 | import 'package:get/get.dart'; | 15 | import 'package:get/get.dart'; |
| 14 | 16 | ||
| 15 | import '../../../routes/app_pages.dart'; | 17 | import '../../../routes/app_pages.dart'; |
| 16 | -import '../../purchase/purchase_route_args.dart'; | ||
| 17 | import '../../app_review_prompt/logic/app_review_prompt_logic.dart'; | 18 | import '../../app_review_prompt/logic/app_review_prompt_logic.dart'; |
| 19 | +import '../../purchase/purchase_route_args.dart'; | ||
| 18 | import '../../report_common/models/report_period.dart'; | 20 | import '../../report_common/models/report_period.dart'; |
| 19 | import 'today_controller.dart'; | 21 | import 'today_controller.dart'; |
| 20 | import 'trend/trend_controller.dart'; | 22 | import 'trend/trend_controller.dart'; |
| @@ -81,8 +83,29 @@ class HomeController extends GetxController { | @@ -81,8 +83,29 @@ class HomeController extends GetxController { | ||
| 81 | return url.isNotEmpty; | 83 | return url.isNotEmpty; |
| 82 | } | 84 | } |
| 83 | 85 | ||
| 84 | - Future<void> maybeShowAppReviewPrompt(BuildContext context) { | ||
| 85 | - return _appReviewPromptLogic.maybeShowPrompt(context); | 86 | + Future<void> maybeShowAppReviewPrompt(BuildContext context) async { |
| 87 | + if (!_appReviewPromptLogic.canShowPrompt(context)) return; | ||
| 88 | + | ||
| 89 | + final canShowPrompt = await _hasReviewPromptClickEvent(); | ||
| 90 | + if (!canShowPrompt) return; | ||
| 91 | + | ||
| 92 | + await _appReviewPromptLogic.maybeShowPrompt(context); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + Future<bool> _hasReviewPromptClickEvent() async { | ||
| 96 | + if (!Get.isRegistered<ClickEventApi>()) return false; | ||
| 97 | + | ||
| 98 | + final result = await Get.find<ClickEventApi>().getClickEvent(); | ||
| 99 | + return switch (result) { | ||
| 100 | + AppSuccess(:final data) => | ||
| 101 | + data.list?.any( | ||
| 102 | + (item) => | ||
| 103 | + EventConst.keys.contains(item.eventName) && | ||
| 104 | + (item.clickCnt ?? 0) > 0, | ||
| 105 | + ) ?? | ||
| 106 | + false, | ||
| 107 | + AppFailure() => false, | ||
| 108 | + }; | ||
| 86 | } | 109 | } |
| 87 | 110 | ||
| 88 | Future<bool> maybeShowMembershipOffer() async { | 111 | Future<bool> maybeShowMembershipOffer() async { |
| @@ -102,7 +125,10 @@ class HomeController extends GetxController { | @@ -102,7 +125,10 @@ class HomeController extends GetxController { | ||
| 102 | ); | 125 | ); |
| 103 | await Get.toNamed( | 126 | await Get.toNamed( |
| 104 | Routes.PURCHASE, | 127 | Routes.PURCHASE, |
| 105 | - arguments: {PurchaseRouteArgs.showCloseButton: true}, | 128 | + arguments: { |
| 129 | + PurchaseRouteArgs.showCloseButton: true, | ||
| 130 | + IntentKeys.channelType: "登录全屏推送", | ||
| 131 | + }, | ||
| 106 | ); | 132 | ); |
| 107 | return true; | 133 | return true; |
| 108 | } | 134 | } |
| @@ -120,7 +146,7 @@ class HomeController extends GetxController { | @@ -120,7 +146,7 @@ class HomeController extends GetxController { | ||
| 120 | } else if (previousIndex == friendsTabIndex && index != friendsTabIndex) { | 146 | } else if (previousIndex == friendsTabIndex && index != friendsTabIndex) { |
| 121 | Get.find<FriendsController>().markPageHidden(); | 147 | Get.find<FriendsController>().markPageHidden(); |
| 122 | } | 148 | } |
| 123 | - switch (index) { | 149 | + switch (index) { |
| 124 | case 0: | 150 | case 0: |
| 125 | Get.find<TodayController>().refreshTab(); | 151 | Get.find<TodayController>().refreshTab(); |
| 126 | break; | 152 | break; |
| @@ -149,6 +175,7 @@ class HomeController extends GetxController { | @@ -149,6 +175,7 @@ class HomeController extends GetxController { | ||
| 149 | final params = uri.queryParameters; // {"tab": "today"} 等 | 175 | final params = uri.queryParameters; // {"tab": "today"} 等 |
| 150 | 176 | ||
| 151 | _dispatchRoute(path, params); | 177 | _dispatchRoute(path, params); |
| 178 | + _executePostClickEvent(path, params); | ||
| 152 | } catch (_) {} | 179 | } catch (_) {} |
| 153 | } | 180 | } |
| 154 | 181 | ||
| @@ -199,6 +226,24 @@ class HomeController extends GetxController { | @@ -199,6 +226,24 @@ class HomeController extends GetxController { | ||
| 199 | } | 226 | } |
| 200 | } | 227 | } |
| 201 | 228 | ||
| 229 | + void _executePostClickEvent(String path, Map<String, String> params) { | ||
| 230 | + try { | ||
| 231 | + var isHrvChange = params['is_hrv_change'] == "1"; | ||
| 232 | + if (isHrvChange) { | ||
| 233 | + final clickEventApi = Get.find<ClickEventApi>(); | ||
| 234 | + if (!Get.isRegistered<ClickEventApi>()) return; | ||
| 235 | + switch (path) { | ||
| 236 | + case AppRoutes.home: | ||
| 237 | + clickEventApi.postClickEvent(EventConst.hrvChangeClick); | ||
| 238 | + break; | ||
| 239 | + case Routes.FRIEND_HOME: | ||
| 240 | + clickEventApi.postClickEvent(EventConst.friendHrvChangeClick); | ||
| 241 | + break; | ||
| 242 | + } | ||
| 243 | + } | ||
| 244 | + } catch (e) {} | ||
| 245 | + } | ||
| 246 | + | ||
| 202 | /// 根据 tab 名称切换首页底部 tab | 247 | /// 根据 tab 名称切换首页底部 tab |
| 203 | void _switchHomeTab(String? tab, Map<String, String> params) { | 248 | void _switchHomeTab(String? tab, Map<String, String> params) { |
| 204 | switch (tab) { | 249 | switch (tab) { |
| 1 | +import 'package:doublefeel_flutter/core/constants/event_const.dart'; | ||
| 1 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
| 2 | import 'package:get/get.dart'; | 3 | import 'package:get/get.dart'; |
| 3 | 4 | ||
| 5 | +import '../../../../../core/network/api/click_event_api.dart'; | ||
| 4 | import '../../../../../core/network/api/friend_api.dart'; | 6 | import '../../../../../core/network/api/friend_api.dart'; |
| 5 | import '../../../../../core/result/app_result.dart'; | 7 | import '../../../../../core/result/app_result.dart'; |
| 6 | import '../../../../../data/models/friend/friend_models.dart'; | 8 | import '../../../../../data/models/friend/friend_models.dart'; |
| @@ -78,6 +80,7 @@ class TrendController extends GetxController with HealthTrendControl { | @@ -78,6 +80,7 @@ class TrendController extends GetxController with HealthTrendControl { | ||
| 78 | if (userId == targetUserId.value) return; | 80 | if (userId == targetUserId.value) return; |
| 79 | targetUserId.value = userId; | 81 | targetUserId.value = userId; |
| 80 | refreshToken.value++; | 82 | refreshToken.value++; |
| 83 | + _trackViewSleepStatsIfNeeded(); | ||
| 81 | } | 84 | } |
| 82 | 85 | ||
| 83 | void selectFriend(FriendItem friendInfo) { | 86 | void selectFriend(FriendItem friendInfo) { |
| @@ -154,5 +157,19 @@ class TrendController extends GetxController with HealthTrendControl { | @@ -154,5 +157,19 @@ class TrendController extends GetxController with HealthTrendControl { | ||
| 154 | selectedTypeIndex.value, | 157 | selectedTypeIndex.value, |
| 155 | userRole: targetFriendInfo.value == null ? '我的' : '好友的', | 158 | userRole: targetFriendInfo.value == null ? '我的' : '好友的', |
| 156 | ); | 159 | ); |
| 160 | + _trackViewSleepStatsIfNeeded(); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + void _trackViewSleepStatsIfNeeded() { | ||
| 164 | + if (!_isPageVisible || | ||
| 165 | + selectedTypeIndex.value != TrendType.sleep.tabIndex) { | ||
| 166 | + return; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + final eventName = targetUserId.value == null | ||
| 170 | + ? EventConst.viewSleepStats | ||
| 171 | + : EventConst.viewFriendSleepStats; | ||
| 172 | + if (!Get.isRegistered<ClickEventApi>()) return; | ||
| 173 | + Get.find<ClickEventApi>().postClickEvent(eventName); | ||
| 157 | } | 174 | } |
| 158 | } | 175 | } |
| @@ -96,7 +96,11 @@ class PurchaseController extends GetxController { | @@ -96,7 +96,11 @@ class PurchaseController extends GetxController { | ||
| 96 | arguments: {'from': 'onboarding'}, | 96 | arguments: {'from': 'onboarding'}, |
| 97 | ); | 97 | ); |
| 98 | } else { | 98 | } else { |
| 99 | - Get.back(); | 99 | + if (Navigator.canPop(Get.context!)) { |
| 100 | + Get.back(); | ||
| 101 | + } else { | ||
| 102 | + Get.offAllNamed(AppRoutes.home); | ||
| 103 | + } | ||
| 100 | } | 104 | } |
| 101 | } | 105 | } |
| 102 | 106 |
lib/core/constants/event_const.dart
0 → 100644
| 1 | +abstract final class EventConst { | ||
| 2 | + static const String hrvChangeClick = "hrv_change_click"; | ||
| 3 | + static const String friendHrvChangeClick = "friend_hrv_change_click"; | ||
| 4 | + static const String viewSleepStats = "view_sleep_stats"; | ||
| 5 | + static const String viewFriendSleepStats = "view_friend_sleep_stats"; | ||
| 6 | + | ||
| 7 | + static const Set<String> keys = { | ||
| 8 | + hrvChangeClick, | ||
| 9 | + friendHrvChangeClick, | ||
| 10 | + viewSleepStats, | ||
| 11 | + viewFriendSleepStats, | ||
| 12 | + }; | ||
| 13 | +} |
lib/core/network/api/click_event_api.dart
0 → 100644
| 1 | +import 'package:doublefeel_flutter/data/models/event/click_event_data.dart'; | ||
| 2 | + | ||
| 3 | +import '../../result/app_result.dart'; | ||
| 4 | +import '../../result/safe_call.dart'; | ||
| 5 | +import '../api_paths.dart'; | ||
| 6 | +import '../dio_client.dart'; | ||
| 7 | + | ||
| 8 | +class ClickEventApi { | ||
| 9 | + ClickEventApi(this._dioClient); | ||
| 10 | + | ||
| 11 | + final DioClient _dioClient; | ||
| 12 | + | ||
| 13 | + Future<AppResult<void>> postClickEvent(String eventName) { | ||
| 14 | + return safeCall( | ||
| 15 | + call: () async { | ||
| 16 | + await _dioClient.dio.post( | ||
| 17 | + ApiPaths.clickEvent, | ||
| 18 | + data: {'event_name': eventName}, | ||
| 19 | + ); | ||
| 20 | + }, | ||
| 21 | + ); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + Future<AppResult<ClickEventData>> getClickEvent() { | ||
| 25 | + return safeCall( | ||
| 26 | + call: () async { | ||
| 27 | + final response = await _dioClient.dio.get(ApiPaths.clickEvent); | ||
| 28 | + return ClickEventData.fromJson(response.data as Map<String, dynamic>); | ||
| 29 | + }, | ||
| 30 | + errorHandlingPolicy: null, | ||
| 31 | + ); | ||
| 32 | + } | ||
| 33 | +} |
| @@ -72,4 +72,7 @@ abstract final class ApiPaths { | @@ -72,4 +72,7 @@ abstract final class ApiPaths { | ||
| 72 | // friends | 72 | // friends |
| 73 | static const friends = '/client/doublefeel/health/v2/friends/'; | 73 | static const friends = '/client/doublefeel/health/v2/friends/'; |
| 74 | static const friendInfo = '/client/doublefeel/health/v2/friend_info/'; | 74 | static const friendInfo = '/client/doublefeel/health/v2/friend_info/'; |
| 75 | + | ||
| 76 | + // click event | ||
| 77 | + static const clickEvent = '/client/doublefeel/click_event/'; | ||
| 75 | } | 78 | } |
lib/data/models/event/click_event_data.dart
0 → 100644
| 1 | +class ClickEventData { | ||
| 2 | + ClickEventData({ | ||
| 3 | + this.list, | ||
| 4 | + }); | ||
| 5 | + | ||
| 6 | + ClickEventData.fromJson(dynamic json) { | ||
| 7 | + if (json['list'] != null) { | ||
| 8 | + list = []; | ||
| 9 | + json['list'].forEach((v) { | ||
| 10 | + list?.add(ClickEventItem.fromJson(v)); | ||
| 11 | + }); | ||
| 12 | + } | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + List<ClickEventItem>? list; | ||
| 16 | + | ||
| 17 | + Map<String, dynamic> toJson() { | ||
| 18 | + final map = <String, dynamic>{}; | ||
| 19 | + if (list != null) { | ||
| 20 | + map['list'] = list?.map((v) => v.toJson()).toList(); | ||
| 21 | + } | ||
| 22 | + return map; | ||
| 23 | + } | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +class ClickEventItem { | ||
| 27 | + ClickEventItem({ | ||
| 28 | + this.eventName, | ||
| 29 | + this.clickCnt, | ||
| 30 | + }); | ||
| 31 | + | ||
| 32 | + ClickEventItem.fromJson(dynamic json) { | ||
| 33 | + eventName = json['event_name']; | ||
| 34 | + clickCnt = json['click_cnt']; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + String? eventName; | ||
| 38 | + num? clickCnt; | ||
| 39 | + | ||
| 40 | + Map<String, dynamic> toJson() { | ||
| 41 | + final map = <String, dynamic>{}; | ||
| 42 | + map['event_name'] = eventName; | ||
| 43 | + map['click_cnt'] = clickCnt; | ||
| 44 | + return map; | ||
| 45 | + } | ||
| 46 | +} |
| @@ -498,7 +498,8 @@ | @@ -498,7 +498,8 @@ | ||
| 498 | "refundFaqTitle": "DoubleFeel FAQs", | 498 | "refundFaqTitle": "DoubleFeel FAQs", |
| 499 | "appReviewPromptTitle": "Enjoying DoubleFeel?", | 499 | "appReviewPromptTitle": "Enjoying DoubleFeel?", |
| 500 | "appReviewPromptMessage": "Hi! Is DoubleFeel helping you better understand\nyour stress and sleep? 💜", | 500 | "appReviewPromptMessage": "Hi! Is DoubleFeel helping you better understand\nyour stress and sleep? 💜", |
| 501 | - "appReviewPromptLikeAction": "😍 Love it", | 501 | + "appReviewPromptLikeActionEmoji": "😍", |
| 502 | + "appReviewPromptLikeAction": "Love it", | ||
| 502 | "appReviewPromptFeedbackAction": "I have feedback", | 503 | "appReviewPromptFeedbackAction": "I have feedback", |
| 503 | "appReviewFeedbackTitle": "We're sorry DoubleFeel didn't give you\na good experience", | 504 | "appReviewFeedbackTitle": "We're sorry DoubleFeel didn't give you\na good experience", |
| 504 | "appReviewFeedbackMessage": "Would you tell us what went wrong?\nYour feedback helps us improve the stress and health experience. 💜", | 505 | "appReviewFeedbackMessage": "Would you tell us what went wrong?\nYour feedback helps us improve the stress and health experience. 💜", |
| @@ -786,7 +786,8 @@ | @@ -786,7 +786,8 @@ | ||
| 786 | "refundFaqTitle": "DoubleFeel 常见问题", | 786 | "refundFaqTitle": "DoubleFeel 常见问题", |
| 787 | "appReviewPromptTitle": "喜欢 DoubleFeel 吗?", | 787 | "appReviewPromptTitle": "喜欢 DoubleFeel 吗?", |
| 788 | "appReviewPromptMessage": "嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜", | 788 | "appReviewPromptMessage": "嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜", |
| 789 | - "appReviewPromptLikeAction": "😍 很喜欢", | 789 | + "appReviewPromptLikeActionEmoji": "😍", |
| 790 | + "appReviewPromptLikeAction": "很喜欢", | ||
| 790 | "appReviewPromptFeedbackAction": "我有意见", | 791 | "appReviewPromptFeedbackAction": "我有意见", |
| 791 | "appReviewFeedbackTitle": "很抱歉 DoubleFeel 没有带\n给你好的体验", | 792 | "appReviewFeedbackTitle": "很抱歉 DoubleFeel 没有带\n给你好的体验", |
| 792 | "appReviewFeedbackMessage": "愿意告诉我们遇到了什么问题吗?\n你的反馈可以帮助我们持续改进压力与健康体验 💜", | 793 | "appReviewFeedbackMessage": "愿意告诉我们遇到了什么问题吗?\n你的反馈可以帮助我们持续改进压力与健康体验 💜", |
| @@ -3087,10 +3087,16 @@ abstract class AppLocalizations { | @@ -3087,10 +3087,16 @@ abstract class AppLocalizations { | ||
| 3087 | /// **'嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜'** | 3087 | /// **'嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜'** |
| 3088 | String get appReviewPromptMessage; | 3088 | String get appReviewPromptMessage; |
| 3089 | 3089 | ||
| 3090 | + /// No description provided for @appReviewPromptLikeActionEmoji. | ||
| 3091 | + /// | ||
| 3092 | + /// In zh, this message translates to: | ||
| 3093 | + /// **'😍'** | ||
| 3094 | + String get appReviewPromptLikeActionEmoji; | ||
| 3095 | + | ||
| 3090 | /// No description provided for @appReviewPromptLikeAction. | 3096 | /// No description provided for @appReviewPromptLikeAction. |
| 3091 | /// | 3097 | /// |
| 3092 | /// In zh, this message translates to: | 3098 | /// In zh, this message translates to: |
| 3093 | - /// **'😍 很喜欢'** | 3099 | + /// **'很喜欢'** |
| 3094 | String get appReviewPromptLikeAction; | 3100 | String get appReviewPromptLikeAction; |
| 3095 | 3101 | ||
| 3096 | /// No description provided for @appReviewPromptFeedbackAction. | 3102 | /// No description provided for @appReviewPromptFeedbackAction. |
| @@ -1722,7 +1722,10 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1722,7 +1722,10 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1722 | 'Hi! Is DoubleFeel helping you better understand\nyour stress and sleep? 💜'; | 1722 | 'Hi! Is DoubleFeel helping you better understand\nyour stress and sleep? 💜'; |
| 1723 | 1723 | ||
| 1724 | @override | 1724 | @override |
| 1725 | - String get appReviewPromptLikeAction => '😍 Love it'; | 1725 | + String get appReviewPromptLikeActionEmoji => '😍'; |
| 1726 | + | ||
| 1727 | + @override | ||
| 1728 | + String get appReviewPromptLikeAction => 'Love it'; | ||
| 1726 | 1729 | ||
| 1727 | @override | 1730 | @override |
| 1728 | String get appReviewPromptFeedbackAction => 'I have feedback'; | 1731 | String get appReviewPromptFeedbackAction => 'I have feedback'; |
| @@ -1628,7 +1628,10 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -1628,7 +1628,10 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 1628 | '嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜'; | 1628 | '嗨~想知道 DoubleFeel 是否正在帮助你\n更了解自己的压力与睡眠状态 💜'; |
| 1629 | 1629 | ||
| 1630 | @override | 1630 | @override |
| 1631 | - String get appReviewPromptLikeAction => '😍 很喜欢'; | 1631 | + String get appReviewPromptLikeActionEmoji => '😍'; |
| 1632 | + | ||
| 1633 | + @override | ||
| 1634 | + String get appReviewPromptLikeAction => '很喜欢'; | ||
| 1632 | 1635 | ||
| 1633 | @override | 1636 | @override |
| 1634 | String get appReviewPromptFeedbackAction => '我有意见'; | 1637 | String get appReviewPromptFeedbackAction => '我有意见'; |
-
Please register or login to post a comment