Showing
18 changed files
with
370 additions
and
145 deletions
| @@ -98,6 +98,19 @@ class PlatformHostApiImpl(private val application: android.app.Application) : Pl | @@ -98,6 +98,19 @@ class PlatformHostApiImpl(private val application: android.app.Application) : Pl | ||
| 98 | return BuildConfig.DEBUG | 98 | return BuildConfig.DEBUG |
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | + override fun shareText( | ||
| 102 | + text: String, | ||
| 103 | + callback: (Result<Boolean>) -> Unit | ||
| 104 | + ) { | ||
| 105 | + | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + override fun shareFileData( | ||
| 109 | + databytes: ByteArray, | ||
| 110 | + callback: (Result<Boolean>) -> Unit | ||
| 111 | + ) { | ||
| 112 | + } | ||
| 113 | + | ||
| 101 | override fun requestAppReview(callback: (Result<Boolean>) -> Unit) { | 114 | override fun requestAppReview(callback: (Result<Boolean>) -> Unit) { |
| 102 | } | 115 | } |
| 103 | 116 |
| @@ -8,6 +8,7 @@ import 'package:doublefeel_flutter/core/error/app_error.dart'; | @@ -8,6 +8,7 @@ import 'package:doublefeel_flutter/core/error/app_error.dart'; | ||
| 8 | import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart'; | 8 | import 'package:doublefeel_flutter/core/error/http_error_handling_policy.dart'; |
| 9 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; | 9 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; |
| 10 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 10 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 11 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 11 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 12 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 12 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 13 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 13 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | 14 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; |
| @@ -34,12 +35,22 @@ class BindPartnerController extends GetxController { | @@ -34,12 +35,22 @@ class BindPartnerController extends GetxController { | ||
| 34 | 35 | ||
| 35 | late bool isShowAppBarBack; | 36 | late bool isShowAppBarBack; |
| 36 | late bool isShowSkipButton; | 37 | late bool isShowSkipButton; |
| 38 | + var channel = ''; | ||
| 37 | 39 | ||
| 38 | @override | 40 | @override |
| 39 | void onInit() { | 41 | void onInit() { |
| 40 | super.onInit(); | 42 | super.onInit(); |
| 41 | final from = Get.arguments?['from'] ?? ''; | 43 | final from = Get.arguments?['from'] ?? ''; |
| 42 | if (from == 'onboarding') { | 44 | if (from == 'onboarding') { |
| 45 | + channel = '新手引导'; | ||
| 46 | + } else if (from == 'FriendsTab') { | ||
| 47 | + channel = '好友页'; | ||
| 48 | + } else if (from == 'today') { | ||
| 49 | + channel = '首页'; | ||
| 50 | + } else { | ||
| 51 | + channel = '其他'; | ||
| 52 | + } | ||
| 53 | + if (from == 'onboarding') { | ||
| 43 | isShowAppBarBack = false; | 54 | isShowAppBarBack = false; |
| 44 | isShowSkipButton = true; | 55 | isShowSkipButton = true; |
| 45 | } else { | 56 | } else { |
| @@ -57,6 +68,13 @@ class BindPartnerController extends GetxController { | @@ -57,6 +68,13 @@ class BindPartnerController extends GetxController { | ||
| 57 | } | 68 | } |
| 58 | 69 | ||
| 59 | @override | 70 | @override |
| 71 | + void onReady() { | ||
| 72 | + super.onReady(); | ||
| 73 | + ta.track('enter_doublefeel_add_friend_page', | ||
| 74 | + properties: {'channel_type': channel}); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + @override | ||
| 60 | void onClose() { | 78 | void onClose() { |
| 61 | partnerIdController.dispose(); | 79 | partnerIdController.dispose(); |
| 62 | super.onClose(); | 80 | super.onClose(); |
| @@ -66,6 +84,8 @@ class BindPartnerController extends GetxController { | @@ -66,6 +84,8 @@ class BindPartnerController extends GetxController { | ||
| 66 | 84 | ||
| 67 | void onShareMyCode() async { | 85 | void onShareMyCode() async { |
| 68 | try { | 86 | try { |
| 87 | + ta.track('enter_doublefeel_add_friend_page', | ||
| 88 | + properties: {'channel_type': channel, 'click_content': '分享我的码'}); | ||
| 69 | final success = await PlatformHostApi() | 89 | final success = await PlatformHostApi() |
| 70 | .shareText(l10n.sharePartnerCodeTemplate(myInviteCode.value)); | 90 | .shareText(l10n.sharePartnerCodeTemplate(myInviteCode.value)); |
| 71 | } catch (e) { | 91 | } catch (e) { |
| @@ -74,6 +94,8 @@ class BindPartnerController extends GetxController { | @@ -74,6 +94,8 @@ class BindPartnerController extends GetxController { | ||
| 74 | } | 94 | } |
| 75 | 95 | ||
| 76 | Future<void> onSubmitPartnerId() async { | 96 | Future<void> onSubmitPartnerId() async { |
| 97 | + ta.track('enter_doublefeel_add_friend_page', | ||
| 98 | + properties: {'channel_type': channel, 'click_content': '继续'}); | ||
| 77 | if (!canSubmit || isSubmitting.value) return; | 99 | if (!canSubmit || isSubmitting.value) return; |
| 78 | isSubmitting.value = true; | 100 | isSubmitting.value = true; |
| 79 | try { | 101 | try { |
| @@ -120,6 +142,7 @@ class BindPartnerController extends GetxController { | @@ -120,6 +142,7 @@ class BindPartnerController extends GetxController { | ||
| 120 | } else { | 142 | } else { |
| 121 | Get.to(_BindSuccessPage(friendItem)); | 143 | Get.to(_BindSuccessPage(friendItem)); |
| 122 | } | 144 | } |
| 145 | + ta.track('success_add_doubelfeel_friend'); | ||
| 123 | break; | 146 | break; |
| 124 | } | 147 | } |
| 125 | } catch (e) { | 148 | } catch (e) { |
| @@ -129,6 +152,8 @@ class BindPartnerController extends GetxController { | @@ -129,6 +152,8 @@ class BindPartnerController extends GetxController { | ||
| 129 | } | 152 | } |
| 130 | 153 | ||
| 131 | Future<void> onSkip() async { | 154 | Future<void> onSkip() async { |
| 155 | + ta.track('enter_doublefeel_add_friend_page', | ||
| 156 | + properties: {'channel_type': channel, 'click_content': '下次再说'}); | ||
| 132 | final userStateService = Get.find<UserStateService>(); | 157 | final userStateService = Get.find<UserStateService>(); |
| 133 | if (!userStateService.isVip) { | 158 | if (!userStateService.isVip) { |
| 134 | // 使用 offNamed 移除 BIND_PARTNER,并将 onboarding 标识传入 PURCHASE | 159 | // 使用 offNamed 移除 BIND_PARTNER,并将 onboarding 标识传入 PURCHASE |
| 1 | import 'package:doublefeel_flutter/app/widget/dash_divider.dart'; | 1 | import 'package:doublefeel_flutter/app/widget/dash_divider.dart'; |
| 2 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 2 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 3 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 3 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 4 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 4 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
| @@ -198,6 +199,10 @@ class BindPartnerView extends GetView<BindPartnerController> { | @@ -198,6 +199,10 @@ class BindPartnerView extends GetView<BindPartnerController> { | ||
| 198 | ClipboardData(text: controller.myInviteCode.value), | 199 | ClipboardData(text: controller.myInviteCode.value), |
| 199 | ); | 200 | ); |
| 200 | AppToast.show('复制成功'); | 201 | AppToast.show('复制成功'); |
| 202 | + ta.track('enter_doublefeel_add_friend_page', properties: { | ||
| 203 | + 'channel_type': controller.channel, | ||
| 204 | + 'click_content': '复制共享码' | ||
| 205 | + }); | ||
| 201 | }, | 206 | }, |
| 202 | child: Container( | 207 | child: Container( |
| 203 | width: 20, | 208 | width: 20, |
| @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_ | @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_ | ||
| 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/network/api/friend_api.dart'; | 3 | import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; |
| 4 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 4 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 5 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 5 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 6 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 6 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; | 7 | import 'package:doublefeel_flutter/data/local/local_storage.dart'; |
| 7 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; | 8 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; |
| @@ -119,6 +120,14 @@ class HomeController extends GetxController { | @@ -119,6 +120,14 @@ class HomeController extends GetxController { | ||
| 119 | } else if (previousIndex == friendsTabIndex && index != friendsTabIndex) { | 120 | } else if (previousIndex == friendsTabIndex && index != friendsTabIndex) { |
| 120 | Get.find<FriendsController>().markPageHidden(); | 121 | Get.find<FriendsController>().markPageHidden(); |
| 121 | } | 122 | } |
| 123 | + switch (index) { | ||
| 124 | + case 0: | ||
| 125 | + // ta.track('enter_doublefeel_my_page'); | ||
| 126 | + break; | ||
| 127 | + case 3: | ||
| 128 | + ta.track('enter_doublefeel_my_page'); | ||
| 129 | + break; | ||
| 130 | + } | ||
| 122 | } | 131 | } |
| 123 | 132 | ||
| 124 | void openTrend(TrendType type, {DateTime? date, ReportPeriod? period}) { | 133 | void openTrend(TrendType type, {DateTime? date, ReportPeriod? period}) { |
| 1 | import 'dart:async'; | 1 | import 'dart:async'; |
| 2 | +import 'dart:ui'; | ||
| 2 | 3 | ||
| 3 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart'; |
| 4 | import 'package:doublefeel_flutter/app/modules/home/controllers/home_controller.dart'; | 5 | import 'package:doublefeel_flutter/app/modules/home/controllers/home_controller.dart'; |
| @@ -14,6 +15,7 @@ import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; | @@ -14,6 +15,7 @@ import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; | ||
| 14 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; | 15 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; |
| 15 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 16 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 16 | import 'package:doublefeel_flutter/core/services/loading_service.dart'; | 17 | import 'package:doublefeel_flutter/core/services/loading_service.dart'; |
| 18 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 17 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 19 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 18 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 20 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 19 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; | 21 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; |
| @@ -29,6 +31,7 @@ import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; | @@ -29,6 +31,7 @@ import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; | ||
| 29 | import 'package:flutter/material.dart'; | 31 | import 'package:flutter/material.dart'; |
| 30 | import 'package:get/get.dart'; | 32 | import 'package:get/get.dart'; |
| 31 | import 'package:intl/intl.dart'; | 33 | import 'package:intl/intl.dart'; |
| 34 | +import 'package:permission_handler/permission_handler.dart'; | ||
| 32 | 35 | ||
| 33 | /// HRV 趋势数据点 | 36 | /// HRV 趋势数据点 |
| 34 | class HrvDataPoint { | 37 | class HrvDataPoint { |
| @@ -135,6 +138,15 @@ class TodayController extends GetMaterialController { | @@ -135,6 +138,15 @@ class TodayController extends GetMaterialController { | ||
| 135 | final v2ActivityTarget = Rxn<V2ActivityTarget>(); | 138 | final v2ActivityTarget = Rxn<V2ActivityTarget>(); |
| 136 | 139 | ||
| 137 | @override | 140 | @override |
| 141 | + void onReady() { | ||
| 142 | + super.onReady(); | ||
| 143 | + ta.track( | ||
| 144 | + 'enter_doublefeel_today_status', | ||
| 145 | + properties: {'user_role': isFriend ? '好友的' : '我的'}, | ||
| 146 | + ); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + @override | ||
| 138 | void onInit() { | 150 | void onInit() { |
| 139 | super.onInit(); | 151 | super.onInit(); |
| 140 | targetFriendInfo.value = _initialFriendInfo; | 152 | targetFriendInfo.value = _initialFriendInfo; |
| @@ -155,6 +167,7 @@ class TodayController extends GetMaterialController { | @@ -155,6 +167,7 @@ class TodayController extends GetMaterialController { | ||
| 155 | checkAddFriendVisible(); | 167 | checkAddFriendVisible(); |
| 156 | checkHrvAdBannerVisible(); | 168 | checkHrvAdBannerVisible(); |
| 157 | checkHealthDataAuthCardVisible(); | 169 | checkHealthDataAuthCardVisible(); |
| 170 | + checkNotificationCardVisible(); | ||
| 158 | if (!Get.find<UserStateService>().isVip) { | 171 | if (!Get.find<UserStateService>().isVip) { |
| 159 | getProductList(); | 172 | getProductList(); |
| 160 | } | 173 | } |
| @@ -287,6 +300,7 @@ class TodayController extends GetMaterialController { | @@ -287,6 +300,7 @@ class TodayController extends GetMaterialController { | ||
| 287 | } | 300 | } |
| 288 | 301 | ||
| 289 | Future<void> onAuthorizeTap() async { | 302 | Future<void> onAuthorizeTap() async { |
| 303 | + ta.track('click_doublefeel_today_status', properties: {'ita': '授权访问健康数据'}); | ||
| 290 | if (showHealthDataAuthCardStatus.value == 1) { | 304 | if (showHealthDataAuthCardStatus.value == 1) { |
| 291 | return; | 305 | return; |
| 292 | } else if (showHealthDataAuthCardStatus.value == 0) { | 306 | } else if (showHealthDataAuthCardStatus.value == 0) { |
| @@ -322,7 +336,7 @@ class TodayController extends GetMaterialController { | @@ -322,7 +336,7 @@ class TodayController extends GetMaterialController { | ||
| 322 | [HealthAuthorization? preResult]) async { | 336 | [HealthAuthorization? preResult]) async { |
| 323 | try { | 337 | try { |
| 324 | final result = preResult ?? await _hostApi.checkHealthAppAuthorization(); | 338 | final result = preResult ?? await _hostApi.checkHealthAppAuthorization(); |
| 325 | - | 339 | + ta.userSet({'health_data_permission': result.status.clamp(0, 1)}); |
| 326 | if (result.status == 1) { | 340 | if (result.status == 1) { |
| 327 | await _performDataUpload(); | 341 | await _performDataUpload(); |
| 328 | } | 342 | } |
| @@ -335,6 +349,26 @@ class TodayController extends GetMaterialController { | @@ -335,6 +349,26 @@ class TodayController extends GetMaterialController { | ||
| 335 | } catch (e) {} | 349 | } catch (e) {} |
| 336 | } | 350 | } |
| 337 | 351 | ||
| 352 | + Future<void> checkNotificationCardVisible() async { | ||
| 353 | + try { | ||
| 354 | + Permission.notification.status.then((status) { | ||
| 355 | + ta.userSet({'notification_permission': status.isGranted ? 1 : 0}); | ||
| 356 | + // if (status.isGranted) { | ||
| 357 | + // return; | ||
| 358 | + // } | ||
| 359 | + | ||
| 360 | + // if (status.isPermanentlyDenied) { | ||
| 361 | + // // await openAppSettings(); | ||
| 362 | + // return; | ||
| 363 | + // } | ||
| 364 | + | ||
| 365 | + // final result = await Permission.notification.request(); | ||
| 366 | + }); | ||
| 367 | + } catch (error) { | ||
| 368 | + AppLogger.e('Notification authorization failed: $error'); | ||
| 369 | + } | ||
| 370 | + } | ||
| 371 | + | ||
| 338 | Future<void> _refreshHealthDataForDate(DateTime date) async { | 372 | Future<void> _refreshHealthDataForDate(DateTime date) async { |
| 339 | _clearRealTimeData(); | 373 | _clearRealTimeData(); |
| 340 | final intDate = int.parse(DateFormat('yyyyMMdd').format(date)); | 374 | final intDate = int.parse(DateFormat('yyyyMMdd').format(date)); |
| @@ -4,6 +4,7 @@ import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measuremen | @@ -4,6 +4,7 @@ import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measuremen | ||
| 4 | import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; |
| 5 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_bottom_slogan.dart'; | 5 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_bottom_slogan.dart'; |
| 6 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; | 6 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; |
| 7 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 7 | 8 | ||
| 8 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 9 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 9 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 10 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| @@ -99,6 +100,8 @@ class TodayTabBody extends StatelessWidget { | @@ -99,6 +100,8 @@ class TodayTabBody extends StatelessWidget { | ||
| 99 | ), | 100 | ), |
| 100 | onBackToToday: _backToToday, | 101 | onBackToToday: _backToToday, |
| 101 | onTapPremiumCard: () { | 102 | onTapPremiumCard: () { |
| 103 | + ta.track('click_doublefeel_today_status', | ||
| 104 | + properties: {'ita': '右上角VIP优惠入口'}); | ||
| 102 | controller.toPremiumDiscoutPage(); | 105 | controller.toPremiumDiscoutPage(); |
| 103 | }, | 106 | }, |
| 104 | ), | 107 | ), |
| @@ -227,7 +230,11 @@ class TodayTabBody extends StatelessWidget { | @@ -227,7 +230,11 @@ class TodayTabBody extends StatelessWidget { | ||
| 227 | return Get.find<UserStateService>().isVip | 230 | return Get.find<UserStateService>().isVip |
| 228 | ? const SizedBox.shrink() | 231 | ? const SizedBox.shrink() |
| 229 | : GestureDetector( | 232 | : GestureDetector( |
| 230 | - onTap: () => controller.toPremiumDiscoutPage(), | 233 | + onTap: () { |
| 234 | + ta.track('click_doublefeel_today_status', | ||
| 235 | + properties: {'ita': '会员banner'}); | ||
| 236 | + controller.toPremiumDiscoutPage(); | ||
| 237 | + }, | ||
| 231 | child: PremiumCard( | 238 | child: PremiumCard( |
| 232 | controller, | 239 | controller, |
| 233 | )); | 240 | )); |
| @@ -674,6 +681,8 @@ class _TopFriendBar extends StatelessWidget { | @@ -674,6 +681,8 @@ class _TopFriendBar extends StatelessWidget { | ||
| 674 | highlightColor: Colors.transparent, | 681 | highlightColor: Colors.transparent, |
| 675 | padding: EdgeInsets.zero, | 682 | padding: EdgeInsets.zero, |
| 676 | onPressed: () { | 683 | onPressed: () { |
| 684 | + ta.track('click_doublefeel_today_status', | ||
| 685 | + properties: {'ita': '切换好友'}); | ||
| 677 | controller.showFriendListBottomSheet(); | 686 | controller.showFriendListBottomSheet(); |
| 678 | }, | 687 | }, |
| 679 | icon: Image.asset( | 688 | icon: Image.asset( |
| 1 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 1 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 2 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 2 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 3 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 3 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
| 4 | import 'package:get/get.dart'; | 5 | import 'package:get/get.dart'; |
| 5 | 6 | ||
| 6 | void showHrvMeasurementBottomSheet() { | 7 | void showHrvMeasurementBottomSheet() { |
| 8 | + ta.track('click_doublefeel_today_status', properties: {'ita': '立刻测量HRV'}); | ||
| 7 | Get.bottomSheet( | 9 | Get.bottomSheet( |
| 8 | const FractionallySizedBox( | 10 | const FractionallySizedBox( |
| 9 | heightFactor: 0.9, | 11 | heightFactor: 0.9, |
| 1 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | 1 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; |
| 2 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 2 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 3 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 3 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 4 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 4 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
| @@ -18,6 +19,8 @@ class TodayHrvAdBanner extends StatelessWidget { | @@ -18,6 +19,8 @@ class TodayHrvAdBanner extends StatelessWidget { | ||
| 18 | 19 | ||
| 19 | return GestureDetector( | 20 | return GestureDetector( |
| 20 | onTap: () async { | 21 | onTap: () async { |
| 22 | + ta.track('click_doublefeel_today_status', | ||
| 23 | + properties: {'ita': '点击添加HRV表盘'}); | ||
| 21 | await Get.toNamed(Routes.WATCH_THEME); | 24 | await Get.toNamed(Routes.WATCH_THEME); |
| 22 | controller.checkAddFriendVisible(); | 25 | controller.checkAddFriendVisible(); |
| 23 | }, | 26 | }, |
| @@ -61,7 +64,9 @@ class TodayPartnerAdBanner extends StatelessWidget { | @@ -61,7 +64,9 @@ class TodayPartnerAdBanner extends StatelessWidget { | ||
| 61 | 64 | ||
| 62 | return GestureDetector( | 65 | return GestureDetector( |
| 63 | onTap: () async { | 66 | onTap: () async { |
| 64 | - await Get.toNamed(AppRoutes.bindPartner); | 67 | + ta.track('click_doublefeel_today_status', properties: {'ita': '加好友'}); |
| 68 | + await Get.toNamed(AppRoutes.bindPartner, | ||
| 69 | + arguments: {'from': 'today'}); | ||
| 65 | controller.checkAddFriendVisible(); | 70 | controller.checkAddFriendVisible(); |
| 66 | }, | 71 | }, |
| 67 | child: _TodayGuideBanner( | 72 | child: _TodayGuideBanner( |
| @@ -34,8 +34,7 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -34,8 +34,7 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 34 | label: controller.isFriend | 34 | label: controller.isFriend |
| 35 | ? context.l10n.reportOtherPossessiveTitle(otherHrv) | 35 | ? context.l10n.reportOtherPossessiveTitle(otherHrv) |
| 36 | : otherHrv, | 36 | : otherHrv, |
| 37 | - value: | ||
| 38 | - controller.v2HealthData.value?.hrvAvg?.toString() ?? '--', | 37 | + value: controller.v2HealthData.value?.hrvAvg?.toString() ?? '-', |
| 39 | unit: 'ms', | 38 | unit: 'ms', |
| 40 | ), | 39 | ), |
| 41 | ), | 40 | ), |
| @@ -48,7 +47,7 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -48,7 +47,7 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 48 | : context.l10n.restingHeartRate, | 47 | : context.l10n.restingHeartRate, |
| 49 | value: controller.v2HealthData.value?.lastRestingHrValue | 48 | value: controller.v2HealthData.value?.lastRestingHrValue |
| 50 | ?.toString() ?? | 49 | ?.toString() ?? |
| 51 | - '--', | 50 | + '-', |
| 52 | unit: 'bpm', | 51 | unit: 'bpm', |
| 53 | ), | 52 | ), |
| 54 | ), | 53 | ), |
| @@ -13,6 +13,7 @@ class TodaySleepCard extends StatelessWidget { | @@ -13,6 +13,7 @@ class TodaySleepCard extends StatelessWidget { | ||
| 13 | 13 | ||
| 14 | @override | 14 | @override |
| 15 | Widget build(BuildContext context) { | 15 | Widget build(BuildContext context) { |
| 16 | + final l10n = context.l10n; | ||
| 16 | return GestureDetector( | 17 | return GestureDetector( |
| 17 | onTap: controller.toTrendSleepPage, | 18 | onTap: controller.toTrendSleepPage, |
| 18 | child: Obx(() { | 19 | child: Obx(() { |
| @@ -25,10 +26,10 @@ class TodaySleepCard extends StatelessWidget { | @@ -25,10 +26,10 @@ class TodaySleepCard extends StatelessWidget { | ||
| 25 | ? context.colors.textPrimary | 26 | ? context.colors.textPrimary |
| 26 | : healthData?.sleepStateColor(context); | 27 | : healthData?.sleepStateColor(context); |
| 27 | String sleepStateStr = | 28 | String sleepStateStr = |
| 28 | - sleepDuration == 0 ? '--' : healthData?.sleepStateStr() ?? '--'; | 29 | + sleepDuration == 0 ? '-' : healthData?.sleepStateStr() ?? '-'; |
| 29 | String sleepAverageHeartRate = | 30 | String sleepAverageHeartRate = |
| 30 | (healthData?.hrAvg == null || healthData?.hrAvg == 0) | 31 | (healthData?.hrAvg == null || healthData?.hrAvg == 0) |
| 31 | - ? '--' | 32 | + ? '-' |
| 32 | : '${healthData?.hrAvg}'; | 33 | : '${healthData?.hrAvg}'; |
| 33 | double sleepDurationProgress = sleepDuration == 0 | 34 | double sleepDurationProgress = sleepDuration == 0 |
| 34 | ? -1 | 35 | ? -1 |
| @@ -39,36 +40,103 @@ class TodaySleepCard extends StatelessWidget { | @@ -39,36 +40,103 @@ class TodaySleepCard extends StatelessWidget { | ||
| 39 | return _TodaySummaryCard( | 40 | return _TodaySummaryCard( |
| 40 | iconAsset: 'assets/images/common/ic_sleep_stroke.png', | 41 | iconAsset: 'assets/images/common/ic_sleep_stroke.png', |
| 41 | iconColor: context.colors.primary, | 42 | iconColor: context.colors.primary, |
| 42 | - title: context.l10n.sleep, | ||
| 43 | - actionText: context.l10n.viewSleepReport, | 43 | + title: l10n.sleep, |
| 44 | + actionText: l10n.viewSleepReport, | ||
| 44 | metrics: [ | 45 | metrics: [ |
| 45 | - _MetricData( | ||
| 46 | - label: context.l10n.duration, | ||
| 47 | - labelColor: context.colors.primary, | ||
| 48 | - valueSpans: [ | ||
| 49 | - _MetricValueSpan( | ||
| 50 | - sleepHours > 0 ? '$sleepHours' : '--', l10n.reportUnitHour), | ||
| 51 | - _MetricValueSpan(sleepMinutes > 0 ? '$sleepMinutes' : '--', | ||
| 52 | - l10n.reportUnitMinute), | ||
| 53 | - ], | ||
| 54 | - ), | ||
| 55 | - _MetricData( | ||
| 56 | - label: context.l10n.quality, | 46 | + _MetricBlock( |
| 47 | + label: l10n.duration, | ||
| 48 | + labelColor: context.colors.primary, | ||
| 49 | + value: (sleepDuration > 0) | ||
| 50 | + ? Text.rich( | ||
| 51 | + TextSpan( | ||
| 52 | + style: TextStyle( | ||
| 53 | + color: context.colors.textPrimary, | ||
| 54 | + fontSize: 16, | ||
| 55 | + fontWeight: FontWeight.w600, | ||
| 56 | + height: 19 / 16, | ||
| 57 | + ), | ||
| 58 | + children: [ | ||
| 59 | + if (sleepHours > 0) ...[ | ||
| 60 | + TextSpan(text: '$sleepHours'), | ||
| 61 | + TextSpan( | ||
| 62 | + text: ' ${l10n.reportUnitHour} ', | ||
| 63 | + style: TextStyle( | ||
| 64 | + color: context.colors.textSecondary, | ||
| 65 | + fontSize: 12, | ||
| 66 | + fontWeight: FontWeight.w400, | ||
| 67 | + ), | ||
| 68 | + ) | ||
| 69 | + ], | ||
| 70 | + if (sleepMinutes > 0) ...[ | ||
| 71 | + TextSpan(text: '$sleepMinutes'), | ||
| 72 | + TextSpan( | ||
| 73 | + text: ' ${l10n.reportUnitMinute}', | ||
| 74 | + style: TextStyle( | ||
| 75 | + color: context.colors.textSecondary, | ||
| 76 | + fontSize: 12, | ||
| 77 | + fontWeight: FontWeight.w400, | ||
| 78 | + ), | ||
| 79 | + ), | ||
| 80 | + ] | ||
| 81 | + ], | ||
| 82 | + ), | ||
| 83 | + ) | ||
| 84 | + : Text( | ||
| 85 | + '-', | ||
| 86 | + style: TextStyle( | ||
| 87 | + color: context.colors.textPrimary, | ||
| 88 | + fontSize: 16, | ||
| 89 | + fontWeight: FontWeight.w600, | ||
| 90 | + height: 19 / 16, | ||
| 91 | + ), | ||
| 92 | + )), | ||
| 93 | + _MetricBlock( | ||
| 94 | + label: l10n.quality, | ||
| 57 | labelColor: const Color(0xFF7B9BFB), | 95 | labelColor: const Color(0xFF7B9BFB), |
| 58 | - valueSpans: [ | ||
| 59 | - _MetricValueSpan( | ||
| 60 | - sleepStateStr, | ||
| 61 | - '', | ||
| 62 | - valueColor: sleepStateColor, | 96 | + value: Text( |
| 97 | + sleepStateStr, | ||
| 98 | + style: TextStyle( | ||
| 99 | + color: sleepStateColor, | ||
| 100 | + fontSize: 16, | ||
| 101 | + fontWeight: FontWeight.w600, | ||
| 102 | + height: 19 / 16, | ||
| 63 | ), | 103 | ), |
| 64 | - ], | 104 | + ), |
| 65 | ), | 105 | ), |
| 66 | - _MetricData( | ||
| 67 | - label: context.l10n.averageHeartRate, | 106 | + _MetricBlock( |
| 107 | + label: l10n.averageHeartRate, | ||
| 68 | labelColor: context.colors.textSecondary, | 108 | labelColor: context.colors.textSecondary, |
| 69 | - valueSpans: [ | ||
| 70 | - _MetricValueSpan(sleepAverageHeartRate, 'bpm'), | ||
| 71 | - ], | 109 | + value: sleepDuration > 0 && ((healthData?.hrAvg ?? 0) >= 0) |
| 110 | + ? Text.rich( | ||
| 111 | + TextSpan( | ||
| 112 | + style: TextStyle( | ||
| 113 | + color: context.colors.textPrimary, | ||
| 114 | + fontSize: 16, | ||
| 115 | + fontWeight: FontWeight.w600, | ||
| 116 | + height: 19 / 16, | ||
| 117 | + ), | ||
| 118 | + children: [ | ||
| 119 | + TextSpan(text: sleepAverageHeartRate), | ||
| 120 | + TextSpan( | ||
| 121 | + text: ' bpm', | ||
| 122 | + style: TextStyle( | ||
| 123 | + color: context.colors.textSecondary, | ||
| 124 | + fontSize: 12, | ||
| 125 | + fontWeight: FontWeight.w400, | ||
| 126 | + ), | ||
| 127 | + ), | ||
| 128 | + ], | ||
| 129 | + ), | ||
| 130 | + ) | ||
| 131 | + : Text( | ||
| 132 | + '-', | ||
| 133 | + style: TextStyle( | ||
| 134 | + color: context.colors.textPrimary, | ||
| 135 | + fontSize: 16, | ||
| 136 | + fontWeight: FontWeight.w600, | ||
| 137 | + height: 19 / 16, | ||
| 138 | + ), | ||
| 139 | + ), | ||
| 72 | ), | 140 | ), |
| 73 | ], | 141 | ], |
| 74 | rightWidget: Container( | 142 | rightWidget: Container( |
| @@ -104,6 +172,7 @@ class TodayActivityCard extends StatelessWidget { | @@ -104,6 +172,7 @@ class TodayActivityCard extends StatelessWidget { | ||
| 104 | 172 | ||
| 105 | @override | 173 | @override |
| 106 | Widget build(BuildContext context) { | 174 | Widget build(BuildContext context) { |
| 175 | + final l10n = context.l10n; | ||
| 107 | return GestureDetector( | 176 | return GestureDetector( |
| 108 | onTap: controller.toTrendActivityPage, | 177 | onTap: controller.toTrendActivityPage, |
| 109 | child: Obx(() { | 178 | child: Obx(() { |
| @@ -115,30 +184,100 @@ class TodayActivityCard extends StatelessWidget { | @@ -115,30 +184,100 @@ class TodayActivityCard extends StatelessWidget { | ||
| 115 | 184 | ||
| 116 | var activityStr = (activity > 0 && (activityTarget?.move ?? 0) > 0) | 185 | var activityStr = (activity > 0 && (activityTarget?.move ?? 0) > 0) |
| 117 | ? '$activity' | 186 | ? '$activity' |
| 118 | - : '--'; | 187 | + : '-'; |
| 119 | // exercise 单位为秒,转换为小时+分钟 | 188 | // exercise 单位为秒,转换为小时+分钟 |
| 120 | - List<_MetricValueSpan> exerciseSpans; | 189 | + Widget exerciseWidget; |
| 121 | if (exercise > 0 && (activityTarget?.exercise ?? 0) > 0) { | 190 | if (exercise > 0 && (activityTarget?.exercise ?? 0) > 0) { |
| 122 | final exHours = exercise ~/ 3600; | 191 | final exHours = exercise ~/ 3600; |
| 123 | final exMinutes = (exercise % 3600) ~/ 60; | 192 | final exMinutes = (exercise % 3600) ~/ 60; |
| 124 | - exerciseSpans = [ | ||
| 125 | - if (exHours > 0) _MetricValueSpan('$exHours', l10n.reportUnitHour), | ||
| 126 | - if (exMinutes > 0 || exHours == 0) | ||
| 127 | - _MetricValueSpan( | ||
| 128 | - exMinutes > 0 ? '$exMinutes' : '0', l10n.reportUnitMinute), | ||
| 129 | - ]; | 193 | + exerciseWidget = Text.rich( |
| 194 | + TextSpan( | ||
| 195 | + style: TextStyle( | ||
| 196 | + color: context.colors.textPrimary, | ||
| 197 | + fontSize: 16, | ||
| 198 | + fontWeight: FontWeight.w600, | ||
| 199 | + height: 19 / 16, | ||
| 200 | + ), | ||
| 201 | + children: [ | ||
| 202 | + if (exHours > 0) ...[ | ||
| 203 | + TextSpan(text: '$exHours'), | ||
| 204 | + TextSpan( | ||
| 205 | + text: ' ${l10n.reportUnitHour} ', | ||
| 206 | + style: TextStyle( | ||
| 207 | + color: context.colors.textSecondary, | ||
| 208 | + fontSize: 12, | ||
| 209 | + fontWeight: FontWeight.w400, | ||
| 210 | + ), | ||
| 211 | + ), | ||
| 212 | + ], | ||
| 213 | + if (exMinutes > 0 || exHours == 0) ...[ | ||
| 214 | + TextSpan(text: exMinutes > 0 ? '$exMinutes' : '0'), | ||
| 215 | + TextSpan( | ||
| 216 | + text: ' ${l10n.reportUnitMinute}', | ||
| 217 | + style: TextStyle( | ||
| 218 | + color: context.colors.textSecondary, | ||
| 219 | + fontSize: 12, | ||
| 220 | + fontWeight: FontWeight.w400, | ||
| 221 | + ), | ||
| 222 | + ), | ||
| 223 | + ], | ||
| 224 | + ], | ||
| 225 | + ), | ||
| 226 | + ); | ||
| 130 | } else { | 227 | } else { |
| 131 | - exerciseSpans = [_MetricValueSpan('--', l10n.reportUnitMinute)]; | 228 | + exerciseWidget = Text.rich( |
| 229 | + TextSpan( | ||
| 230 | + style: TextStyle( | ||
| 231 | + color: context.colors.textPrimary, | ||
| 232 | + fontSize: 16, | ||
| 233 | + fontWeight: FontWeight.w600, | ||
| 234 | + height: 19 / 16, | ||
| 235 | + ), | ||
| 236 | + children: [ | ||
| 237 | + const TextSpan(text: '-'), | ||
| 238 | + ], | ||
| 239 | + ), | ||
| 240 | + ); | ||
| 132 | } | 241 | } |
| 133 | 242 | ||
| 134 | // stand 单位为秒,转换为小时+分钟 | 243 | // stand 单位为秒,转换为小时+分钟 |
| 135 | - List<_MetricValueSpan> standSpans; | 244 | + Widget standWidget; |
| 136 | if (stand > 0 && (activityTarget?.stand ?? 0) > 0) { | 245 | if (stand > 0 && (activityTarget?.stand ?? 0) > 0) { |
| 137 | - standSpans = [ | ||
| 138 | - _MetricValueSpan('$stand', l10n.reportUnitHour), | ||
| 139 | - ]; | 246 | + standWidget = Text.rich( |
| 247 | + TextSpan( | ||
| 248 | + style: TextStyle( | ||
| 249 | + color: context.colors.textPrimary, | ||
| 250 | + fontSize: 16, | ||
| 251 | + fontWeight: FontWeight.w600, | ||
| 252 | + height: 19 / 16, | ||
| 253 | + ), | ||
| 254 | + children: [ | ||
| 255 | + TextSpan(text: '$stand'), | ||
| 256 | + TextSpan( | ||
| 257 | + text: ' ${l10n.reportUnitHour}', | ||
| 258 | + style: TextStyle( | ||
| 259 | + color: context.colors.textSecondary, | ||
| 260 | + fontSize: 12, | ||
| 261 | + fontWeight: FontWeight.w400, | ||
| 262 | + ), | ||
| 263 | + ), | ||
| 264 | + ], | ||
| 265 | + ), | ||
| 266 | + ); | ||
| 140 | } else { | 267 | } else { |
| 141 | - standSpans = [_MetricValueSpan('--', l10n.reportUnitHour)]; | 268 | + standWidget = Text.rich( |
| 269 | + TextSpan( | ||
| 270 | + style: TextStyle( | ||
| 271 | + color: context.colors.textPrimary, | ||
| 272 | + fontSize: 16, | ||
| 273 | + fontWeight: FontWeight.w600, | ||
| 274 | + height: 19 / 16, | ||
| 275 | + ), | ||
| 276 | + children: [ | ||
| 277 | + const TextSpan(text: '-'), | ||
| 278 | + ], | ||
| 279 | + ), | ||
| 280 | + ); | ||
| 142 | } | 281 | } |
| 143 | double activityProgress = | 282 | double activityProgress = |
| 144 | (activity > 0 && (activityTarget?.move ?? 0) > 0) | 283 | (activity > 0 && (activityTarget?.move ?? 0) > 0) |
| @@ -155,25 +294,43 @@ class TodayActivityCard extends StatelessWidget { | @@ -155,25 +294,43 @@ class TodayActivityCard extends StatelessWidget { | ||
| 155 | return _TodaySummaryCard( | 294 | return _TodaySummaryCard( |
| 156 | iconAsset: 'assets/images/common/ic_exercise.png', | 295 | iconAsset: 'assets/images/common/ic_exercise.png', |
| 157 | iconColor: context.colors.warning, | 296 | iconColor: context.colors.warning, |
| 158 | - title: context.l10n.fitness, | ||
| 159 | - actionText: context.l10n.viewFitnessReport, | 297 | + title: l10n.fitness, |
| 298 | + actionText: l10n.viewFitnessReport, | ||
| 160 | metrics: [ | 299 | metrics: [ |
| 161 | - _MetricData( | ||
| 162 | - label: context.l10n.event, | 300 | + _MetricBlock( |
| 301 | + label: l10n.event, | ||
| 163 | labelColor: context.colors.warning, | 302 | labelColor: context.colors.warning, |
| 164 | - valueSpans: [ | ||
| 165 | - _MetricValueSpan(activityStr, l10n.reportUnitKcal), | ||
| 166 | - ], | 303 | + value: Text.rich( |
| 304 | + TextSpan( | ||
| 305 | + style: TextStyle( | ||
| 306 | + color: context.colors.textPrimary, | ||
| 307 | + fontSize: 16, | ||
| 308 | + fontWeight: FontWeight.w600, | ||
| 309 | + height: 19 / 16, | ||
| 310 | + ), | ||
| 311 | + children: [ | ||
| 312 | + TextSpan(text: activityStr), | ||
| 313 | + TextSpan( | ||
| 314 | + text: ' ${l10n.reportUnitKcal}', | ||
| 315 | + style: TextStyle( | ||
| 316 | + color: context.colors.textSecondary, | ||
| 317 | + fontSize: 12, | ||
| 318 | + fontWeight: FontWeight.w400, | ||
| 319 | + ), | ||
| 320 | + ), | ||
| 321 | + ], | ||
| 322 | + ), | ||
| 323 | + ), | ||
| 167 | ), | 324 | ), |
| 168 | - _MetricData( | ||
| 169 | - label: context.l10n.exercise, | 325 | + _MetricBlock( |
| 326 | + label: l10n.exercise, | ||
| 170 | labelColor: context.colors.chartGreen, | 327 | labelColor: context.colors.chartGreen, |
| 171 | - valueSpans: exerciseSpans, | 328 | + value: exerciseWidget, |
| 172 | ), | 329 | ), |
| 173 | - _MetricData( | ||
| 174 | - label: context.l10n.standing, | 330 | + _MetricBlock( |
| 331 | + label: l10n.standing, | ||
| 175 | labelColor: const Color(0xFF7B9BFB), | 332 | labelColor: const Color(0xFF7B9BFB), |
| 176 | - valueSpans: standSpans, | 333 | + value: standWidget, |
| 177 | ), | 334 | ), |
| 178 | ], | 335 | ], |
| 179 | rightWidget: Container( | 336 | rightWidget: Container( |
| @@ -238,7 +395,7 @@ class _TodaySummaryCard extends StatelessWidget { | @@ -238,7 +395,7 @@ class _TodaySummaryCard extends StatelessWidget { | ||
| 238 | final Color iconColor; | 395 | final Color iconColor; |
| 239 | final String title; | 396 | final String title; |
| 240 | final String actionText; | 397 | final String actionText; |
| 241 | - final List<_MetricData> metrics; | 398 | + final List<Widget> metrics; |
| 242 | 399 | ||
| 243 | final Widget rightWidget; | 400 | final Widget rightWidget; |
| 244 | 401 | ||
| @@ -299,8 +456,7 @@ class _TodaySummaryCard extends StatelessWidget { | @@ -299,8 +456,7 @@ class _TodaySummaryCard extends StatelessWidget { | ||
| 299 | child: Row( | 456 | child: Row( |
| 300 | crossAxisAlignment: CrossAxisAlignment.start, | 457 | crossAxisAlignment: CrossAxisAlignment.start, |
| 301 | children: [ | 458 | children: [ |
| 302 | - for (final metric in metrics) | ||
| 303 | - Expanded(child: _MetricBlock(metric: metric)), | 459 | + for (final metric in metrics) Expanded(child: metric), |
| 304 | ], | 460 | ], |
| 305 | ), | 461 | ), |
| 306 | ), | 462 | ), |
| @@ -314,9 +470,15 @@ class _TodaySummaryCard extends StatelessWidget { | @@ -314,9 +470,15 @@ class _TodaySummaryCard extends StatelessWidget { | ||
| 314 | } | 470 | } |
| 315 | 471 | ||
| 316 | class _MetricBlock extends StatelessWidget { | 472 | class _MetricBlock extends StatelessWidget { |
| 317 | - const _MetricBlock({required this.metric}); | 473 | + const _MetricBlock({ |
| 474 | + required this.label, | ||
| 475 | + required this.labelColor, | ||
| 476 | + required this.value, | ||
| 477 | + }); | ||
| 318 | 478 | ||
| 319 | - final _MetricData metric; | 479 | + final String label; |
| 480 | + final Color labelColor; | ||
| 481 | + final Widget value; | ||
| 320 | 482 | ||
| 321 | @override | 483 | @override |
| 322 | Widget build(BuildContext context) { | 484 | Widget build(BuildContext context) { |
| @@ -326,11 +488,11 @@ class _MetricBlock extends StatelessWidget { | @@ -326,11 +488,11 @@ class _MetricBlock extends StatelessWidget { | ||
| 326 | crossAxisAlignment: CrossAxisAlignment.start, | 488 | crossAxisAlignment: CrossAxisAlignment.start, |
| 327 | children: [ | 489 | children: [ |
| 328 | Text( | 490 | Text( |
| 329 | - metric.label, | 491 | + label, |
| 330 | maxLines: 1, | 492 | maxLines: 1, |
| 331 | overflow: TextOverflow.ellipsis, | 493 | overflow: TextOverflow.ellipsis, |
| 332 | style: TextStyle( | 494 | style: TextStyle( |
| 333 | - color: metric.labelColor, | 495 | + color: labelColor, |
| 334 | fontSize: 12, | 496 | fontSize: 12, |
| 335 | fontWeight: FontWeight.w500, | 497 | fontWeight: FontWeight.w500, |
| 336 | height: 17 / 12, | 498 | height: 17 / 12, |
| @@ -340,78 +502,10 @@ class _MetricBlock extends StatelessWidget { | @@ -340,78 +502,10 @@ class _MetricBlock extends StatelessWidget { | ||
| 340 | FittedBox( | 502 | FittedBox( |
| 341 | fit: BoxFit.scaleDown, | 503 | fit: BoxFit.scaleDown, |
| 342 | alignment: Alignment.centerLeft, | 504 | alignment: Alignment.centerLeft, |
| 343 | - child: Row( | ||
| 344 | - crossAxisAlignment: CrossAxisAlignment.end, | ||
| 345 | - children: [ | ||
| 346 | - for (final span in metric.valueSpans) _MetricValue(value: span), | ||
| 347 | - ], | ||
| 348 | - ), | 505 | + child: value, |
| 349 | ), | 506 | ), |
| 350 | ], | 507 | ], |
| 351 | ), | 508 | ), |
| 352 | ); | 509 | ); |
| 353 | } | 510 | } |
| 354 | } | 511 | } |
| 355 | - | ||
| 356 | -class _MetricValue extends StatelessWidget { | ||
| 357 | - const _MetricValue({required this.value}); | ||
| 358 | - | ||
| 359 | - final _MetricValueSpan value; | ||
| 360 | - | ||
| 361 | - @override | ||
| 362 | - Widget build(BuildContext context) { | ||
| 363 | - return Row( | ||
| 364 | - mainAxisSize: MainAxisSize.min, | ||
| 365 | - crossAxisAlignment: CrossAxisAlignment.end, | ||
| 366 | - children: [ | ||
| 367 | - Text( | ||
| 368 | - value.value, | ||
| 369 | - style: TextStyle( | ||
| 370 | - color: value.valueColor ?? context.colors.textPrimary, | ||
| 371 | - fontSize: 16, | ||
| 372 | - fontWeight: FontWeight.w600, | ||
| 373 | - height: 19 / 16, | ||
| 374 | - ), | ||
| 375 | - ), | ||
| 376 | - if (value.unit.isNotEmpty) | ||
| 377 | - Padding( | ||
| 378 | - padding: const EdgeInsets.only(left: 1, bottom: 1), | ||
| 379 | - child: Text( | ||
| 380 | - value.unit, | ||
| 381 | - style: TextStyle( | ||
| 382 | - color: context.colors.textSecondary, | ||
| 383 | - fontSize: 12, | ||
| 384 | - fontWeight: FontWeight.w400, | ||
| 385 | - height: 17 / 12, | ||
| 386 | - ), | ||
| 387 | - ), | ||
| 388 | - ), | ||
| 389 | - if (value.unit.isNotEmpty) const SizedBox(width: 4), | ||
| 390 | - ], | ||
| 391 | - ); | ||
| 392 | - } | ||
| 393 | -} | ||
| 394 | - | ||
| 395 | -class _MetricData { | ||
| 396 | - const _MetricData({ | ||
| 397 | - required this.label, | ||
| 398 | - required this.labelColor, | ||
| 399 | - required this.valueSpans, | ||
| 400 | - }); | ||
| 401 | - | ||
| 402 | - final String label; | ||
| 403 | - final Color labelColor; | ||
| 404 | - final List<_MetricValueSpan> valueSpans; | ||
| 405 | -} | ||
| 406 | - | ||
| 407 | -class _MetricValueSpan { | ||
| 408 | - const _MetricValueSpan( | ||
| 409 | - this.value, | ||
| 410 | - this.unit, { | ||
| 411 | - this.valueColor, | ||
| 412 | - }); | ||
| 413 | - | ||
| 414 | - final String value; | ||
| 415 | - final String unit; | ||
| 416 | - final Color? valueColor; | ||
| 417 | -} |
| @@ -57,6 +57,7 @@ class LoginController extends GetxController { | @@ -57,6 +57,7 @@ class LoginController extends GetxController { | ||
| 57 | AppToast.show(AppLocalizations.of(Get.context!)!.loginAgreeToTermsToast); | 57 | AppToast.show(AppLocalizations.of(Get.context!)!.loginAgreeToTermsToast); |
| 58 | return; | 58 | return; |
| 59 | } | 59 | } |
| 60 | + ta.track('enter_doublefeel_telephone_login_page'); | ||
| 60 | Get.toNamed(AppRoutes.phoneLogin); | 61 | Get.toNamed(AppRoutes.phoneLogin); |
| 61 | } | 62 | } |
| 62 | 63 | ||
| @@ -227,6 +228,7 @@ class LoginController extends GetxController { | @@ -227,6 +228,7 @@ class LoginController extends GetxController { | ||
| 227 | String accessToken = loginData.accessToken; | 228 | String accessToken = loginData.accessToken; |
| 228 | 229 | ||
| 229 | if (isRegister) { | 230 | if (isRegister) { |
| 231 | + ta.track('success_regist'); | ||
| 230 | final regRes = await _userApi.register(isApple | 232 | final regRes = await _userApi.register(isApple |
| 231 | ? { | 233 | ? { |
| 232 | 'login_type': 'apple', | 234 | 'login_type': 'apple', |
| @@ -172,8 +172,8 @@ class PhoneLoginView extends GetView<LoginController> { | @@ -172,8 +172,8 @@ class PhoneLoginView extends GetView<LoginController> { | ||
| 172 | return TextField( | 172 | return TextField( |
| 173 | controller: controller.codeController, | 173 | controller: controller.codeController, |
| 174 | focusNode: controller.codeFocusNode, | 174 | focusNode: controller.codeFocusNode, |
| 175 | - keyboardType: TextInputType.text, | ||
| 176 | - maxLength: 8, | 175 | + keyboardType: TextInputType.phone, |
| 176 | + maxLength: 6, | ||
| 177 | style: TextStyle( | 177 | style: TextStyle( |
| 178 | color: context.colors.textPrimary, | 178 | color: context.colors.textPrimary, |
| 179 | fontSize: 16, | 179 | fontSize: 16, |
| @@ -5,6 +5,7 @@ import 'package:doublefeel_flutter/core/logging/app_logger.dart'; | @@ -5,6 +5,7 @@ import 'package:doublefeel_flutter/core/logging/app_logger.dart'; | ||
| 5 | import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; | 5 | import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; |
| 6 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; | 6 | import 'package:doublefeel_flutter/core/network/api/vip_api.dart'; |
| 7 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 7 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 8 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 8 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 9 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 9 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | 10 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; |
| 10 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | 11 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; |
| @@ -214,4 +215,10 @@ class MembershipOfferController extends GetxController { | @@ -214,4 +215,10 @@ class MembershipOfferController extends GetxController { | ||
| 214 | Get.back(); | 215 | Get.back(); |
| 215 | } | 216 | } |
| 216 | } | 217 | } |
| 218 | + | ||
| 219 | + @override | ||
| 220 | + void onReady() { | ||
| 221 | + super.onReady(); | ||
| 222 | + ta.track('enter_doublefeel_vip_discount_page'); | ||
| 223 | + } | ||
| 217 | } | 224 | } |
| @@ -45,7 +45,7 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | @@ -45,7 +45,7 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | ||
| 45 | 0, | 45 | 0, |
| 46 | MediaQuery.paddingOf(context).top + 4, | 46 | MediaQuery.paddingOf(context).top + 4, |
| 47 | 0, | 47 | 0, |
| 48 | - 140, | 48 | + 0, |
| 49 | ), | 49 | ), |
| 50 | child: Stack( | 50 | child: Stack( |
| 51 | children: [ | 51 | children: [ |
| 1 | import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; | 1 | import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; |
| 2 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; | 2 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; |
| 3 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | 3 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; |
| 4 | +import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; | ||
| 4 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 5 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 5 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; | 6 | import 'package:doublefeel_flutter/data/local/user_account_storage.dart'; |
| 6 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | 7 | import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; |
| @@ -119,6 +120,11 @@ class UserOnboardingController extends GetxController { | @@ -119,6 +120,11 @@ class UserOnboardingController extends GetxController { | ||
| 119 | report(); | 120 | report(); |
| 120 | } on Exception catch (e) {} | 121 | } on Exception catch (e) {} |
| 121 | currentPageIndex.value++; | 122 | currentPageIndex.value++; |
| 123 | + if (currentPageIndex.value == 7) { | ||
| 124 | + ta.track('enter_doublefeel_health_grant_page'); | ||
| 125 | + } else if (currentPageIndex.value == 8) { | ||
| 126 | + ta.track('enter_doublefeel_notification_grant_page'); | ||
| 127 | + } | ||
| 122 | } | 128 | } |
| 123 | 129 | ||
| 124 | void report() { | 130 | void report() { |
| @@ -26,11 +26,13 @@ class AppErrorHandler { | @@ -26,11 +26,13 @@ class AppErrorHandler { | ||
| 26 | } | 26 | } |
| 27 | if (businessCode == null || | 27 | if (businessCode == null || |
| 28 | !policy.excludeErrorCodeList.contains(businessCode)) { | 28 | !policy.excludeErrorCodeList.contains(businessCode)) { |
| 29 | - _showMessage(businessMessage ?? error.displayMessage); | 29 | + if (businessMessage != null) { |
| 30 | + _showMessage(businessMessage); | ||
| 31 | + } | ||
| 30 | } | 32 | } |
| 31 | case AppNetworkError(): | 33 | case AppNetworkError(): |
| 32 | case AppUnknownError(): | 34 | case AppUnknownError(): |
| 33 | - _showMessage(error.displayMessage); | 35 | + // _showMessage(error.displayMessage); |
| 34 | case AppCancelledError(): | 36 | case AppCancelledError(): |
| 35 | break; | 37 | break; |
| 36 | } | 38 | } |
| @@ -18,6 +18,13 @@ class ThinkingDataService { | @@ -18,6 +18,13 @@ class ThinkingDataService { | ||
| 18 | ); | 18 | ); |
| 19 | TDAnalytics.init( | 19 | TDAnalytics.init( |
| 20 | _environment.thinkingDataAppId, AppConst.thinkingDataServerUrl); | 20 | _environment.thinkingDataAppId, AppConst.thinkingDataServerUrl); |
| 21 | + TDAnalytics.calibrateTimeWithNtp('time.apple.com'); | ||
| 22 | + TDAnalytics.enableAutoTrack(TDAutoTrackEventType.APP_START | | ||
| 23 | + TDAutoTrackEventType.APP_END | | ||
| 24 | + TDAutoTrackEventType.APP_INSTALL); | ||
| 25 | + if (_environment.isDebug) { | ||
| 26 | + TDAnalytics.enableLog(true); | ||
| 27 | + } | ||
| 21 | } | 28 | } |
| 22 | 29 | ||
| 23 | void login(String userId) { | 30 | void login(String userId) { |
| @@ -32,7 +39,7 @@ class ThinkingDataService { | @@ -32,7 +39,7 @@ class ThinkingDataService { | ||
| 32 | 39 | ||
| 33 | void userSet(Map<String, Object?> properties) { | 40 | void userSet(Map<String, Object?> properties) { |
| 34 | AppLogger.i('ThinkingData userSet $properties'); | 41 | AppLogger.i('ThinkingData userSet $properties'); |
| 35 | - TDAnalytics.setSuperProperties(properties); | 42 | + TDAnalytics.userSet(properties); |
| 36 | } | 43 | } |
| 37 | 44 | ||
| 38 | void timeEvent(String s) { | 45 | void timeEvent(String s) { |
| @@ -61,10 +61,16 @@ class UserStateService { | @@ -61,10 +61,16 @@ class UserStateService { | ||
| 61 | final partner = _userPrefs.preferences.value.partnerUserInfo; | 61 | final partner = _userPrefs.preferences.value.partnerUserInfo; |
| 62 | final props = <String, Object?>{}; | 62 | final props = <String, Object?>{}; |
| 63 | if ((me?.id ?? 0) > 0) { | 63 | if ((me?.id ?? 0) > 0) { |
| 64 | - props['uid'] = me!.id; | 64 | + props['user_id'] = me!.id; |
| 65 | } | 65 | } |
| 66 | - if ((partner?.id ?? 0) > 0) { | ||
| 67 | - props['lid'] = partner!.id; | 66 | + if (me?.telephone != null) { |
| 67 | + props['user_phone'] = me!.telephone; | ||
| 68 | + } | ||
| 69 | + if (me?.thirdAccountId != null) { | ||
| 70 | + props['apple_id'] = me!.thirdAccountId; | ||
| 71 | + } | ||
| 72 | + if ((me?.createTime ?? 0) > 0) { | ||
| 73 | + props['account_create_time'] = me!.createTime; | ||
| 68 | } | 74 | } |
| 69 | if (props.isNotEmpty) { | 75 | if (props.isNotEmpty) { |
| 70 | _thinkingDataService.userSet(props); | 76 | _thinkingDataService.userSet(props); |
-
Please register or login to post a comment