Showing
25 changed files
with
1091 additions
and
334 deletions
| @@ -8,6 +8,7 @@ import '../../core/services/user_state_service.dart'; | @@ -8,6 +8,7 @@ import '../../core/services/user_state_service.dart'; | ||
| 8 | import '../../data/local/local_storage.dart'; | 8 | import '../../data/local/local_storage.dart'; |
| 9 | import '../../data/local/user_account_storage.dart'; | 9 | import '../../data/local/user_account_storage.dart'; |
| 10 | import '../../data/local/user_preferences_storage.dart'; | 10 | import '../../data/local/user_preferences_storage.dart'; |
| 11 | +import '../../core/services/loading_service.dart'; | ||
| 11 | import '../bindings/initial_binding.dart'; | 12 | import '../bindings/initial_binding.dart'; |
| 12 | 13 | ||
| 13 | /// Application startup before [runApp]. | 14 | /// Application startup before [runApp]. |
| @@ -36,6 +37,8 @@ abstract final class AppBootstrap { | @@ -36,6 +37,8 @@ abstract final class AppBootstrap { | ||
| 36 | localStorage: local, | 37 | localStorage: local, |
| 37 | ).dependencies(); | 38 | ).dependencies(); |
| 38 | 39 | ||
| 40 | + LoadingService.init(); | ||
| 41 | + | ||
| 39 | if (local.termsAgreed) { | 42 | if (local.termsAgreed) { |
| 40 | await Get.find<UserStateService>().initializeSdks(); | 43 | await Get.find<UserStateService>().initializeSdks(); |
| 41 | } | 44 | } |
| @@ -66,11 +66,8 @@ class BindPartnerController extends GetxController { | @@ -66,11 +66,8 @@ class BindPartnerController extends GetxController { | ||
| 66 | 66 | ||
| 67 | void onShareMyCode() async { | 67 | void onShareMyCode() async { |
| 68 | try { | 68 | try { |
| 69 | - final success = | ||
| 70 | - await PlatformHostApi().shareText("""嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App! | ||
| 71 | -它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。 | ||
| 72 | -快来一起使用吧 👉 https://doublefeel.cn/ | ||
| 73 | -我的好友id:${myInviteCode.value}"""); | 69 | + final success = await PlatformHostApi() |
| 70 | + .shareText(l10n.sharePartnerCodeTemplate(myInviteCode.value)); | ||
| 74 | } catch (e) { | 71 | } catch (e) { |
| 75 | AppToast.show(e.toString()); | 72 | AppToast.show(e.toString()); |
| 76 | } | 73 | } |
| @@ -91,25 +88,25 @@ class BindPartnerController extends GetxController { | @@ -91,25 +88,25 @@ class BindPartnerController extends GetxController { | ||
| 91 | switch (err.businessCode) { | 88 | switch (err.businessCode) { |
| 92 | case -1011201: | 89 | case -1011201: |
| 93 | DialogUtils.showCommonDialog(DialogMetaData( | 90 | DialogUtils.showCommonDialog(DialogMetaData( |
| 94 | - title: '该ID不存在', | ||
| 95 | - message: '这个ID不存在哦,请检查后重新输入', | ||
| 96 | - confirmText: '我知道了', | 91 | + title: l10n.bindPartnerIdNotExistTitle, |
| 92 | + message: l10n.bindPartnerIdNotExistMessage, | ||
| 93 | + confirmText: l10n.bindPartnerDialogGotIt, | ||
| 97 | iconAsset: null, | 94 | iconAsset: null, |
| 98 | )); | 95 | )); |
| 99 | break; | 96 | break; |
| 100 | case -1011202: | 97 | case -1011202: |
| 101 | DialogUtils.showCommonDialog(DialogMetaData( | 98 | DialogUtils.showCommonDialog(DialogMetaData( |
| 102 | - title: '添加失败', | ||
| 103 | - message: '该用户不允许被添加好友,无法添加Ta哦', | ||
| 104 | - confirmText: '我知道了', | 99 | + title: l10n.bindPartnerAddFailedTitle, |
| 100 | + message: l10n.bindPartnerAddFailedMessage, | ||
| 101 | + confirmText: l10n.bindPartnerDialogGotIt, | ||
| 105 | iconAsset: null, | 102 | iconAsset: null, |
| 106 | )); | 103 | )); |
| 107 | break; | 104 | break; |
| 108 | case -1011203: | 105 | case -1011203: |
| 109 | DialogUtils.showCommonDialog(DialogMetaData( | 106 | DialogUtils.showCommonDialog(DialogMetaData( |
| 110 | - title: '对方已经是你的好友啦', | ||
| 111 | - message: '请不要重复添加哦', | ||
| 112 | - confirmText: '我知道了', | 107 | + title: l10n.bindPartnerAlreadyFriendTitle, |
| 108 | + message: l10n.bindPartnerAlreadyFriendMessage, | ||
| 109 | + confirmText: l10n.bindPartnerDialogGotIt, | ||
| 113 | iconAsset: null, | 110 | iconAsset: null, |
| 114 | )); | 111 | )); |
| 115 | break; | 112 | break; |
| @@ -125,40 +122,6 @@ class BindPartnerController extends GetxController { | @@ -125,40 +122,6 @@ class BindPartnerController extends GetxController { | ||
| 125 | } | 122 | } |
| 126 | break; | 123 | break; |
| 127 | } | 124 | } |
| 128 | - // if (result is AppFailure) { | ||
| 129 | - // if (result.error is AppHttpError) { | ||
| 130 | - // var error = result.error as AppHttpError; | ||
| 131 | - | ||
| 132 | - // switch (error.businessCode) { | ||
| 133 | - // case -1011201: | ||
| 134 | - // DialogUtils.showCommonDialog(DialogMetaData( | ||
| 135 | - // title: '该ID不存在', | ||
| 136 | - // message: '这个ID不存在哦,请检查后重新输入', | ||
| 137 | - // confirmText: '我知道了', | ||
| 138 | - // iconAsset: null, | ||
| 139 | - // )); | ||
| 140 | - // break; | ||
| 141 | - // case -1011202: | ||
| 142 | - // DialogUtils.showCommonDialog(DialogMetaData( | ||
| 143 | - // title: '添加失败', | ||
| 144 | - // message: '该用户不允许被添加好友,无法添加Ta哦', | ||
| 145 | - // confirmText: '我知道了', | ||
| 146 | - // iconAsset: null, | ||
| 147 | - // )); | ||
| 148 | - // break; | ||
| 149 | - // case -1011203: | ||
| 150 | - // DialogUtils.showCommonDialog(DialogMetaData( | ||
| 151 | - // title: '对方已经是你的好友啦', | ||
| 152 | - // message: '请不要重复添加哦', | ||
| 153 | - // confirmText: '我知道了', | ||
| 154 | - // iconAsset: null, | ||
| 155 | - // )); | ||
| 156 | - // break; | ||
| 157 | - // } | ||
| 158 | - // } | ||
| 159 | - // return; | ||
| 160 | - // } | ||
| 161 | - // if (result is AppSuccess) {} | ||
| 162 | } catch (e) { | 125 | } catch (e) { |
| 163 | } finally { | 126 | } finally { |
| 164 | isSubmitting.value = false; | 127 | isSubmitting.value = false; |
| @@ -168,25 +131,18 @@ class BindPartnerController extends GetxController { | @@ -168,25 +131,18 @@ class BindPartnerController extends GetxController { | ||
| 168 | Future<void> onSkip() async { | 131 | Future<void> onSkip() async { |
| 169 | final userStateService = Get.find<UserStateService>(); | 132 | final userStateService = Get.find<UserStateService>(); |
| 170 | if (!userStateService.isVip) { | 133 | if (!userStateService.isVip) { |
| 171 | - await Get.toNamed( | 134 | + // 使用 offNamed 移除 BIND_PARTNER,并将 onboarding 标识传入 PURCHASE |
| 135 | + // 关闭 PURCHASE 后的跳转逻辑由 PurchaseController.onBackPressed() 接管 | ||
| 136 | + Get.offNamed( | ||
| 172 | Routes.PURCHASE, | 137 | Routes.PURCHASE, |
| 173 | - arguments: {PurchaseRouteArgs.showCloseButton: true}, | 138 | + arguments: { |
| 139 | + PurchaseRouteArgs.showCloseButton: true, | ||
| 140 | + 'from': 'onboarding', | ||
| 141 | + }, | ||
| 174 | ); | 142 | ); |
| 175 | - final userStateService = Get.find<UserStateService>(); | ||
| 176 | - if (userStateService.isVip) { | ||
| 177 | - Get.offAllNamed(AppRoutes.home); | ||
| 178 | - } else { | ||
| 179 | - await Future.delayed(Durations.medium1); | ||
| 180 | - Get.toNamed( | ||
| 181 | - Routes.MEMBERSHIP_OFFER, | ||
| 182 | - arguments: {'from': 'onboarding'}, | ||
| 183 | - ); | ||
| 184 | - } | ||
| 185 | } else { | 143 | } else { |
| 186 | Get.offAllNamed(AppRoutes.home); | 144 | Get.offAllNamed(AppRoutes.home); |
| 187 | } | 145 | } |
| 188 | - // Get.to(_BindSuccessPage(FriendItem( | ||
| 189 | - // avatar: 'https://cdn.couple360.net/couple360/themes/im_theme7.png'))); | ||
| 190 | } | 146 | } |
| 191 | } | 147 | } |
| 192 | 148 | ||
| @@ -207,20 +163,13 @@ class _BindSuccessPage extends StatelessWidget { | @@ -207,20 +163,13 @@ class _BindSuccessPage extends StatelessWidget { | ||
| 207 | onPressed: () async { | 163 | onPressed: () async { |
| 208 | final userStateService = Get.find<UserStateService>(); | 164 | final userStateService = Get.find<UserStateService>(); |
| 209 | if (!userStateService.isVip) { | 165 | if (!userStateService.isVip) { |
| 210 | - await Get.toNamed( | 166 | + Get.offNamed( |
| 211 | Routes.PURCHASE, | 167 | Routes.PURCHASE, |
| 212 | - arguments: {PurchaseRouteArgs.showCloseButton: true}, | 168 | + arguments: { |
| 169 | + PurchaseRouteArgs.showCloseButton: true, | ||
| 170 | + 'from': 'onboarding', | ||
| 171 | + }, | ||
| 213 | ); | 172 | ); |
| 214 | - final userStateService = Get.find<UserStateService>(); | ||
| 215 | - if (userStateService.isVip) { | ||
| 216 | - Get.offAllNamed(AppRoutes.home); | ||
| 217 | - } else { | ||
| 218 | - await Future.delayed(Durations.medium1); | ||
| 219 | - Get.toNamed( | ||
| 220 | - Routes.MEMBERSHIP_OFFER, | ||
| 221 | - arguments: {'from': 'onboarding'}, | ||
| 222 | - ); | ||
| 223 | - } | ||
| 224 | } else { | 173 | } else { |
| 225 | Get.offAllNamed(AppRoutes.home); | 174 | Get.offAllNamed(AppRoutes.home); |
| 226 | } | 175 | } |
| @@ -187,9 +187,7 @@ class _FeedbackInput extends GetView<SubmitFeedbackController> { | @@ -187,9 +187,7 @@ class _FeedbackInput extends GetView<SubmitFeedbackController> { | ||
| 187 | enabledBorder: InputBorder.none, | 187 | enabledBorder: InputBorder.none, |
| 188 | focusedBorder: InputBorder.none, | 188 | focusedBorder: InputBorder.none, |
| 189 | counterText: '', | 189 | counterText: '', |
| 190 | - hintText: '''1.请说明问题的界面和使用场景 | ||
| 191 | -2.请提供截图,以便提升解决问题效率 | ||
| 192 | -3.请留下联系方式,我们尽快回复联系您''', | 190 | + hintText: context.l10n.feedbackHintText, |
| 193 | hintStyle: TextStyle( | 191 | hintStyle: TextStyle( |
| 194 | color: context.colors.textTertiary, | 192 | color: context.colors.textTertiary, |
| 195 | fontSize: 14, | 193 | fontSize: 14, |
| @@ -21,6 +21,7 @@ import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'; | @@ -21,6 +21,7 @@ import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'; | ||
| 21 | import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; | 21 | import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; |
| 22 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; | 22 | import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; |
| 23 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; | 23 | import 'package:doublefeel_flutter/data/models/pay/pay_models.dart'; |
| 24 | +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | ||
| 24 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; | 25 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; |
| 25 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; | 26 | import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; |
| 26 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; | 27 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; |
| @@ -89,7 +90,7 @@ class TodayController extends GetMaterialController { | @@ -89,7 +90,7 @@ class TodayController extends GetMaterialController { | ||
| 89 | 90 | ||
| 90 | final isLoadingToday = false.obs; | 91 | final isLoadingToday = false.obs; |
| 91 | final showHealthDataAuthCardStatus = 1.obs; | 92 | final showHealthDataAuthCardStatus = 1.obs; |
| 92 | - final stressSubtitle = 'Hi, 你今日的综合压力状态'.obs; | 93 | + final stressSubtitle = ''.obs; |
| 93 | 94 | ||
| 94 | // ── HRV 表盘引导 Banner ─────────────────── | 95 | // ── HRV 表盘引导 Banner ─────────────────── |
| 95 | final showHrvAdBanner = false.obs; | 96 | final showHrvAdBanner = false.obs; |
| @@ -267,13 +268,13 @@ class TodayController extends GetMaterialController { | @@ -267,13 +268,13 @@ class TodayController extends GetMaterialController { | ||
| 267 | if (showHealthDataAuthCardStatus.value == 1) { | 268 | if (showHealthDataAuthCardStatus.value == 1) { |
| 268 | return; | 269 | return; |
| 269 | } else if (showHealthDataAuthCardStatus.value == 0) { | 270 | } else if (showHealthDataAuthCardStatus.value == 0) { |
| 270 | - Get.to(NoHealthDataPage( | ||
| 271 | - onRefresh: requestHealthAuthorization, | ||
| 272 | - )); | ||
| 273 | - } else { | ||
| 274 | Get.to(NoHealthDataNoPermissionPage( | 271 | Get.to(NoHealthDataNoPermissionPage( |
| 275 | onRequest: requestHealthAuthorization, | 272 | onRequest: requestHealthAuthorization, |
| 276 | )); | 273 | )); |
| 274 | + } else { | ||
| 275 | + Get.to(NoHealthDataPage( | ||
| 276 | + onRefresh: requestHealthAuthorization, | ||
| 277 | + )); | ||
| 277 | } | 278 | } |
| 278 | } | 279 | } |
| 279 | 280 | ||
| @@ -296,18 +297,17 @@ class TodayController extends GetMaterialController { | @@ -296,18 +297,17 @@ class TodayController extends GetMaterialController { | ||
| 296 | 297 | ||
| 297 | Future<void> checkHealthDataAuthCardVisible() async { | 298 | Future<void> checkHealthDataAuthCardVisible() async { |
| 298 | try { | 299 | try { |
| 300 | + final result = await _hostApi.checkHealthAppAuthorization(); | ||
| 301 | + | ||
| 302 | + if (result.status == 1) { | ||
| 303 | + await _performDataUpload(); | ||
| 304 | + } | ||
| 299 | final hasUploaded = | 305 | final hasUploaded = |
| 300 | switch (await _healthApi.getHealthDataEverUploaded()) { | 306 | switch (await _healthApi.getHealthDataEverUploaded()) { |
| 301 | AppSuccess(:final data) => data, | 307 | AppSuccess(:final data) => data, |
| 302 | AppFailure() => false, | 308 | AppFailure() => false, |
| 303 | }; | 309 | }; |
| 304 | - final result = await _hostApi.checkHealthAppAuthorization(); | ||
| 305 | - | ||
| 306 | - if (result.status == 1) { | ||
| 307 | - _performDataUpload(); | ||
| 308 | - } | ||
| 309 | - | ||
| 310 | - showHealthDataAuthCardStatus.value = !hasUploaded ? 0 : result.status; | 310 | + showHealthDataAuthCardStatus.value = !hasUploaded ? -1 : result.status; |
| 311 | } catch (e) {} | 311 | } catch (e) {} |
| 312 | } | 312 | } |
| 313 | 313 | ||
| @@ -316,7 +316,8 @@ class TodayController extends GetMaterialController { | @@ -316,7 +316,8 @@ class TodayController extends GetMaterialController { | ||
| 316 | final intDate = int.parse(DateFormat('yyyyMMdd').format(date)); | 316 | final intDate = int.parse(DateFormat('yyyyMMdd').format(date)); |
| 317 | 317 | ||
| 318 | final isToday = DateUtils.isSameDay(date, lastSelectableDay); | 318 | final isToday = DateUtils.isSameDay(date, lastSelectableDay); |
| 319 | - stressSubtitle.value = isToday ? 'Hi, 你今日的综合压力状态' : '该日压力状态'; | 319 | + stressSubtitle.value = (isFriend ? 'Ta' : '') + |
| 320 | + (isToday ? l10n.overallStressLevelToday : l10n.stressLevelsOnThatDay); | ||
| 320 | switch (await _healthApi.getV2HealthData(friendUserId, intDate)) { | 321 | switch (await _healthApi.getV2HealthData(friendUserId, intDate)) { |
| 321 | case AppSuccess(:final data): | 322 | case AppSuccess(:final data): |
| 322 | v2HealthData.value = data; | 323 | v2HealthData.value = data; |
| @@ -271,7 +271,7 @@ class _Avatar extends StatelessWidget { | @@ -271,7 +271,7 @@ class _Avatar extends StatelessWidget { | ||
| 271 | ), | 271 | ), |
| 272 | alignment: Alignment.center, | 272 | alignment: Alignment.center, |
| 273 | child: Text( | 273 | child: Text( |
| 274 | - '拍摄', | 274 | + l10n.filming, |
| 275 | style: TextStyle( | 275 | style: TextStyle( |
| 276 | color: context.colors.textPrimary, | 276 | color: context.colors.textPrimary, |
| 277 | fontSize: 14, | 277 | fontSize: 14, |
| @@ -298,7 +298,7 @@ class _Avatar extends StatelessWidget { | @@ -298,7 +298,7 @@ class _Avatar extends StatelessWidget { | ||
| 298 | ), | 298 | ), |
| 299 | alignment: Alignment.center, | 299 | alignment: Alignment.center, |
| 300 | child: Text( | 300 | child: Text( |
| 301 | - '相册上传', | 301 | + l10n.uploadPhotos, |
| 302 | style: TextStyle( | 302 | style: TextStyle( |
| 303 | color: context.colors.textPrimary, | 303 | color: context.colors.textPrimary, |
| 304 | fontSize: 14, | 304 | fontSize: 14, |
| @@ -321,7 +321,7 @@ class _Avatar extends StatelessWidget { | @@ -321,7 +321,7 @@ class _Avatar extends StatelessWidget { | ||
| 321 | ), | 321 | ), |
| 322 | alignment: Alignment.center, | 322 | alignment: Alignment.center, |
| 323 | child: Text( | 323 | child: Text( |
| 324 | - '取消', | 324 | + l10n.cancel, |
| 325 | style: TextStyle( | 325 | style: TextStyle( |
| 326 | color: context.colors.textSecondary, | 326 | color: context.colors.textSecondary, |
| 327 | fontSize: 14, | 327 | fontSize: 14, |
| @@ -396,8 +396,8 @@ class _UnlockPremiumCard extends StatelessWidget { | @@ -396,8 +396,8 @@ class _UnlockPremiumCard extends StatelessWidget { | ||
| 396 | Color(0xFFFFFFFF) | 396 | Color(0xFFFFFFFF) |
| 397 | ], | 397 | ], |
| 398 | ).createShader(bounds), | 398 | ).createShader(bounds), |
| 399 | - child: const Text( | ||
| 400 | - '解锁专业版', | 399 | + child: Text( |
| 400 | + l10n.unlockTheProVersion, | ||
| 401 | style: TextStyle( | 401 | style: TextStyle( |
| 402 | color: Colors.white, | 402 | color: Colors.white, |
| 403 | fontSize: 16, | 403 | fontSize: 16, |
| @@ -409,14 +409,15 @@ class _UnlockPremiumCard extends StatelessWidget { | @@ -409,14 +409,15 @@ class _UnlockPremiumCard extends StatelessWidget { | ||
| 409 | height: 4, | 409 | height: 4, |
| 410 | ), | 410 | ), |
| 411 | Text( | 411 | Text( |
| 412 | - '开启压力预警与健康陪伴之旅', | ||
| 413 | - textAlign: TextAlign.center, | 412 | + l10n.embarkOnAJourneyOfStressAwarenessAndWellnessSupport, |
| 413 | + textAlign: TextAlign.left, | ||
| 414 | style: TextStyle( | 414 | style: TextStyle( |
| 415 | - color: const Color(0xFFC6B2FF), | ||
| 416 | - fontSize: 12, | ||
| 417 | - fontFamily: 'PingFang SC', | ||
| 418 | - fontWeight: FontWeight.w500, | ||
| 419 | - ), | 415 | + color: const Color(0xFFC6B2FF), |
| 416 | + fontSize: 12, | ||
| 417 | + fontWeight: FontWeight.w500, | ||
| 418 | + height: 1.2), | ||
| 419 | + maxLines: 2, | ||
| 420 | + overflow: TextOverflow.ellipsis, | ||
| 420 | ), | 421 | ), |
| 421 | Container( | 422 | Container( |
| 422 | height: 32, | 423 | height: 32, |
| @@ -445,7 +446,7 @@ class _UnlockPremiumCard extends StatelessWidget { | @@ -445,7 +446,7 @@ class _UnlockPremiumCard extends StatelessWidget { | ||
| 445 | height: 18, | 446 | height: 18, |
| 446 | color: context.colors.textPrimary), | 447 | color: context.colors.textPrimary), |
| 447 | Text( | 448 | Text( |
| 448 | - '立即解锁', | 449 | + l10n.unlockNow, |
| 449 | style: TextStyle( | 450 | style: TextStyle( |
| 450 | color: context.colors.textPrimary, | 451 | color: context.colors.textPrimary, |
| 451 | fontSize: 14, | 452 | fontSize: 14, |
| @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; | @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; | ||
| 2 | 2 | ||
| 3 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measurement_bottom_sheet.dart'; | 3 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measurement_bottom_sheet.dart'; |
| 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_date_picker_sheet.dart'; | 6 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; |
| 6 | 7 | ||
| 7 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 8 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| @@ -166,7 +167,7 @@ class TodayTabBody extends StatelessWidget { | @@ -166,7 +167,7 @@ class TodayTabBody extends StatelessWidget { | ||
| 166 | mainAxisAlignment: MainAxisAlignment.center, | 167 | mainAxisAlignment: MainAxisAlignment.center, |
| 167 | children: [ | 168 | children: [ |
| 168 | Text( | 169 | Text( |
| 169 | - controller.stressSubtitle.value, | 170 | + 'Hi,${controller.stressSubtitle.value}', |
| 170 | textAlign: TextAlign.center, | 171 | textAlign: TextAlign.center, |
| 171 | style: const TextStyle( | 172 | style: const TextStyle( |
| 172 | color: Color(0xFF78787D), | 173 | color: Color(0xFF78787D), |
| @@ -246,7 +247,10 @@ class TodayTabBody extends StatelessWidget { | @@ -246,7 +247,10 @@ class TodayTabBody extends StatelessWidget { | ||
| 246 | Padding( | 247 | Padding( |
| 247 | padding: const EdgeInsets.symmetric(vertical: 12), | 248 | padding: const EdgeInsets.symmetric(vertical: 12), |
| 248 | child: Text( | 249 | child: Text( |
| 249 | - controller.isFriend ? 'TA的今日行动' : context.l10n.dailyActions, | 250 | + (controller.isFriend |
| 251 | + ? context.l10n | ||
| 252 | + .reportOtherPossessiveTitle(context.l10n.dailyActions) | ||
| 253 | + : context.l10n.dailyActions), | ||
| 250 | style: TextStyle( | 254 | style: TextStyle( |
| 251 | color: context.colors.textPrimary, | 255 | color: context.colors.textPrimary, |
| 252 | fontSize: 16, | 256 | fontSize: 16, |
| @@ -259,14 +263,10 @@ class TodayTabBody extends StatelessWidget { | @@ -259,14 +263,10 @@ class TodayTabBody extends StatelessWidget { | ||
| 259 | Container( | 263 | Container( |
| 260 | alignment: Alignment.center, | 264 | alignment: Alignment.center, |
| 261 | margin: EdgeInsets.only( | 265 | margin: EdgeInsets.only( |
| 262 | - bottom: 16 + MediaQuery.paddingOf(context).bottom, | ||
| 263 | - top: 28, | ||
| 264 | - ), | ||
| 265 | - child: Image.asset( | ||
| 266 | - 'assets/images/common/ic_bottom_slogan.png', | ||
| 267 | - width: 223, | ||
| 268 | - height: 35, | 266 | + bottom: 16, |
| 267 | + // top: 28, | ||
| 269 | ), | 268 | ), |
| 269 | + child: ReportBottomSlogan(), | ||
| 270 | ), | 270 | ), |
| 271 | ]), | 271 | ]), |
| 272 | ), | 272 | ), |
| @@ -425,7 +425,7 @@ class TodayTabBody extends StatelessWidget { | @@ -425,7 +425,7 @@ class TodayTabBody extends StatelessWidget { | ||
| 425 | : null, | 425 | : null, |
| 426 | child: Text( | 426 | child: Text( |
| 427 | isSameDay(day, controller.lastSelectableDay) | 427 | isSameDay(day, controller.lastSelectableDay) |
| 428 | - ? AppLocalizations.of(context)!.today | 428 | + ? l10n.todayOnWeeklyCalendar |
| 429 | : _weekdayText(context, day), | 429 | : _weekdayText(context, day), |
| 430 | style: TextStyle( | 430 | style: TextStyle( |
| 431 | color: _calendarTextColor(context, day, selected: selected), | 431 | color: _calendarTextColor(context, day, selected: selected), |
| @@ -489,7 +489,7 @@ class TodayTabBody extends StatelessWidget { | @@ -489,7 +489,7 @@ class TodayTabBody extends StatelessWidget { | ||
| 489 | return context.l10n.yesterday; | 489 | return context.l10n.yesterday; |
| 490 | } | 490 | } |
| 491 | if (isSameDay(selectedDay, today.subtract(const Duration(days: 2)))) { | 491 | if (isSameDay(selectedDay, today.subtract(const Duration(days: 2)))) { |
| 492 | - return '前天'; | 492 | + return l10n.theDayBeforeYesterday; |
| 493 | } | 493 | } |
| 494 | final locale = Localizations.localeOf(context).toString(); | 494 | final locale = Localizations.localeOf(context).toString(); |
| 495 | if (selectedDay.year == today.year) { | 495 | if (selectedDay.year == today.year) { |
| @@ -571,7 +571,7 @@ class _LatestHrvCard extends StatelessWidget { | @@ -571,7 +571,7 @@ class _LatestHrvCard extends StatelessWidget { | ||
| 571 | crossAxisAlignment: CrossAxisAlignment.start, | 571 | crossAxisAlignment: CrossAxisAlignment.start, |
| 572 | children: [ | 572 | children: [ |
| 573 | Text( | 573 | Text( |
| 574 | - '最近一次($latestDataTimeText)', | 574 | + '${l10n.mostRecent}($latestDataTimeText)', |
| 575 | style: TextStyle( | 575 | style: TextStyle( |
| 576 | color: colors.textSecondary, | 576 | color: colors.textSecondary, |
| 577 | fontSize: 12, | 577 | fontSize: 12, |
| @@ -661,7 +661,8 @@ class _TopFriendBar extends StatelessWidget { | @@ -661,7 +661,8 @@ class _TopFriendBar extends StatelessWidget { | ||
| 661 | ), | 661 | ), |
| 662 | ), | 662 | ), |
| 663 | Text( | 663 | Text( |
| 664 | - '${controller.targetFriendInfo.value?.remarkName}的状态', | 664 | + context.l10n.friendStatusTitle( |
| 665 | + controller.targetFriendInfo.value?.remarkName ?? ''), | ||
| 665 | style: TextStyle( | 666 | style: TextStyle( |
| 666 | color: Colors.black, | 667 | color: Colors.black, |
| 667 | fontSize: 16, | 668 | fontSize: 16, |
| @@ -775,7 +776,7 @@ class _TopDateBar extends StatelessWidget { | @@ -775,7 +776,7 @@ class _TopDateBar extends StatelessWidget { | ||
| 775 | ), | 776 | ), |
| 776 | const SizedBox(width: 2), | 777 | const SizedBox(width: 2), |
| 777 | Text( | 778 | Text( |
| 778 | - '20%优惠', | 779 | + l10n.purchaseAnnualDiscount, |
| 779 | style: TextStyle( | 780 | style: TextStyle( |
| 780 | fontSize: 14, | 781 | fontSize: 14, |
| 781 | fontWeight: FontWeight.w500, | 782 | fontWeight: FontWeight.w500, |
| @@ -6,6 +6,7 @@ import 'package:doublefeel_flutter/app/utils/dialog_utils.dart'; | @@ -6,6 +6,7 @@ import 'package:doublefeel_flutter/app/utils/dialog_utils.dart'; | ||
| 6 | 6 | ||
| 7 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | 7 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; |
| 8 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 8 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 9 | +import 'package:doublefeel_flutter/core/services/loading_service.dart'; | ||
| 9 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 10 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 10 | import 'package:doublefeel_flutter/core/theme/app_colors.dart'; | 11 | import 'package:doublefeel_flutter/core/theme/app_colors.dart'; |
| 11 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 12 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| @@ -174,23 +175,28 @@ class AccountSettingView extends GetView<MyController> { | @@ -174,23 +175,28 @@ class AccountSettingView extends GetView<MyController> { | ||
| 174 | 175 | ||
| 175 | Future<void> _logout() async { | 176 | Future<void> _logout() async { |
| 176 | final confirmResult = await DialogUtils.showCommonDialog(DialogMetaData( | 177 | final confirmResult = await DialogUtils.showCommonDialog(DialogMetaData( |
| 177 | - title: '退出登录', | ||
| 178 | - message: '确认退出登录吗?', | ||
| 179 | - confirmText: '确认退出', | 178 | + title: l10n.logOut, |
| 179 | + message: l10n.confirmLogoutPrompt, | ||
| 180 | + confirmText: l10n.confirmLogout, | ||
| 180 | iconAsset: null)); | 181 | iconAsset: null)); |
| 181 | if (confirmResult.action != DialogAction.confirm) return; | 182 | if (confirmResult.action != DialogAction.confirm) return; |
| 182 | 183 | ||
| 183 | - await Get.find<UserStateService>().onLogout(); | ||
| 184 | - AppToast.show('已退出登录'); | 184 | + await LoadingService.instance.run(() async { |
| 185 | + await Get.find<UserStateService>().onLogout(); | ||
| 186 | + }); | ||
| 187 | + | ||
| 188 | + AppToast.show(l10n.loggedOutSuccessfully); | ||
| 185 | Get.offAllNamed(AppRoutes.login); | 189 | Get.offAllNamed(AppRoutes.login); |
| 186 | } | 190 | } |
| 187 | 191 | ||
| 188 | Future<void> _deleteAccount() async { | 192 | Future<void> _deleteAccount() async { |
| 189 | - final deleteResult = await Get.find<UserApi>().deleteAccount(); | ||
| 190 | - if (deleteResult is! AppSuccess<void>) return; | 193 | + await LoadingService.instance.run(() async { |
| 194 | + final deleteResult = await Get.find<UserApi>().deleteAccount(); | ||
| 195 | + if (deleteResult is! AppSuccess<void>) return; | ||
| 191 | 196 | ||
| 192 | - await Get.find<UserStateService>().onLogout(callServerLogout: false); | ||
| 193 | - AppToast.show('账号注销成功'); | 197 | + await Get.find<UserStateService>().onLogout(callServerLogout: false); |
| 198 | + }); | ||
| 199 | + AppToast.show(l10n.accountDeletedSuccessfully); | ||
| 194 | Get.offAllNamed(AppRoutes.login); | 200 | Get.offAllNamed(AppRoutes.login); |
| 195 | } | 201 | } |
| 196 | } | 202 | } |
| @@ -269,7 +275,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | @@ -269,7 +275,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | ||
| 269 | crossAxisAlignment: CrossAxisAlignment.start, | 275 | crossAxisAlignment: CrossAxisAlignment.start, |
| 270 | children: [ | 276 | children: [ |
| 271 | Text( | 277 | Text( |
| 272 | - '注销账号后将无法找回!请谨慎操作', | 278 | + context.l10n.deleteAccountWarningTitle, |
| 273 | textAlign: TextAlign.center, | 279 | textAlign: TextAlign.center, |
| 274 | style: TextStyle( | 280 | style: TextStyle( |
| 275 | color: context.colors.warning, | 281 | color: context.colors.warning, |
| @@ -281,7 +287,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | @@ -281,7 +287,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | ||
| 281 | height: 12, | 287 | height: 12, |
| 282 | ), | 288 | ), |
| 283 | Text( | 289 | Text( |
| 284 | - '提示:注销账号将会删除该账号内包括但不限于\n个人资料、情绪记录、统计数据等全部信息。', | 290 | + context.l10n.deleteAccountWarningPrompt, |
| 285 | style: TextStyle( | 291 | style: TextStyle( |
| 286 | color: context.colors.textSecondary, | 292 | color: context.colors.textSecondary, |
| 287 | fontSize: 12, | 293 | fontSize: 12, |
| @@ -292,7 +298,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | @@ -292,7 +298,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | ||
| 292 | height: 12, | 298 | height: 12, |
| 293 | ), | 299 | ), |
| 294 | Text( | 300 | Text( |
| 295 | - '注1:你的健康数据会保存在苹果健康,我们不会删除苹果健康中的数据。', | 301 | + context.l10n.deleteAccountWarningNote1, |
| 296 | style: TextStyle( | 302 | style: TextStyle( |
| 297 | color: context.colors.textSecondary, | 303 | color: context.colors.textSecondary, |
| 298 | fontSize: 12, | 304 | fontSize: 12, |
| @@ -303,7 +309,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | @@ -303,7 +309,7 @@ class _DeleteAccountBottomSheet extends StatelessWidget { | ||
| 303 | height: 12, | 309 | height: 12, |
| 304 | ), | 310 | ), |
| 305 | Text( | 311 | Text( |
| 306 | - '注2:删除账号不会影响你在App Store的订阅状态,如果需要取消订阅,请在APPs Store - 头像 - 订阅中手动取消订阅。', | 312 | + context.l10n.deleteAccountWarningNote2, |
| 307 | style: TextStyle( | 313 | style: TextStyle( |
| 308 | color: context.colors.textSecondary, | 314 | color: context.colors.textSecondary, |
| 309 | fontSize: 12, | 315 | fontSize: 12, |
| @@ -59,7 +59,7 @@ class PremiumCard extends StatelessWidget { | @@ -59,7 +59,7 @@ class PremiumCard extends StatelessWidget { | ||
| 59 | crossAxisAlignment: CrossAxisAlignment.start, | 59 | crossAxisAlignment: CrossAxisAlignment.start, |
| 60 | children: [ | 60 | children: [ |
| 61 | Text( | 61 | Text( |
| 62 | - '年度会员优惠', | 62 | + l10n.annualMemberDiscounts, |
| 63 | style: TextStyle( | 63 | style: TextStyle( |
| 64 | fontSize: 12, | 64 | fontSize: 12, |
| 65 | fontWeight: FontWeight.w500, | 65 | fontWeight: FontWeight.w500, |
| @@ -81,11 +81,11 @@ class PremiumCard extends StatelessWidget { | @@ -81,11 +81,11 @@ class PremiumCard extends StatelessWidget { | ||
| 81 | ), | 81 | ), |
| 82 | ), | 82 | ), |
| 83 | const SizedBox(width: 4), | 83 | const SizedBox(width: 4), |
| 84 | - const Padding( | ||
| 85 | - padding: EdgeInsets.only(bottom: 4), | 84 | + Padding( |
| 85 | + padding: const EdgeInsets.only(bottom: 4), | ||
| 86 | child: Text( | 86 | child: Text( |
| 87 | - '优惠', | ||
| 88 | - style: TextStyle( | 87 | + l10n.specialOffers, |
| 88 | + style: const TextStyle( | ||
| 89 | fontSize: 16, | 89 | fontSize: 16, |
| 90 | fontWeight: FontWeight.w600, | 90 | fontWeight: FontWeight.w600, |
| 91 | color: _accent, | 91 | color: _accent, |
| @@ -102,7 +102,7 @@ class PremiumCard extends StatelessWidget { | @@ -102,7 +102,7 @@ class PremiumCard extends StatelessWidget { | ||
| 102 | ? Row( | 102 | ? Row( |
| 103 | children: [ | 103 | children: [ |
| 104 | Text( | 104 | Text( |
| 105 | - '现价', | 105 | + l10n.currentPrice, |
| 106 | style: TextStyle( | 106 | style: TextStyle( |
| 107 | fontSize: 12, | 107 | fontSize: 12, |
| 108 | fontWeight: FontWeight.w500, | 108 | fontWeight: FontWeight.w500, |
| @@ -120,7 +120,8 @@ class PremiumCard extends StatelessWidget { | @@ -120,7 +120,8 @@ class PremiumCard extends StatelessWidget { | ||
| 120 | ), | 120 | ), |
| 121 | const SizedBox(width: 8), | 121 | const SizedBox(width: 8), |
| 122 | Text( | 122 | Text( |
| 123 | - '原价${controller.originDisplayPrice()}', | 123 | + l10n.originalPrice( |
| 124 | + controller.originDisplayPrice()), | ||
| 124 | style: TextStyle( | 125 | style: TextStyle( |
| 125 | fontSize: 12, | 126 | fontSize: 12, |
| 126 | color: context.colors.textTertiary, | 127 | color: context.colors.textTertiary, |
| @@ -130,16 +130,21 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -130,16 +130,21 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 130 | 130 | ||
| 131 | return Row( | 131 | return Row( |
| 132 | children: [ | 132 | children: [ |
| 133 | - Text( | ||
| 134 | - controller.isFriend | ||
| 135 | - ? (sameDay ? 'Ta的今日HRV趋势' : 'Ta的该日HRV趋势') | ||
| 136 | - : (sameDay ? '今日HRV趋势' : context.l10n.averageHrvForTheDay), | ||
| 137 | - style: TextStyle( | ||
| 138 | - color: context.colors.textPrimary, | ||
| 139 | - fontSize: 16, | ||
| 140 | - fontWeight: FontWeight.w600, | ||
| 141 | - ), | ||
| 142 | - ), | 133 | + Builder(builder: (context) { |
| 134 | + final s = (sameDay | ||
| 135 | + ? context.l10n.todaySHrvTrend | ||
| 136 | + : context.l10n.averageHrvForTheDay); | ||
| 137 | + return Text( | ||
| 138 | + (controller.isFriend | ||
| 139 | + ? context.l10n.reportOtherPossessiveTitle(s) | ||
| 140 | + : s), | ||
| 141 | + style: TextStyle( | ||
| 142 | + color: context.colors.textPrimary, | ||
| 143 | + fontSize: 16, | ||
| 144 | + fontWeight: FontWeight.w600, | ||
| 145 | + ), | ||
| 146 | + ); | ||
| 147 | + }), | ||
| 143 | GestureDetector( | 148 | GestureDetector( |
| 144 | behavior: HitTestBehavior.opaque, | 149 | behavior: HitTestBehavior.opaque, |
| 145 | onTap: () => showHrvPrincipleExplanationBottomSheet(context), | 150 | onTap: () => showHrvPrincipleExplanationBottomSheet(context), |
| @@ -353,7 +358,7 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -353,7 +358,7 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 353 | return Stack( | 358 | return Stack( |
| 354 | children: [ | 359 | children: [ |
| 355 | if (stressPoints.isEmpty) | 360 | if (stressPoints.isEmpty) |
| 356 | - _EmptyChart(text: '暂无压力数据') | 361 | + _EmptyChart(text: context.l10n.noPressureDataAvailableAtThisTime) |
| 357 | else | 362 | else |
| 358 | LayoutBuilder( | 363 | LayoutBuilder( |
| 359 | builder: (context, constraints) { | 364 | builder: (context, constraints) { |
| @@ -623,7 +628,7 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -623,7 +628,7 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 623 | color: context.colors.textTertiary, | 628 | color: context.colors.textTertiary, |
| 624 | ), | 629 | ), |
| 625 | Text( | 630 | Text( |
| 626 | - '会员可查看完整数据', | 631 | + context.l10n.membersCanViewTheCompleteData, |
| 627 | style: TextStyle( | 632 | style: TextStyle( |
| 628 | color: context.colors.textTertiary, | 633 | color: context.colors.textTertiary, |
| 629 | fontSize: 14, | 634 | fontSize: 14, |
| @@ -647,8 +652,8 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -647,8 +652,8 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 647 | borderRadius: BorderRadius.circular(24), | 652 | borderRadius: BorderRadius.circular(24), |
| 648 | ), | 653 | ), |
| 649 | ), | 654 | ), |
| 650 | - child: const Text( | ||
| 651 | - '立即解锁', | 655 | + child: Text( |
| 656 | + context.l10n.unlockNow, | ||
| 652 | textAlign: TextAlign.center, | 657 | textAlign: TextAlign.center, |
| 653 | style: TextStyle( | 658 | style: TextStyle( |
| 654 | color: Color(0xFF0F0F11), | 659 | color: Color(0xFF0F0F11), |
| @@ -695,15 +700,15 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -695,15 +700,15 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 695 | String _getStatusByState(int? state) { | 700 | String _getStatusByState(int? state) { |
| 696 | switch (state) { | 701 | switch (state) { |
| 697 | case 1: | 702 | case 1: |
| 698 | - return '压力过载'; | 703 | + return l10n.pressureOverload; |
| 699 | case 2: | 704 | case 2: |
| 700 | - return '注意压力'; | 705 | + return l10n.beMindfulOfStress; |
| 701 | case 3: | 706 | case 3: |
| 702 | - return '状态正常'; | 707 | + return l10n.statusNormal; |
| 703 | case 4: | 708 | case 4: |
| 704 | - return '状态优秀'; | 709 | + return l10n.inExcellentCondition; |
| 705 | } | 710 | } |
| 706 | - return '等待数据'; | 711 | + return l10n.waitingForData; |
| 707 | } | 712 | } |
| 708 | 713 | ||
| 709 | List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) { | 714 | List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) { |
| @@ -723,7 +728,8 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -723,7 +728,8 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 723 | ), | 728 | ), |
| 724 | ), | 729 | ), |
| 725 | TextSpan( | 730 | TextSpan( |
| 726 | - text: '♥ 压力 ${_formatNumber(rod.toY)} · ${_formatHour(x.toDouble())}', | 731 | + text: |
| 732 | + '♥ ${l10n.pressure} ${_formatNumber(rod.toY)} · ${_formatHour(x.toDouble())}', | ||
| 727 | style: const TextStyle( | 733 | style: const TextStyle( |
| 728 | color: Color(0xFF78787D), | 734 | color: Color(0xFF78787D), |
| 729 | fontWeight: FontWeight.bold, | 735 | fontWeight: FontWeight.bold, |
| @@ -23,14 +23,17 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -23,14 +23,17 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 23 | var sameDay = DateUtils.isSameDay( | 23 | var sameDay = DateUtils.isSameDay( |
| 24 | controller.selectedDate.value, controller.lastSelectableDay); | 24 | controller.selectedDate.value, controller.lastSelectableDay); |
| 25 | 25 | ||
| 26 | + var otherHrv = (sameDay | ||
| 27 | + ? context.l10n.todaySHrvTrend | ||
| 28 | + : context.l10n.averageHrvForTheDay); | ||
| 26 | return Row( | 29 | return Row( |
| 27 | children: [ | 30 | children: [ |
| 28 | // HRV | 31 | // HRV |
| 29 | Expanded( | 32 | Expanded( |
| 30 | child: _NumberItem( | 33 | child: _NumberItem( |
| 31 | label: controller.isFriend | 34 | label: controller.isFriend |
| 32 | - ? (sameDay ? 'Ta的今日平均HRV' : 'Ta的该日平均HRV') | ||
| 33 | - : (sameDay ? '今日平均HRV' : context.l10n.averageHrvForTheDay), | 35 | + ? context.l10n.reportOtherPossessiveTitle(otherHrv) |
| 36 | + : otherHrv, | ||
| 34 | value: | 37 | value: |
| 35 | controller.v2HealthData.value?.hrvAvg?.toString() ?? '--', | 38 | controller.v2HealthData.value?.hrvAvg?.toString() ?? '--', |
| 36 | unit: 'ms', | 39 | unit: 'ms', |
| @@ -40,7 +43,8 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -40,7 +43,8 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 40 | Expanded( | 43 | Expanded( |
| 41 | child: _NumberItem( | 44 | child: _NumberItem( |
| 42 | label: controller.isFriend | 45 | label: controller.isFriend |
| 43 | - ? 'Ta的静息心率' | 46 | + ? context.l10n.reportOtherPossessiveTitle( |
| 47 | + context.l10n.restingHeartRate) | ||
| 44 | : context.l10n.restingHeartRate, | 48 | : context.l10n.restingHeartRate, |
| 45 | value: controller.v2HealthData.value?.lastRestingHrValue | 49 | value: controller.v2HealthData.value?.lastRestingHrValue |
| 46 | ?.toString() ?? | 50 | ?.toString() ?? |
| @@ -20,7 +20,7 @@ class TodaySleepCard extends StatelessWidget { | @@ -20,7 +20,7 @@ class TodaySleepCard extends StatelessWidget { | ||
| 20 | 20 | ||
| 21 | int sleepDuration = healthData?.sleepDuration ?? 0; | 21 | int sleepDuration = healthData?.sleepDuration ?? 0; |
| 22 | int sleepHours = sleepDuration ~/ 3600; | 22 | int sleepHours = sleepDuration ~/ 3600; |
| 23 | - int sleepMinutes = sleepDuration % 60; | 23 | + int sleepMinutes = (sleepDuration % 3600) ~/ 60; |
| 24 | Color sleepStateColor = sleepDuration == 0 | 24 | Color sleepStateColor = sleepDuration == 0 |
| 25 | ? context.colors.textPrimary | 25 | ? context.colors.textPrimary |
| 26 | : healthData?.sleepStateColor(context); | 26 | : healthData?.sleepStateColor(context); |
| @@ -46,9 +46,10 @@ class TodaySleepCard extends StatelessWidget { | @@ -46,9 +46,10 @@ class TodaySleepCard extends StatelessWidget { | ||
| 46 | label: context.l10n.duration, | 46 | label: context.l10n.duration, |
| 47 | labelColor: context.colors.primary, | 47 | labelColor: context.colors.primary, |
| 48 | valueSpans: [ | 48 | valueSpans: [ |
| 49 | - _MetricValueSpan(sleepHours > 0 ? '$sleepHours' : '--', '小时'), | ||
| 50 | _MetricValueSpan( | 49 | _MetricValueSpan( |
| 51 | - sleepMinutes > 0 ? '$sleepMinutes' : '--', '分钟'), | 50 | + sleepHours > 0 ? '$sleepHours' : '--', l10n.reportUnitHour), |
| 51 | + _MetricValueSpan(sleepMinutes > 0 ? '$sleepMinutes' : '--', | ||
| 52 | + l10n.reportUnitMinute), | ||
| 52 | ], | 53 | ], |
| 53 | ), | 54 | ), |
| 54 | _MetricData( | 55 | _MetricData( |
| @@ -121,22 +122,23 @@ class TodayActivityCard extends StatelessWidget { | @@ -121,22 +122,23 @@ class TodayActivityCard extends StatelessWidget { | ||
| 121 | final exHours = exercise ~/ 3600; | 122 | final exHours = exercise ~/ 3600; |
| 122 | final exMinutes = (exercise % 3600) ~/ 60; | 123 | final exMinutes = (exercise % 3600) ~/ 60; |
| 123 | exerciseSpans = [ | 124 | exerciseSpans = [ |
| 124 | - if (exHours > 0) _MetricValueSpan('$exHours', '小时'), | 125 | + if (exHours > 0) _MetricValueSpan('$exHours', l10n.reportUnitHour), |
| 125 | if (exMinutes > 0 || exHours == 0) | 126 | if (exMinutes > 0 || exHours == 0) |
| 126 | - _MetricValueSpan(exMinutes > 0 ? '$exMinutes' : '0', '分钟'), | 127 | + _MetricValueSpan( |
| 128 | + exMinutes > 0 ? '$exMinutes' : '0', l10n.reportUnitMinute), | ||
| 127 | ]; | 129 | ]; |
| 128 | } else { | 130 | } else { |
| 129 | - exerciseSpans = [_MetricValueSpan('--', '分钟')]; | 131 | + exerciseSpans = [_MetricValueSpan('--', l10n.reportUnitMinute)]; |
| 130 | } | 132 | } |
| 131 | 133 | ||
| 132 | // stand 单位为秒,转换为小时+分钟 | 134 | // stand 单位为秒,转换为小时+分钟 |
| 133 | List<_MetricValueSpan> standSpans; | 135 | List<_MetricValueSpan> standSpans; |
| 134 | if (stand > 0 && (activityTarget?.stand ?? 0) > 0) { | 136 | if (stand > 0 && (activityTarget?.stand ?? 0) > 0) { |
| 135 | standSpans = [ | 137 | standSpans = [ |
| 136 | - _MetricValueSpan('$stand', '小时'), | 138 | + _MetricValueSpan('$stand', l10n.reportUnitHour), |
| 137 | ]; | 139 | ]; |
| 138 | } else { | 140 | } else { |
| 139 | - standSpans = [_MetricValueSpan('--', '小时')]; | 141 | + standSpans = [_MetricValueSpan('--', l10n.reportUnitHour)]; |
| 140 | } | 142 | } |
| 141 | double activityProgress = | 143 | double activityProgress = |
| 142 | (activity > 0 && (activityTarget?.move ?? 0) > 0) | 144 | (activity > 0 && (activityTarget?.move ?? 0) > 0) |
| @@ -160,7 +162,7 @@ class TodayActivityCard extends StatelessWidget { | @@ -160,7 +162,7 @@ class TodayActivityCard extends StatelessWidget { | ||
| 160 | label: context.l10n.event, | 162 | label: context.l10n.event, |
| 161 | labelColor: context.colors.warning, | 163 | labelColor: context.colors.warning, |
| 162 | valueSpans: [ | 164 | valueSpans: [ |
| 163 | - _MetricValueSpan(activityStr, '千卡'), | 165 | + _MetricValueSpan(activityStr, l10n.reportUnitKcal), |
| 164 | ], | 166 | ], |
| 165 | ), | 167 | ), |
| 166 | _MetricData( | 168 | _MetricData( |
| @@ -19,6 +19,7 @@ import 'package:doublefeel_flutter/data/models/user/user_models.dart'; | @@ -19,6 +19,7 @@ import 'package:doublefeel_flutter/data/models/user/user_models.dart'; | ||
| 19 | import 'package:doublefeel_flutter/data/models/vip/vip_info.dart'; | 19 | import 'package:doublefeel_flutter/data/models/vip/vip_info.dart'; |
| 20 | import 'package:doublefeel_flutter/core/result/app_result.dart'; | 20 | import 'package:doublefeel_flutter/core/result/app_result.dart'; |
| 21 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | 21 | import 'package:doublefeel_flutter/app/routes/app_pages.dart'; |
| 22 | +import 'package:doublefeel_flutter/core/services/loading_service.dart'; | ||
| 22 | 23 | ||
| 23 | class LoginController extends GetxController { | 24 | class LoginController extends GetxController { |
| 24 | final UserApi _userApi = Get.find<UserApi>(); | 25 | final UserApi _userApi = Get.find<UserApi>(); |
| @@ -30,6 +31,7 @@ class LoginController extends GetxController { | @@ -30,6 +31,7 @@ class LoginController extends GetxController { | ||
| 30 | 31 | ||
| 31 | final phoneController = TextEditingController(); | 32 | final phoneController = TextEditingController(); |
| 32 | final codeController = TextEditingController(); | 33 | final codeController = TextEditingController(); |
| 34 | + final codeFocusNode = FocusNode(); | ||
| 33 | 35 | ||
| 34 | final phoneInput = ''.obs; | 36 | final phoneInput = ''.obs; |
| 35 | final codeInput = ''.obs; | 37 | final codeInput = ''.obs; |
| @@ -113,6 +115,7 @@ class LoginController extends GetxController { | @@ -113,6 +115,7 @@ class LoginController extends GetxController { | ||
| 113 | _countdownTimer?.cancel(); | 115 | _countdownTimer?.cancel(); |
| 114 | phoneController.dispose(); | 116 | phoneController.dispose(); |
| 115 | codeController.dispose(); | 117 | codeController.dispose(); |
| 118 | + codeFocusNode.dispose(); | ||
| 116 | super.onClose(); | 119 | super.onClose(); |
| 117 | } | 120 | } |
| 118 | 121 | ||
| @@ -145,6 +148,7 @@ class LoginController extends GetxController { | @@ -145,6 +148,7 @@ class LoginController extends GetxController { | ||
| 145 | AppToast.show( | 148 | AppToast.show( |
| 146 | AppLocalizations.of(Get.context!)!.phoneLoginCodeSentSuccess); | 149 | AppLocalizations.of(Get.context!)!.phoneLoginCodeSentSuccess); |
| 147 | _startCountdown(); | 150 | _startCountdown(); |
| 151 | + codeFocusNode.requestFocus(); | ||
| 148 | } | 152 | } |
| 149 | } | 153 | } |
| 150 | 154 | ||
| @@ -175,27 +179,35 @@ class LoginController extends GetxController { | @@ -175,27 +179,35 @@ class LoginController extends GetxController { | ||
| 175 | } | 179 | } |
| 176 | 180 | ||
| 177 | isLoggingIn.value = true; | 181 | isLoggingIn.value = true; |
| 178 | - final loginRes = await _userApi.login( | ||
| 179 | - telephone: cleanPhone, | ||
| 180 | - verifyCode: codeInput.value, | ||
| 181 | - ); | 182 | + try { |
| 183 | + await LoadingService.instance.run(() async { | ||
| 184 | + final loginRes = await _userApi.login( | ||
| 185 | + telephone: cleanPhone, | ||
| 186 | + verifyCode: codeInput.value, | ||
| 187 | + ); | ||
| 182 | 188 | ||
| 183 | - if (loginRes is AppSuccess<LoginResponse>) { | ||
| 184 | - await _handleLoginSuccess(loginRes.data); | ||
| 185 | - await Get.find<LocalStorage>().setLastLoginMethod('phone'); | ||
| 186 | - } else { | 189 | + if (loginRes is AppSuccess<LoginResponse>) { |
| 190 | + await _handleLoginSuccess(loginRes.data); | ||
| 191 | + await Get.find<LocalStorage>().setLastLoginMethod('phone'); | ||
| 192 | + } | ||
| 193 | + }); | ||
| 194 | + } finally { | ||
| 187 | isLoggingIn.value = false; | 195 | isLoggingIn.value = false; |
| 188 | } | 196 | } |
| 189 | } | 197 | } |
| 190 | 198 | ||
| 191 | Future<void> _loginWithApple(AppleSignInModel appleModel) async { | 199 | Future<void> _loginWithApple(AppleSignInModel appleModel) async { |
| 192 | isLoggingIn.value = true; | 200 | isLoggingIn.value = true; |
| 193 | - final loginRes = await _userApi.loginWithApple(appleModel); | 201 | + try { |
| 202 | + await LoadingService.instance.run(() async { | ||
| 203 | + final loginRes = await _userApi.loginWithApple(appleModel); | ||
| 194 | 204 | ||
| 195 | - if (loginRes is AppSuccess<LoginResponse>) { | ||
| 196 | - await _handleLoginSuccess(loginRes.data, appleModel: appleModel); | ||
| 197 | - await Get.find<LocalStorage>().setLastLoginMethod('apple'); | ||
| 198 | - } else { | 205 | + if (loginRes is AppSuccess<LoginResponse>) { |
| 206 | + await _handleLoginSuccess(loginRes.data, appleModel: appleModel); | ||
| 207 | + await Get.find<LocalStorage>().setLastLoginMethod('apple'); | ||
| 208 | + } | ||
| 209 | + }); | ||
| 210 | + } finally { | ||
| 199 | isLoggingIn.value = false; | 211 | isLoggingIn.value = false; |
| 200 | } | 212 | } |
| 201 | } | 213 | } |
| @@ -284,8 +296,6 @@ class LoginController extends GetxController { | @@ -284,8 +296,6 @@ class LoginController extends GetxController { | ||
| 284 | // 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用 | 296 | // 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用 |
| 285 | await Get.find<LocalStorage>().setTermsAgreed(true); | 297 | await Get.find<LocalStorage>().setTermsAgreed(true); |
| 286 | 298 | ||
| 287 | - isLoggingIn.value = false; | ||
| 288 | - | ||
| 289 | // 根据引导完成状态决定跳转目标 | 299 | // 根据引导完成状态决定跳转目标 |
| 290 | final userId = me.id ?? 0; | 300 | final userId = me.id ?? 0; |
| 291 | if (_userAccount.hasCompletedOnboarding(userId) || | 301 | if (_userAccount.hasCompletedOnboarding(userId) || |
| @@ -301,7 +311,7 @@ class LoginController extends GetxController { | @@ -301,7 +311,7 @@ class LoginController extends GetxController { | ||
| 301 | ); | 311 | ); |
| 302 | } | 312 | } |
| 303 | } else { | 313 | } else { |
| 304 | - isLoggingIn.value = false; | 314 | + // userInfoRes 失败,外层 finally 会重置 isLoggingIn |
| 305 | } | 315 | } |
| 306 | } | 316 | } |
| 307 | } | 317 | } |
| @@ -171,6 +171,7 @@ class PhoneLoginView extends GetView<LoginController> { | @@ -171,6 +171,7 @@ class PhoneLoginView extends GetView<LoginController> { | ||
| 171 | 171 | ||
| 172 | return TextField( | 172 | return TextField( |
| 173 | controller: controller.codeController, | 173 | controller: controller.codeController, |
| 174 | + focusNode: controller.codeFocusNode, | ||
| 174 | keyboardType: TextInputType.text, | 175 | keyboardType: TextInputType.text, |
| 175 | maxLength: 8, | 176 | maxLength: 8, |
| 176 | style: TextStyle( | 177 | style: TextStyle( |
| @@ -17,126 +17,135 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | @@ -17,126 +17,135 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | ||
| 17 | @override | 17 | @override |
| 18 | Widget build(BuildContext context) { | 18 | Widget build(BuildContext context) { |
| 19 | final l10n = context.l10n; | 19 | final l10n = context.l10n; |
| 20 | - return GuideCommonScaffold( | ||
| 21 | - showGradientBg: true, | ||
| 22 | - onBackPressed: () { | ||
| 23 | - controller.onBackPressed(); | ||
| 24 | - }, | ||
| 25 | - backIcon: 'assets/images/common/ic_close.png', | ||
| 26 | - bottom: OnboardingBottomButton( | ||
| 27 | - label: controller.isFromOnboard ? l10n.continueButton : '免费兑换优惠', | ||
| 28 | - enabled: true, | ||
| 29 | - onPressed: () { | ||
| 30 | - controller.unlock(); | 20 | + return PopScope( |
| 21 | + canPop: !controller.isFromOnboard, | ||
| 22 | + onPopInvokedWithResult: (didPop, _) { | ||
| 23 | + if (!didPop) { | ||
| 24 | + controller.onBackPressed(); | ||
| 25 | + } | ||
| 31 | }, | 26 | }, |
| 32 | - ), | ||
| 33 | - child: SingleChildScrollView( | ||
| 34 | - physics: const ClampingScrollPhysics(), | ||
| 35 | - padding: EdgeInsets.fromLTRB( | ||
| 36 | - 0, | ||
| 37 | - MediaQuery.paddingOf(context).top + 4, | ||
| 38 | - 0, | ||
| 39 | - 140, | ||
| 40 | - ), | ||
| 41 | - child: Stack( | ||
| 42 | - children: [ | ||
| 43 | - IgnorePointer( | ||
| 44 | - child: Lottie.asset('assets/lottie/scatter_flowers.json'), | 27 | + child: GuideCommonScaffold( |
| 28 | + showGradientBg: true, | ||
| 29 | + onBackPressed: () { | ||
| 30 | + controller.onBackPressed(); | ||
| 31 | + }, | ||
| 32 | + backIcon: 'assets/images/common/ic_close.png', | ||
| 33 | + bottom: OnboardingBottomButton( | ||
| 34 | + label: controller.isFromOnboard | ||
| 35 | + ? l10n.continueButton | ||
| 36 | + : l10n.freeRedemptionOffer, | ||
| 37 | + enabled: true, | ||
| 38 | + onPressed: () { | ||
| 39 | + controller.unlock(); | ||
| 40 | + }, | ||
| 41 | + ), | ||
| 42 | + child: SingleChildScrollView( | ||
| 43 | + physics: const ClampingScrollPhysics(), | ||
| 44 | + padding: EdgeInsets.fromLTRB( | ||
| 45 | + 0, | ||
| 46 | + MediaQuery.paddingOf(context).top + 4, | ||
| 47 | + 0, | ||
| 48 | + 140, | ||
| 45 | ), | 49 | ), |
| 46 | - Column( | ||
| 47 | - mainAxisSize: MainAxisSize.max, | 50 | + child: Stack( |
| 48 | children: [ | 51 | children: [ |
| 49 | - Padding( | ||
| 50 | - padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 51 | - child: Column( | ||
| 52 | - children: [ | ||
| 53 | - SizedBox(height: 48), | ||
| 54 | - OnboardingTitleText(l10n.onboardingMemberTitle), | ||
| 55 | - const SizedBox(height: 12), | ||
| 56 | - OnboardingBodyText( | ||
| 57 | - l10n.onboardingMemberBody, | ||
| 58 | - fontSize: 14, | ||
| 59 | - ), | ||
| 60 | - ], | ||
| 61 | - ), | 52 | + IgnorePointer( |
| 53 | + child: Lottie.asset('assets/lottie/scatter_flowers.json'), | ||
| 62 | ), | 54 | ), |
| 63 | - const SizedBox(height: 12), | ||
| 64 | - Image.asset( | ||
| 65 | - 'assets/images/user_onboarding/ic_membership_box.png'), | ||
| 66 | - const SizedBox(height: 4), | ||
| 67 | - Obx(() { | ||
| 68 | - final yearInfo = controller.yearlyProductAppleInfo.value; | ||
| 69 | - final yearProduct = controller.yearlyProduct.value; | ||
| 70 | - | ||
| 71 | - return yearInfo == null || yearProduct == null | ||
| 72 | - ? const SizedBox(child: CircularProgressIndicator()) | ||
| 73 | - : Column( | ||
| 74 | - children: [ | ||
| 75 | - Text( | ||
| 76 | - '原价${_originDisplayPrice(yearInfo)}/年', | ||
| 77 | - textAlign: TextAlign.center, | ||
| 78 | - style: const TextStyle( | ||
| 79 | - color: Color(0xFFB0B0B6), | ||
| 80 | - fontSize: 16, | ||
| 81 | - decoration: TextDecoration.lineThrough, | ||
| 82 | - decorationColor: Color(0xFFB0B0B6), | ||
| 83 | - letterSpacing: 0, | ||
| 84 | - ), | ||
| 85 | - ), | ||
| 86 | - const SizedBox(height: 6), | ||
| 87 | - Text( | ||
| 88 | - _displayPrice(yearInfo), | ||
| 89 | - textAlign: TextAlign.center, | ||
| 90 | - style: TextStyle( | ||
| 91 | - color: context.colors.primary, | ||
| 92 | - fontSize: 28, | ||
| 93 | - fontWeight: FontWeight.w600, | ||
| 94 | - letterSpacing: 0, | ||
| 95 | - ), | ||
| 96 | - ), | ||
| 97 | - const SizedBox(height: 6), | ||
| 98 | - Text( | ||
| 99 | - _planSubtitle(yearProduct, yearInfo), | ||
| 100 | - textAlign: TextAlign.center, | ||
| 101 | - style: const TextStyle( | ||
| 102 | - color: Color(0xFFB0B0B6), | ||
| 103 | - fontSize: 12, | ||
| 104 | - letterSpacing: 0, | ||
| 105 | - ), | ||
| 106 | - ), | ||
| 107 | - ], | ||
| 108 | - ); | ||
| 109 | - }), | ||
| 110 | - const SizedBox(height: 58), | ||
| 111 | - GestureDetector( | ||
| 112 | - behavior: HitTestBehavior.opaque, | ||
| 113 | - onTap: () { | ||
| 114 | - Get.offNamed(Routes.PURCHASE); | ||
| 115 | - }, | ||
| 116 | - child: Padding( | ||
| 117 | - padding: const EdgeInsets.symmetric( | ||
| 118 | - vertical: 12, | ||
| 119 | - horizontal: 24, | 55 | + Column( |
| 56 | + mainAxisSize: MainAxisSize.max, | ||
| 57 | + children: [ | ||
| 58 | + Padding( | ||
| 59 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 60 | + child: Column( | ||
| 61 | + children: [ | ||
| 62 | + SizedBox(height: 48), | ||
| 63 | + OnboardingTitleText(l10n.onboardingMemberTitle), | ||
| 64 | + const SizedBox(height: 12), | ||
| 65 | + OnboardingBodyText( | ||
| 66 | + l10n.onboardingMemberBody, | ||
| 67 | + fontSize: 14, | ||
| 68 | + ), | ||
| 69 | + ], | ||
| 70 | + ), | ||
| 120 | ), | 71 | ), |
| 121 | - child: Text( | ||
| 122 | - l10n.onboardingMemberAllOptions, | ||
| 123 | - textAlign: TextAlign.center, | ||
| 124 | - style: TextStyle( | ||
| 125 | - color: context.colors.textPrimary, | ||
| 126 | - fontSize: 12, | ||
| 127 | - decoration: TextDecoration.underline, | ||
| 128 | - decorationColor: context.colors.textPrimary, | ||
| 129 | - letterSpacing: 0, | 72 | + const SizedBox(height: 12), |
| 73 | + Image.asset( | ||
| 74 | + 'assets/images/user_onboarding/ic_membership_box.png'), | ||
| 75 | + const SizedBox(height: 4), | ||
| 76 | + Obx(() { | ||
| 77 | + final yearInfo = controller.yearlyProductAppleInfo.value; | ||
| 78 | + final yearProduct = controller.yearlyProduct.value; | ||
| 79 | + | ||
| 80 | + return yearInfo == null || yearProduct == null | ||
| 81 | + ? const SizedBox(child: CircularProgressIndicator()) | ||
| 82 | + : Column( | ||
| 83 | + children: [ | ||
| 84 | + Text( | ||
| 85 | + '原价${_originDisplayPrice(yearInfo)}/年', | ||
| 86 | + textAlign: TextAlign.center, | ||
| 87 | + style: const TextStyle( | ||
| 88 | + color: Color(0xFFB0B0B6), | ||
| 89 | + fontSize: 16, | ||
| 90 | + decoration: TextDecoration.lineThrough, | ||
| 91 | + decorationColor: Color(0xFFB0B0B6), | ||
| 92 | + letterSpacing: 0, | ||
| 93 | + ), | ||
| 94 | + ), | ||
| 95 | + const SizedBox(height: 6), | ||
| 96 | + Text( | ||
| 97 | + _displayPrice(yearInfo), | ||
| 98 | + textAlign: TextAlign.center, | ||
| 99 | + style: TextStyle( | ||
| 100 | + color: context.colors.primary, | ||
| 101 | + fontSize: 28, | ||
| 102 | + fontWeight: FontWeight.w600, | ||
| 103 | + letterSpacing: 0, | ||
| 104 | + ), | ||
| 105 | + ), | ||
| 106 | + const SizedBox(height: 6), | ||
| 107 | + Text( | ||
| 108 | + _planSubtitle(yearProduct, yearInfo), | ||
| 109 | + textAlign: TextAlign.center, | ||
| 110 | + style: const TextStyle( | ||
| 111 | + color: Color(0xFFB0B0B6), | ||
| 112 | + fontSize: 12, | ||
| 113 | + letterSpacing: 0, | ||
| 114 | + ), | ||
| 115 | + ), | ||
| 116 | + ], | ||
| 117 | + ); | ||
| 118 | + }), | ||
| 119 | + const SizedBox(height: 58), | ||
| 120 | + GestureDetector( | ||
| 121 | + behavior: HitTestBehavior.opaque, | ||
| 122 | + onTap: () { | ||
| 123 | + Get.offNamed(Routes.PURCHASE); | ||
| 124 | + }, | ||
| 125 | + child: Padding( | ||
| 126 | + padding: const EdgeInsets.symmetric( | ||
| 127 | + vertical: 12, | ||
| 128 | + horizontal: 24, | ||
| 129 | + ), | ||
| 130 | + child: Text( | ||
| 131 | + l10n.onboardingMemberAllOptions, | ||
| 132 | + textAlign: TextAlign.center, | ||
| 133 | + style: TextStyle( | ||
| 134 | + color: context.colors.textPrimary, | ||
| 135 | + fontSize: 12, | ||
| 136 | + decoration: TextDecoration.underline, | ||
| 137 | + decorationColor: context.colors.textPrimary, | ||
| 138 | + letterSpacing: 0, | ||
| 139 | + ), | ||
| 140 | + ), | ||
| 130 | ), | 141 | ), |
| 131 | ), | 142 | ), |
| 132 | - ), | 143 | + ], |
| 133 | ), | 144 | ), |
| 134 | ], | 145 | ], |
| 135 | ), | 146 | ), |
| 136 | - ], | ||
| 137 | - ), | ||
| 138 | - ), | ||
| 139 | - ); | 147 | + ), |
| 148 | + )); | ||
| 140 | } | 149 | } |
| 141 | 150 | ||
| 142 | String _originDisplayPrice(AppleProductInfo appleInfo) { | 151 | String _originDisplayPrice(AppleProductInfo appleInfo) { |
| @@ -53,9 +53,15 @@ class PurchaseController extends GetxController { | @@ -53,9 +53,15 @@ class PurchaseController extends GetxController { | ||
| 53 | final isUnlocking = false.obs; | 53 | final isUnlocking = false.obs; |
| 54 | final isRestoring = false.obs; | 54 | final isRestoring = false.obs; |
| 55 | 55 | ||
| 56 | + bool isFromOnboard = false; | ||
| 57 | + | ||
| 56 | @override | 58 | @override |
| 57 | void onInit() { | 59 | void onInit() { |
| 58 | super.onInit(); | 60 | super.onInit(); |
| 61 | + final args = Get.arguments; | ||
| 62 | + if (args is Map) { | ||
| 63 | + isFromOnboard = args['from'] == 'onboarding'; | ||
| 64 | + } | ||
| 59 | scrollController.addListener(_handleScroll); | 65 | scrollController.addListener(_handleScroll); |
| 60 | unawaited(loadProductList()); | 66 | unawaited(loadProductList()); |
| 61 | } | 67 | } |
| @@ -69,6 +75,17 @@ class PurchaseController extends GetxController { | @@ -69,6 +75,17 @@ class PurchaseController extends GetxController { | ||
| 69 | super.onClose(); | 75 | super.onClose(); |
| 70 | } | 76 | } |
| 71 | 77 | ||
| 78 | + void onBackPressed() { | ||
| 79 | + if (isFromOnboard) { | ||
| 80 | + Get.offNamed( | ||
| 81 | + Routes.MEMBERSHIP_OFFER, | ||
| 82 | + arguments: {'from': 'onboarding'}, | ||
| 83 | + ); | ||
| 84 | + } else { | ||
| 85 | + Get.back(); | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + | ||
| 72 | void _handleScroll() { | 89 | void _handleScroll() { |
| 73 | final next = scrollController.hasClients && scrollController.offset > 250; | 90 | final next = scrollController.hasClients && scrollController.offset > 250; |
| 74 | if (next != isScrolled.value) { | 91 | if (next != isScrolled.value) { |
| @@ -223,7 +240,12 @@ class PurchaseController extends GetxController { | @@ -223,7 +240,12 @@ class PurchaseController extends GetxController { | ||
| 223 | try { | 240 | try { |
| 224 | await _refreshVipInfo(); | 241 | await _refreshVipInfo(); |
| 225 | } finally { | 242 | } finally { |
| 226 | - Get.offNamed(Routes.PREMIUM_ACTIVATED); | 243 | + if (isFromOnboard) { |
| 244 | + // onboarding 流程购买成功,清空栈进入首页 | ||
| 245 | + Get.offAllNamed(AppRoutes.home); | ||
| 246 | + } else { | ||
| 247 | + Get.offNamed(Routes.PREMIUM_ACTIVATED); | ||
| 248 | + } | ||
| 227 | } | 249 | } |
| 228 | } | 250 | } |
| 229 | 251 |
| @@ -17,16 +17,21 @@ class PurchaseView extends GetView<PurchaseController> { | @@ -17,16 +17,21 @@ class PurchaseView extends GetView<PurchaseController> { | ||
| 17 | 17 | ||
| 18 | @override | 18 | @override |
| 19 | Widget build(BuildContext context) { | 19 | Widget build(BuildContext context) { |
| 20 | - return AnnotatedRegion<SystemUiOverlayStyle>( | ||
| 21 | - value: const SystemUiOverlayStyle( | ||
| 22 | - statusBarColor: Colors.transparent, | ||
| 23 | - statusBarIconBrightness: Brightness.dark, | ||
| 24 | - statusBarBrightness: Brightness.light, | ||
| 25 | - systemNavigationBarColor: PurchaseColors.background, | ||
| 26 | - systemNavigationBarIconBrightness: Brightness.dark, | ||
| 27 | - ), | ||
| 28 | - child: Scaffold( | ||
| 29 | - backgroundColor: PurchaseColors.background, | 20 | + return PopScope( |
| 21 | + canPop: !controller.isFromOnboard, | ||
| 22 | + onPopInvokedWithResult: (didPop, _) { | ||
| 23 | + if (!didPop) controller.onBackPressed(); | ||
| 24 | + }, | ||
| 25 | + child: AnnotatedRegion<SystemUiOverlayStyle>( | ||
| 26 | + value: const SystemUiOverlayStyle( | ||
| 27 | + statusBarColor: Colors.transparent, | ||
| 28 | + statusBarIconBrightness: Brightness.dark, | ||
| 29 | + statusBarBrightness: Brightness.light, | ||
| 30 | + systemNavigationBarColor: PurchaseColors.background, | ||
| 31 | + systemNavigationBarIconBrightness: Brightness.dark, | ||
| 32 | + ), | ||
| 33 | + child: Scaffold( | ||
| 34 | + backgroundColor: PurchaseColors.background, | ||
| 30 | body: Stack( | 35 | body: Stack( |
| 31 | children: [ | 36 | children: [ |
| 32 | Positioned.fill( | 37 | Positioned.fill( |
| @@ -85,7 +90,7 @@ class PurchaseView extends GetView<PurchaseController> { | @@ -85,7 +90,7 @@ class PurchaseView extends GetView<PurchaseController> { | ||
| 85 | ], | 90 | ], |
| 86 | ), | 91 | ), |
| 87 | ), | 92 | ), |
| 88 | - ); | 93 | + )); |
| 89 | } | 94 | } |
| 90 | } | 95 | } |
| 91 | 96 | ||
| @@ -190,7 +195,7 @@ class _PurchaseNavigationBar extends StatelessWidget { | @@ -190,7 +195,7 @@ class _PurchaseNavigationBar extends StatelessWidget { | ||
| 190 | children: [ | 195 | children: [ |
| 191 | const SizedBox(width: 4), | 196 | const SizedBox(width: 4), |
| 192 | IconButton( | 197 | IconButton( |
| 193 | - onPressed: Get.back, | 198 | + onPressed: controller.onBackPressed, |
| 194 | icon: Image.asset( | 199 | icon: Image.asset( |
| 195 | showCloseButton | 200 | showCloseButton |
| 196 | ? R.assetsImagesCloseIcon | 201 | ? R.assetsImagesCloseIcon |
| @@ -274,12 +274,6 @@ class UserOnboardingController extends GetxController { | @@ -274,12 +274,6 @@ class UserOnboardingController extends GetxController { | ||
| 274 | return; | 274 | return; |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | - if (status.isDenied && GetPlatform.isIOS) { | ||
| 278 | - // iOS 上被拒绝后无法再弹窗,引导去设置 | ||
| 279 | - await openAppSettings(); | ||
| 280 | - return; | ||
| 281 | - } | ||
| 282 | - | ||
| 283 | // 未决定 → 弹出系统授权弹窗 | 277 | // 未决定 → 弹出系统授权弹窗 |
| 284 | final result = await Permission.notification.request(); | 278 | final result = await Permission.notification.request(); |
| 285 | AppLogger.d(result.isGranted | 279 | AppLogger.d(result.isGranted |
| @@ -50,7 +50,7 @@ List<Widget> buildUserOnboardingPages(BuildContext context, | @@ -50,7 +50,7 @@ List<Widget> buildUserOnboardingPages(BuildContext context, | ||
| 50 | _SelectionPage( | 50 | _SelectionPage( |
| 51 | pageIndex: 2, | 51 | pageIndex: 2, |
| 52 | title: l10n.onboardingStressGoalQuestion, | 52 | title: l10n.onboardingStressGoalQuestion, |
| 53 | - multiple: false, | 53 | + multiple: true, |
| 54 | options: [ | 54 | options: [ |
| 55 | OnboardingOptionData( | 55 | OnboardingOptionData( |
| 56 | label: l10n.onboardingStressGoalSource, | 56 | label: l10n.onboardingStressGoalSource, |
| @@ -353,7 +353,7 @@ class _HrvResearchPageState extends State<_HrvResearchPage> { | @@ -353,7 +353,7 @@ class _HrvResearchPageState extends State<_HrvResearchPage> { | ||
| 353 | 353 | ||
| 354 | void _startAutoScroll() { | 354 | void _startAutoScroll() { |
| 355 | _autoScrollTimer?.cancel(); | 355 | _autoScrollTimer?.cancel(); |
| 356 | - _autoScrollTimer = Timer.periodic(const Duration(seconds: 3), (_) { | 356 | + _autoScrollTimer = Timer.periodic(const Duration(seconds: 2), (_) { |
| 357 | if (!mounted || _userScrolling) return; | 357 | if (!mounted || _userScrolling) return; |
| 358 | if (_pageController.hasClients) { | 358 | if (_pageController.hasClients) { |
| 359 | final next = (_pageController.page?.round() ?? _initialPage) + 1; | 359 | final next = (_pageController.page?.round() ?? _initialPage) + 1; |
lib/core/services/loading_service.dart
0 → 100644
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | +import 'package:get/get.dart'; | ||
| 3 | +import 'package:lottie/lottie.dart'; | ||
| 4 | + | ||
| 5 | +class LoadingService { | ||
| 6 | + OverlayEntry? _entry; | ||
| 7 | + int _counter = 0; | ||
| 8 | + | ||
| 9 | + static void init() { | ||
| 10 | + Get.put(LoadingService()); | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + static LoadingService get instance => Get.find(); | ||
| 14 | + | ||
| 15 | + /// 包装一个异步操作,执行期间显示 Loading,完成后自动隐藏。 | ||
| 16 | + Future<T> run<T>(Future<T> Function() block) async { | ||
| 17 | + show(); | ||
| 18 | + try { | ||
| 19 | + return await block(); | ||
| 20 | + } finally { | ||
| 21 | + hide(); | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + void show() { | ||
| 26 | + _counter++; | ||
| 27 | + if (_counter > 1) return; | ||
| 28 | + | ||
| 29 | + // 直接从根 Navigator 获取 Overlay,避免 context 查找失败 | ||
| 30 | + final overlay = Get.key.currentState?.overlay; | ||
| 31 | + if (overlay == null) { | ||
| 32 | + _counter--; | ||
| 33 | + return; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + // 防御:清理可能残留的旧 entry | ||
| 37 | + _entry?.remove(); | ||
| 38 | + _entry = OverlayEntry(builder: (_) => const _GlobalLoadingWidget()); | ||
| 39 | + overlay.insert(_entry!); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + void hide() { | ||
| 43 | + if (_counter <= 0) return; | ||
| 44 | + _counter--; | ||
| 45 | + if (_counter == 0) { | ||
| 46 | + _entry?.remove(); | ||
| 47 | + _entry = null; | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +class _GlobalLoadingWidget extends StatelessWidget { | ||
| 53 | + const _GlobalLoadingWidget(); | ||
| 54 | + | ||
| 55 | + @override | ||
| 56 | + Widget build(BuildContext context) { | ||
| 57 | + // GestureDetector 吸收所有点击,防止穿透触发底层 UI | ||
| 58 | + return GestureDetector( | ||
| 59 | + behavior: HitTestBehavior.opaque, | ||
| 60 | + onTap: () {}, | ||
| 61 | + child: SizedBox.expand( | ||
| 62 | + child: ColoredBox( | ||
| 63 | + color: Colors.black.withValues(alpha: 0.3), | ||
| 64 | + child: Align( | ||
| 65 | + alignment: const Alignment(0.0, -0.1), | ||
| 66 | + child: Lottie.asset( | ||
| 67 | + 'assets/lottie/loading.json', | ||
| 68 | + width: 130, | ||
| 69 | + height: 90, | ||
| 70 | + repeat: true, | ||
| 71 | + animate: true, | ||
| 72 | + ), | ||
| 73 | + ), | ||
| 74 | + ), | ||
| 75 | + ), | ||
| 76 | + ); | ||
| 77 | + } | ||
| 78 | +} |
| 1 | import 'dart:ui'; | 1 | import 'dart:ui'; |
| 2 | 2 | ||
| 3 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 3 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 4 | +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | ||
| 4 | import 'package:flutter/src/widgets/framework.dart'; | 5 | import 'package:flutter/src/widgets/framework.dart'; |
| 5 | 6 | ||
| 6 | int? _parseInt(dynamic value) { | 7 | int? _parseInt(dynamic value) { |
| @@ -36,15 +37,15 @@ class V2LatestHrvData { | @@ -36,15 +37,15 @@ class V2LatestHrvData { | ||
| 36 | String stateString() { | 37 | String stateString() { |
| 37 | switch (state) { | 38 | switch (state) { |
| 38 | case 1: | 39 | case 1: |
| 39 | - return '压力过载'; | 40 | + return l10n.pressureOverload; |
| 40 | case 2: | 41 | case 2: |
| 41 | - return '注意压力'; | 42 | + return l10n.beMindfulOfStress; |
| 42 | case 3: | 43 | case 3: |
| 43 | - return '状态正常'; | 44 | + return l10n.statusNormal; |
| 44 | case 4: | 45 | case 4: |
| 45 | - return '状态优秀'; | 46 | + return l10n.inExcellentCondition; |
| 46 | } | 47 | } |
| 47 | - return '等待数据'; | 48 | + return l10n.waitingForData; |
| 48 | } | 49 | } |
| 49 | 50 | ||
| 50 | stateColor(BuildContext context) { | 51 | stateColor(BuildContext context) { |
| @@ -187,11 +188,11 @@ class V2HealthData { | @@ -187,11 +188,11 @@ class V2HealthData { | ||
| 187 | sleepStateStr() { | 188 | sleepStateStr() { |
| 188 | switch (sleepState) { | 189 | switch (sleepState) { |
| 189 | case 1: | 190 | case 1: |
| 190 | - return '优秀'; | 191 | + return l10n.sleepQualityExcellent; |
| 191 | case 2: | 192 | case 2: |
| 192 | - return '正常'; | 193 | + return l10n.sleepQualityNormal; |
| 193 | case 3: | 194 | case 3: |
| 194 | - return '注意'; | 195 | + return l10n.sleepQualityAttention; |
| 195 | } | 196 | } |
| 196 | return '--'; | 197 | return '--'; |
| 197 | } | 198 | } |
| @@ -238,15 +239,15 @@ class V2StressScore { | @@ -238,15 +239,15 @@ class V2StressScore { | ||
| 238 | String stateString() { | 239 | String stateString() { |
| 239 | switch (state) { | 240 | switch (state) { |
| 240 | case 1: | 241 | case 1: |
| 241 | - return '压力过载'; | 242 | + return l10n.pressureOverload; |
| 242 | case 2: | 243 | case 2: |
| 243 | - return '注意压力'; | 244 | + return l10n.beMindfulOfStress; |
| 244 | case 3: | 245 | case 3: |
| 245 | - return '状态正常'; | 246 | + return l10n.statusNormal; |
| 246 | case 4: | 247 | case 4: |
| 247 | - return '状态优秀'; | 248 | + return l10n.inExcellentCondition; |
| 248 | } | 249 | } |
| 249 | - return '等待数据'; | 250 | + return l10n.waitingForData; |
| 250 | } | 251 | } |
| 251 | 252 | ||
| 252 | String stateBg() { | 253 | String stateBg() { |
| @@ -250,6 +250,7 @@ | @@ -250,6 +250,7 @@ | ||
| 250 | "submit": "Submit", | 250 | "submit": "Submit", |
| 251 | "questionsAndFeedback": "Questions and Feedback", | 251 | "questionsAndFeedback": "Questions and Feedback", |
| 252 | "ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress": "If you would like us to reply, please provide your email address", | 252 | "ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress": "If you would like us to reply, please provide your email address", |
| 253 | + "feedbackHintText": "1. Please describe the screen and scenario where the issue occurred\n2. Please provide screenshots to help us resolve the issue more efficiently\n3. Please leave your contact information so we can get back to you as soon as possible", | ||
| 253 | "uploadProof": "Upload Proof", | 254 | "uploadProof": "Upload Proof", |
| 254 | "frequentlyAskedQuestions": "Frequently Asked Questions", | 255 | "frequentlyAskedQuestions": "Frequently Asked Questions", |
| 255 | "areYouSureYouWantToDeleteYourAccount": "Are you sure you want to delete your account?", | 256 | "areYouSureYouWantToDeleteYourAccount": "Are you sure you want to delete your account?", |
| @@ -257,6 +258,14 @@ | @@ -257,6 +258,14 @@ | ||
| 257 | "deleteAccount": "Delete Account", | 258 | "deleteAccount": "Delete Account", |
| 258 | "mobilePhoneNumber": "Mobile phone number", | 259 | "mobilePhoneNumber": "Mobile phone number", |
| 259 | "logOut": "Log Out", | 260 | "logOut": "Log Out", |
| 261 | + "confirmLogoutPrompt": "Are you sure you want to log out?", | ||
| 262 | + "confirmLogout": "Log Out", | ||
| 263 | + "loggedOutSuccessfully": "Logged out successfully", | ||
| 264 | + "accountDeletedSuccessfully": "Account deleted successfully", | ||
| 265 | + "deleteAccountWarningTitle": "Once deleted, the account cannot be recovered! Please proceed with caution.", | ||
| 266 | + "deleteAccountWarningPrompt": "Note: Deleting the account will remove all information in this account, including but not limited to\npersonal profile, mood records, and statistical data.", | ||
| 267 | + "deleteAccountWarningNote1": "Note 1: Your health data will be saved in Apple Health, and we will not delete data from Apple Health.", | ||
| 268 | + "deleteAccountWarningNote2": "Note 2: Deleting the account will not affect your subscription status in the App Store. If you need to cancel the subscription, please cancel it manually in the App Store -> Profile -> Subscriptions.", | ||
| 260 | "confirmDeletion": "Confirm Deletion", | 269 | "confirmDeletion": "Confirm Deletion", |
| 261 | "iLlThinkAboutItSomeMore": "I'll think about it some more.", | 270 | "iLlThinkAboutItSomeMore": "I'll think about it some more.", |
| 262 | "sleep": "Sleep", | 271 | "sleep": "Sleep", |
| @@ -494,5 +503,62 @@ | @@ -494,5 +503,62 @@ | ||
| 494 | "appReviewFeedbackMessage": "Would you tell us what went wrong?\nYour feedback helps us improve the stress and health experience. 💜", | 503 | "appReviewFeedbackMessage": "Would you tell us what went wrong?\nYour feedback helps us improve the stress and health experience. 💜", |
| 495 | "appReviewFeedbackSendAction": "Send Feedback", | 504 | "appReviewFeedbackSendAction": "Send Feedback", |
| 496 | "appReviewFeedbackLaterAction": "Maybe Later", | 505 | "appReviewFeedbackLaterAction": "Maybe Later", |
| 497 | - "appReviewIllustrationPlaceholder": "Illustration Placeholder" | ||
| 498 | -} | 506 | + "appReviewIllustrationPlaceholder": "Illustration Placeholder", |
| 507 | + "overallStressLevelToday": "Overall Stress Level Today", | ||
| 508 | + "stressLevelsOnThatDay": "Stress Levels on That Day", | ||
| 509 | + "noPressureDataAvailableAtThisTime": "No pressure data available at this time", | ||
| 510 | + "membersCanViewTheCompleteData": "Members can view the complete data", | ||
| 511 | + "unlockNow": "Unlock Now", | ||
| 512 | + "pressureOverload": "Pressure Overload", | ||
| 513 | + "beMindfulOfStress": "Be Aware of Stress", | ||
| 514 | + "statusNormal": "Status: Normal", | ||
| 515 | + "inExcellentCondition": "In excellent condition", | ||
| 516 | + "waitingForData": "Waiting for data", | ||
| 517 | + "pressure": "Pressure", | ||
| 518 | + "mostRecent": "Most recent", | ||
| 519 | + "theDayBeforeYesterday": "The day before yesterday", | ||
| 520 | + "uploadPhotos": "Upload Photos", | ||
| 521 | + "filming": "Filming", | ||
| 522 | + "unlockTheProVersion": "Unlock the Pro Version", | ||
| 523 | + "embarkOnAJourneyOfStressAwarenessAndWellnessSupport": "Embark on a Journey of Stress Awareness and Wellness Support", | ||
| 524 | + "sharePartnerCodeTemplate": "Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other's health in real-time.\nCome join me 👉 https://doublefeel.cn/\nMy friend ID: {inviteCode}", | ||
| 525 | + "@sharePartnerCodeTemplate": { | ||
| 526 | + "description": "Share partner code template", | ||
| 527 | + "placeholders": { | ||
| 528 | + "inviteCode": { | ||
| 529 | + "type": "String" | ||
| 530 | + } | ||
| 531 | + } | ||
| 532 | + }, | ||
| 533 | + "bindPartnerIdNotExistTitle": "ID does not exist", | ||
| 534 | + "bindPartnerIdNotExistMessage": "This ID does not exist. Please check and try again", | ||
| 535 | + "bindPartnerDialogGotIt": "Got it", | ||
| 536 | + "bindPartnerAddFailedTitle": "Failed to add", | ||
| 537 | + "bindPartnerAddFailedMessage": "This user does not allow adding friends, cannot add them", | ||
| 538 | + "bindPartnerAlreadyFriendTitle": "They are already your friend", | ||
| 539 | + "bindPartnerAlreadyFriendMessage": "Please do not add again", | ||
| 540 | + "friendStatusTitle": "{remarkName}'s Status", | ||
| 541 | + "@friendStatusTitle": { | ||
| 542 | + "description": "Friend status title", | ||
| 543 | + "placeholders": { | ||
| 544 | + "remarkName": { | ||
| 545 | + "type": "String" | ||
| 546 | + } | ||
| 547 | + } | ||
| 548 | + }, | ||
| 549 | + "annualMemberDiscounts": "Annual Member Discounts", | ||
| 550 | + "specialOffers": "Special Offers", | ||
| 551 | + "currentPrice": "Current Price", | ||
| 552 | + "originalPrice": "Original price {price}", | ||
| 553 | + "@originalPrice": { | ||
| 554 | + "description": "Original price label", | ||
| 555 | + "placeholders": { | ||
| 556 | + "price": { | ||
| 557 | + "type": "String" | ||
| 558 | + } | ||
| 559 | + } | ||
| 560 | + }, | ||
| 561 | + "freeRedemptionOffer": "Free Redemption Offer", | ||
| 562 | + "cellPhoneNumber": "Cell phone number", | ||
| 563 | + "todayOnWeeklyCalendar": "today" | ||
| 564 | +} |
| @@ -333,6 +333,7 @@ | @@ -333,6 +333,7 @@ | ||
| 333 | "submit": "提交", | 333 | "submit": "提交", |
| 334 | "questionsAndFeedback": "问题和反馈", | 334 | "questionsAndFeedback": "问题和反馈", |
| 335 | "ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress": "如果需要我们回复,请填写联系邮箱", | 335 | "ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress": "如果需要我们回复,请填写联系邮箱", |
| 336 | + "feedbackHintText": "1.请说明问题的界面和使用场景\n2.请提供截图,以便提升解决问题效率\n3.请留下联系方式,我们尽快回复联系您", | ||
| 336 | "uploadProof": "上传凭证", | 337 | "uploadProof": "上传凭证", |
| 337 | "frequentlyAskedQuestions": "常见问题", | 338 | "frequentlyAskedQuestions": "常见问题", |
| 338 | "areYouSureYouWantToDeleteYourAccount": "确认注销账号吗?", | 339 | "areYouSureYouWantToDeleteYourAccount": "确认注销账号吗?", |
| @@ -340,6 +341,14 @@ | @@ -340,6 +341,14 @@ | ||
| 340 | "deleteAccount": "注销账号", | 341 | "deleteAccount": "注销账号", |
| 341 | "mobilePhoneNumber": "手机号", | 342 | "mobilePhoneNumber": "手机号", |
| 342 | "logOut": "退出登录", | 343 | "logOut": "退出登录", |
| 344 | + "confirmLogoutPrompt": "确认退出登录吗?", | ||
| 345 | + "confirmLogout": "确认退出", | ||
| 346 | + "loggedOutSuccessfully": "已退出登录", | ||
| 347 | + "accountDeletedSuccessfully": "账号注销成功", | ||
| 348 | + "deleteAccountWarningTitle": "注销账号后将无法找回!请谨慎操作", | ||
| 349 | + "deleteAccountWarningPrompt": "提示:注销账号将会删除该账号内包括但不限于\n个人资料、情绪记录、统计数据等全部信息。", | ||
| 350 | + "deleteAccountWarningNote1": "注1:你的健康数据会保存在苹果健康,我们不会删除苹果健康中的数据。", | ||
| 351 | + "deleteAccountWarningNote2": "注2:删除账号不会影响你在App Store的订阅状态,如果需要取消订阅,请在APPs Store - 头像 - 订阅中手动取消订阅。", | ||
| 343 | "confirmDeletion": "确认注销", | 352 | "confirmDeletion": "确认注销", |
| 344 | "iLlThinkAboutItSomeMore": "我再想想", | 353 | "iLlThinkAboutItSomeMore": "我再想想", |
| 345 | "sleep": "睡眠", | 354 | "sleep": "睡眠", |
| @@ -741,6 +750,9 @@ | @@ -741,6 +750,9 @@ | ||
| 741 | "purchaseNoteContact": "如果有更多其他问题,请联系我们。", | 750 | "purchaseNoteContact": "如果有更多其他问题,请联系我们。", |
| 742 | "reportBottomSlogan": "双倍感知 压力减半", | 751 | "reportBottomSlogan": "双倍感知 压力减半", |
| 743 | "refundExplanationTitle": "退费说明", | 752 | "refundExplanationTitle": "退费说明", |
| 753 | + "@refundExplanationTitle": { | ||
| 754 | + "description": "退费说明" | ||
| 755 | + }, | ||
| 744 | "refundAppStoreReviewTitle": "退款由 App Store 审核处理", | 756 | "refundAppStoreReviewTitle": "退款由 App Store 审核处理", |
| 745 | "refundAppStoreReviewDescription": "所有订阅与虚拟商品均通过 App Store 官方支付系统完成,DoubleFeel 无法直接处理付款或退款。", | 757 | "refundAppStoreReviewDescription": "所有订阅与虚拟商品均通过 App Store 官方支付系统完成,DoubleFeel 无法直接处理付款或退款。", |
| 746 | "refundAppleRulesIntroduction": "根据 Apple 的平台规则:", | 758 | "refundAppleRulesIntroduction": "根据 Apple 的平台规则:", |
| @@ -779,5 +791,62 @@ | @@ -779,5 +791,62 @@ | ||
| 779 | "appReviewFeedbackMessage": "愿意告诉我们遇到了什么问题吗?\n你的反馈可以帮助我们持续改进压力与健康体验 💜", | 791 | "appReviewFeedbackMessage": "愿意告诉我们遇到了什么问题吗?\n你的反馈可以帮助我们持续改进压力与健康体验 💜", |
| 780 | "appReviewFeedbackSendAction": "发送反馈", | 792 | "appReviewFeedbackSendAction": "发送反馈", |
| 781 | "appReviewFeedbackLaterAction": "稍后再说", | 793 | "appReviewFeedbackLaterAction": "稍后再说", |
| 782 | - "appReviewIllustrationPlaceholder": "插图占位" | ||
| 783 | -} | 794 | + "appReviewIllustrationPlaceholder": "插图占位", |
| 795 | + "overallStressLevelToday": "今日的综合压力状态", | ||
| 796 | + "stressLevelsOnThatDay": "该日压力状态", | ||
| 797 | + "noPressureDataAvailableAtThisTime": "暂无压力数据", | ||
| 798 | + "membersCanViewTheCompleteData": "会员可查看完整数据", | ||
| 799 | + "unlockNow": "立即解锁", | ||
| 800 | + "pressureOverload": "压力过载", | ||
| 801 | + "beMindfulOfStress": "注意压力", | ||
| 802 | + "statusNormal": "状态正常", | ||
| 803 | + "inExcellentCondition": "状态优秀", | ||
| 804 | + "waitingForData": "等待数据", | ||
| 805 | + "pressure": "压力", | ||
| 806 | + "mostRecent": "最近一次", | ||
| 807 | + "theDayBeforeYesterday": "前天", | ||
| 808 | + "uploadPhotos": "相册上传", | ||
| 809 | + "filming": "拍摄", | ||
| 810 | + "unlockTheProVersion": "解锁专业版", | ||
| 811 | + "embarkOnAJourneyOfStressAwarenessAndWellnessSupport": "开启压力预警与健康陪伴之旅", | ||
| 812 | + "sharePartnerCodeTemplate": "嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:{inviteCode}", | ||
| 813 | + "@sharePartnerCodeTemplate": { | ||
| 814 | + "description": "分享好友邀请码文案模板", | ||
| 815 | + "placeholders": { | ||
| 816 | + "inviteCode": { | ||
| 817 | + "type": "String" | ||
| 818 | + } | ||
| 819 | + } | ||
| 820 | + }, | ||
| 821 | + "bindPartnerIdNotExistTitle": "该ID不存在", | ||
| 822 | + "bindPartnerIdNotExistMessage": "这个ID不存在哦,请检查后重新输入", | ||
| 823 | + "bindPartnerDialogGotIt": "我知道了", | ||
| 824 | + "bindPartnerAddFailedTitle": "添加失败", | ||
| 825 | + "bindPartnerAddFailedMessage": "该用户不允许被添加好友,无法添加Ta哦", | ||
| 826 | + "bindPartnerAlreadyFriendTitle": "对方已经是你的好友啦", | ||
| 827 | + "bindPartnerAlreadyFriendMessage": "请不要重复添加哦", | ||
| 828 | + "friendStatusTitle": "{remarkName}的状态", | ||
| 829 | + "@friendStatusTitle": { | ||
| 830 | + "description": "好友的状态标题", | ||
| 831 | + "placeholders": { | ||
| 832 | + "remarkName": { | ||
| 833 | + "type": "String" | ||
| 834 | + } | ||
| 835 | + } | ||
| 836 | + }, | ||
| 837 | + "annualMemberDiscounts": "年度会员优惠", | ||
| 838 | + "specialOffers": "优惠", | ||
| 839 | + "currentPrice": "现价", | ||
| 840 | + "originalPrice": "原价{price}", | ||
| 841 | + "@originalPrice": { | ||
| 842 | + "description": "原价标签", | ||
| 843 | + "placeholders": { | ||
| 844 | + "price": { | ||
| 845 | + "type": "String" | ||
| 846 | + } | ||
| 847 | + } | ||
| 848 | + }, | ||
| 849 | + "freeRedemptionOffer": "免费兑换优惠", | ||
| 850 | + "cellPhoneNumber": "手机号", | ||
| 851 | + "todayOnWeeklyCalendar": "今" | ||
| 852 | +} |
| @@ -1599,6 +1599,12 @@ abstract class AppLocalizations { | @@ -1599,6 +1599,12 @@ abstract class AppLocalizations { | ||
| 1599 | /// **'如果需要我们回复,请填写联系邮箱'** | 1599 | /// **'如果需要我们回复,请填写联系邮箱'** |
| 1600 | String get ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress; | 1600 | String get ifYouWouldLikeUsToReplyPleaseProvideYourEmailAddress; |
| 1601 | 1601 | ||
| 1602 | + /// No description provided for @feedbackHintText. | ||
| 1603 | + /// | ||
| 1604 | + /// In zh, this message translates to: | ||
| 1605 | + /// **'1.请说明问题的界面和使用场景\n2.请提供截图,以便提升解决问题效率\n3.请留下联系方式,我们尽快回复联系您'** | ||
| 1606 | + String get feedbackHintText; | ||
| 1607 | + | ||
| 1602 | /// No description provided for @uploadProof. | 1608 | /// No description provided for @uploadProof. |
| 1603 | /// | 1609 | /// |
| 1604 | /// In zh, this message translates to: | 1610 | /// In zh, this message translates to: |
| @@ -1641,6 +1647,54 @@ abstract class AppLocalizations { | @@ -1641,6 +1647,54 @@ abstract class AppLocalizations { | ||
| 1641 | /// **'退出登录'** | 1647 | /// **'退出登录'** |
| 1642 | String get logOut; | 1648 | String get logOut; |
| 1643 | 1649 | ||
| 1650 | + /// No description provided for @confirmLogoutPrompt. | ||
| 1651 | + /// | ||
| 1652 | + /// In zh, this message translates to: | ||
| 1653 | + /// **'确认退出登录吗?'** | ||
| 1654 | + String get confirmLogoutPrompt; | ||
| 1655 | + | ||
| 1656 | + /// No description provided for @confirmLogout. | ||
| 1657 | + /// | ||
| 1658 | + /// In zh, this message translates to: | ||
| 1659 | + /// **'确认退出'** | ||
| 1660 | + String get confirmLogout; | ||
| 1661 | + | ||
| 1662 | + /// No description provided for @loggedOutSuccessfully. | ||
| 1663 | + /// | ||
| 1664 | + /// In zh, this message translates to: | ||
| 1665 | + /// **'已退出登录'** | ||
| 1666 | + String get loggedOutSuccessfully; | ||
| 1667 | + | ||
| 1668 | + /// No description provided for @accountDeletedSuccessfully. | ||
| 1669 | + /// | ||
| 1670 | + /// In zh, this message translates to: | ||
| 1671 | + /// **'账号注销成功'** | ||
| 1672 | + String get accountDeletedSuccessfully; | ||
| 1673 | + | ||
| 1674 | + /// No description provided for @deleteAccountWarningTitle. | ||
| 1675 | + /// | ||
| 1676 | + /// In zh, this message translates to: | ||
| 1677 | + /// **'注销账号后将无法找回!请谨慎操作'** | ||
| 1678 | + String get deleteAccountWarningTitle; | ||
| 1679 | + | ||
| 1680 | + /// No description provided for @deleteAccountWarningPrompt. | ||
| 1681 | + /// | ||
| 1682 | + /// In zh, this message translates to: | ||
| 1683 | + /// **'提示:注销账号将会删除该账号内包括但不限于\n个人资料、情绪记录、统计数据等全部信息。'** | ||
| 1684 | + String get deleteAccountWarningPrompt; | ||
| 1685 | + | ||
| 1686 | + /// No description provided for @deleteAccountWarningNote1. | ||
| 1687 | + /// | ||
| 1688 | + /// In zh, this message translates to: | ||
| 1689 | + /// **'注1:你的健康数据会保存在苹果健康,我们不会删除苹果健康中的数据。'** | ||
| 1690 | + String get deleteAccountWarningNote1; | ||
| 1691 | + | ||
| 1692 | + /// No description provided for @deleteAccountWarningNote2. | ||
| 1693 | + /// | ||
| 1694 | + /// In zh, this message translates to: | ||
| 1695 | + /// **'注2:删除账号不会影响你在App Store的订阅状态,如果需要取消订阅,请在APPs Store - 头像 - 订阅中手动取消订阅。'** | ||
| 1696 | + String get deleteAccountWarningNote2; | ||
| 1697 | + | ||
| 1644 | /// No description provided for @confirmDeletion. | 1698 | /// No description provided for @confirmDeletion. |
| 1645 | /// | 1699 | /// |
| 1646 | /// In zh, this message translates to: | 1700 | /// In zh, this message translates to: |
| @@ -2829,7 +2883,7 @@ abstract class AppLocalizations { | @@ -2829,7 +2883,7 @@ abstract class AppLocalizations { | ||
| 2829 | /// **'双倍感知 压力减半'** | 2883 | /// **'双倍感知 压力减半'** |
| 2830 | String get reportBottomSlogan; | 2884 | String get reportBottomSlogan; |
| 2831 | 2885 | ||
| 2832 | - /// No description provided for @refundExplanationTitle. | 2886 | + /// 退费说明 |
| 2833 | /// | 2887 | /// |
| 2834 | /// In zh, this message translates to: | 2888 | /// In zh, this message translates to: |
| 2835 | /// **'退费说明'** | 2889 | /// **'退费说明'** |
| @@ -3068,6 +3122,204 @@ abstract class AppLocalizations { | @@ -3068,6 +3122,204 @@ abstract class AppLocalizations { | ||
| 3068 | /// In zh, this message translates to: | 3122 | /// In zh, this message translates to: |
| 3069 | /// **'插图占位'** | 3123 | /// **'插图占位'** |
| 3070 | String get appReviewIllustrationPlaceholder; | 3124 | String get appReviewIllustrationPlaceholder; |
| 3125 | + | ||
| 3126 | + /// No description provided for @overallStressLevelToday. | ||
| 3127 | + /// | ||
| 3128 | + /// In zh, this message translates to: | ||
| 3129 | + /// **'今日的综合压力状态'** | ||
| 3130 | + String get overallStressLevelToday; | ||
| 3131 | + | ||
| 3132 | + /// No description provided for @stressLevelsOnThatDay. | ||
| 3133 | + /// | ||
| 3134 | + /// In zh, this message translates to: | ||
| 3135 | + /// **'该日压力状态'** | ||
| 3136 | + String get stressLevelsOnThatDay; | ||
| 3137 | + | ||
| 3138 | + /// No description provided for @noPressureDataAvailableAtThisTime. | ||
| 3139 | + /// | ||
| 3140 | + /// In zh, this message translates to: | ||
| 3141 | + /// **'暂无压力数据'** | ||
| 3142 | + String get noPressureDataAvailableAtThisTime; | ||
| 3143 | + | ||
| 3144 | + /// No description provided for @membersCanViewTheCompleteData. | ||
| 3145 | + /// | ||
| 3146 | + /// In zh, this message translates to: | ||
| 3147 | + /// **'会员可查看完整数据'** | ||
| 3148 | + String get membersCanViewTheCompleteData; | ||
| 3149 | + | ||
| 3150 | + /// No description provided for @unlockNow. | ||
| 3151 | + /// | ||
| 3152 | + /// In zh, this message translates to: | ||
| 3153 | + /// **'立即解锁'** | ||
| 3154 | + String get unlockNow; | ||
| 3155 | + | ||
| 3156 | + /// No description provided for @pressureOverload. | ||
| 3157 | + /// | ||
| 3158 | + /// In zh, this message translates to: | ||
| 3159 | + /// **'压力过载'** | ||
| 3160 | + String get pressureOverload; | ||
| 3161 | + | ||
| 3162 | + /// No description provided for @beMindfulOfStress. | ||
| 3163 | + /// | ||
| 3164 | + /// In zh, this message translates to: | ||
| 3165 | + /// **'注意压力'** | ||
| 3166 | + String get beMindfulOfStress; | ||
| 3167 | + | ||
| 3168 | + /// No description provided for @statusNormal. | ||
| 3169 | + /// | ||
| 3170 | + /// In zh, this message translates to: | ||
| 3171 | + /// **'状态正常'** | ||
| 3172 | + String get statusNormal; | ||
| 3173 | + | ||
| 3174 | + /// No description provided for @inExcellentCondition. | ||
| 3175 | + /// | ||
| 3176 | + /// In zh, this message translates to: | ||
| 3177 | + /// **'状态优秀'** | ||
| 3178 | + String get inExcellentCondition; | ||
| 3179 | + | ||
| 3180 | + /// No description provided for @waitingForData. | ||
| 3181 | + /// | ||
| 3182 | + /// In zh, this message translates to: | ||
| 3183 | + /// **'等待数据'** | ||
| 3184 | + String get waitingForData; | ||
| 3185 | + | ||
| 3186 | + /// No description provided for @pressure. | ||
| 3187 | + /// | ||
| 3188 | + /// In zh, this message translates to: | ||
| 3189 | + /// **'压力'** | ||
| 3190 | + String get pressure; | ||
| 3191 | + | ||
| 3192 | + /// No description provided for @mostRecent. | ||
| 3193 | + /// | ||
| 3194 | + /// In zh, this message translates to: | ||
| 3195 | + /// **'最近一次'** | ||
| 3196 | + String get mostRecent; | ||
| 3197 | + | ||
| 3198 | + /// No description provided for @theDayBeforeYesterday. | ||
| 3199 | + /// | ||
| 3200 | + /// In zh, this message translates to: | ||
| 3201 | + /// **'前天'** | ||
| 3202 | + String get theDayBeforeYesterday; | ||
| 3203 | + | ||
| 3204 | + /// No description provided for @uploadPhotos. | ||
| 3205 | + /// | ||
| 3206 | + /// In zh, this message translates to: | ||
| 3207 | + /// **'相册上传'** | ||
| 3208 | + String get uploadPhotos; | ||
| 3209 | + | ||
| 3210 | + /// No description provided for @filming. | ||
| 3211 | + /// | ||
| 3212 | + /// In zh, this message translates to: | ||
| 3213 | + /// **'拍摄'** | ||
| 3214 | + String get filming; | ||
| 3215 | + | ||
| 3216 | + /// No description provided for @unlockTheProVersion. | ||
| 3217 | + /// | ||
| 3218 | + /// In zh, this message translates to: | ||
| 3219 | + /// **'解锁专业版'** | ||
| 3220 | + String get unlockTheProVersion; | ||
| 3221 | + | ||
| 3222 | + /// No description provided for @embarkOnAJourneyOfStressAwarenessAndWellnessSupport. | ||
| 3223 | + /// | ||
| 3224 | + /// In zh, this message translates to: | ||
| 3225 | + /// **'开启压力预警与健康陪伴之旅'** | ||
| 3226 | + String get embarkOnAJourneyOfStressAwarenessAndWellnessSupport; | ||
| 3227 | + | ||
| 3228 | + /// 分享好友邀请码文案模板 | ||
| 3229 | + /// | ||
| 3230 | + /// In zh, this message translates to: | ||
| 3231 | + /// **'嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:{inviteCode}'** | ||
| 3232 | + String sharePartnerCodeTemplate(String inviteCode); | ||
| 3233 | + | ||
| 3234 | + /// No description provided for @bindPartnerIdNotExistTitle. | ||
| 3235 | + /// | ||
| 3236 | + /// In zh, this message translates to: | ||
| 3237 | + /// **'该ID不存在'** | ||
| 3238 | + String get bindPartnerIdNotExistTitle; | ||
| 3239 | + | ||
| 3240 | + /// No description provided for @bindPartnerIdNotExistMessage. | ||
| 3241 | + /// | ||
| 3242 | + /// In zh, this message translates to: | ||
| 3243 | + /// **'这个ID不存在哦,请检查后重新输入'** | ||
| 3244 | + String get bindPartnerIdNotExistMessage; | ||
| 3245 | + | ||
| 3246 | + /// No description provided for @bindPartnerDialogGotIt. | ||
| 3247 | + /// | ||
| 3248 | + /// In zh, this message translates to: | ||
| 3249 | + /// **'我知道了'** | ||
| 3250 | + String get bindPartnerDialogGotIt; | ||
| 3251 | + | ||
| 3252 | + /// No description provided for @bindPartnerAddFailedTitle. | ||
| 3253 | + /// | ||
| 3254 | + /// In zh, this message translates to: | ||
| 3255 | + /// **'添加失败'** | ||
| 3256 | + String get bindPartnerAddFailedTitle; | ||
| 3257 | + | ||
| 3258 | + /// No description provided for @bindPartnerAddFailedMessage. | ||
| 3259 | + /// | ||
| 3260 | + /// In zh, this message translates to: | ||
| 3261 | + /// **'该用户不允许被添加好友,无法添加Ta哦'** | ||
| 3262 | + String get bindPartnerAddFailedMessage; | ||
| 3263 | + | ||
| 3264 | + /// No description provided for @bindPartnerAlreadyFriendTitle. | ||
| 3265 | + /// | ||
| 3266 | + /// In zh, this message translates to: | ||
| 3267 | + /// **'对方已经是你的好友啦'** | ||
| 3268 | + String get bindPartnerAlreadyFriendTitle; | ||
| 3269 | + | ||
| 3270 | + /// No description provided for @bindPartnerAlreadyFriendMessage. | ||
| 3271 | + /// | ||
| 3272 | + /// In zh, this message translates to: | ||
| 3273 | + /// **'请不要重复添加哦'** | ||
| 3274 | + String get bindPartnerAlreadyFriendMessage; | ||
| 3275 | + | ||
| 3276 | + /// 好友的状态标题 | ||
| 3277 | + /// | ||
| 3278 | + /// In zh, this message translates to: | ||
| 3279 | + /// **'{remarkName}的状态'** | ||
| 3280 | + String friendStatusTitle(String remarkName); | ||
| 3281 | + | ||
| 3282 | + /// No description provided for @annualMemberDiscounts. | ||
| 3283 | + /// | ||
| 3284 | + /// In zh, this message translates to: | ||
| 3285 | + /// **'年度会员优惠'** | ||
| 3286 | + String get annualMemberDiscounts; | ||
| 3287 | + | ||
| 3288 | + /// No description provided for @specialOffers. | ||
| 3289 | + /// | ||
| 3290 | + /// In zh, this message translates to: | ||
| 3291 | + /// **'优惠'** | ||
| 3292 | + String get specialOffers; | ||
| 3293 | + | ||
| 3294 | + /// No description provided for @currentPrice. | ||
| 3295 | + /// | ||
| 3296 | + /// In zh, this message translates to: | ||
| 3297 | + /// **'现价'** | ||
| 3298 | + String get currentPrice; | ||
| 3299 | + | ||
| 3300 | + /// 原价标签 | ||
| 3301 | + /// | ||
| 3302 | + /// In zh, this message translates to: | ||
| 3303 | + /// **'原价{price}'** | ||
| 3304 | + String originalPrice(String price); | ||
| 3305 | + | ||
| 3306 | + /// No description provided for @freeRedemptionOffer. | ||
| 3307 | + /// | ||
| 3308 | + /// In zh, this message translates to: | ||
| 3309 | + /// **'免费兑换优惠'** | ||
| 3310 | + String get freeRedemptionOffer; | ||
| 3311 | + | ||
| 3312 | + /// No description provided for @cellPhoneNumber. | ||
| 3313 | + /// | ||
| 3314 | + /// In zh, this message translates to: | ||
| 3315 | + /// **'手机号'** | ||
| 3316 | + String get cellPhoneNumber; | ||
| 3317 | + | ||
| 3318 | + /// No description provided for @todayOnWeeklyCalendar. | ||
| 3319 | + /// | ||
| 3320 | + /// In zh, this message translates to: | ||
| 3321 | + /// **'今'** | ||
| 3322 | + String get todayOnWeeklyCalendar; | ||
| 3071 | } | 3323 | } |
| 3072 | 3324 | ||
| 3073 | class _AppLocalizationsDelegate | 3325 | class _AppLocalizationsDelegate |
| @@ -879,6 +879,10 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -879,6 +879,10 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 879 | 'If you would like us to reply, please provide your email address'; | 879 | 'If you would like us to reply, please provide your email address'; |
| 880 | 880 | ||
| 881 | @override | 881 | @override |
| 882 | + String get feedbackHintText => | ||
| 883 | + '1. Please describe the screen and scenario where the issue occurred\n2. Please provide screenshots to help us resolve the issue more efficiently\n3. Please leave your contact information so we can get back to you as soon as possible'; | ||
| 884 | + | ||
| 885 | + @override | ||
| 882 | String get uploadProof => 'Upload Proof'; | 886 | String get uploadProof => 'Upload Proof'; |
| 883 | 887 | ||
| 884 | @override | 888 | @override |
| @@ -901,6 +905,34 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -901,6 +905,34 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 901 | String get logOut => 'Log Out'; | 905 | String get logOut => 'Log Out'; |
| 902 | 906 | ||
| 903 | @override | 907 | @override |
| 908 | + String get confirmLogoutPrompt => 'Are you sure you want to log out?'; | ||
| 909 | + | ||
| 910 | + @override | ||
| 911 | + String get confirmLogout => 'Log Out'; | ||
| 912 | + | ||
| 913 | + @override | ||
| 914 | + String get loggedOutSuccessfully => 'Logged out successfully'; | ||
| 915 | + | ||
| 916 | + @override | ||
| 917 | + String get accountDeletedSuccessfully => 'Account deleted successfully'; | ||
| 918 | + | ||
| 919 | + @override | ||
| 920 | + String get deleteAccountWarningTitle => | ||
| 921 | + 'Once deleted, the account cannot be recovered! Please proceed with caution.'; | ||
| 922 | + | ||
| 923 | + @override | ||
| 924 | + String get deleteAccountWarningPrompt => | ||
| 925 | + 'Note: Deleting the account will remove all information in this account, including but not limited to\npersonal profile, mood records, and statistical data.'; | ||
| 926 | + | ||
| 927 | + @override | ||
| 928 | + String get deleteAccountWarningNote1 => | ||
| 929 | + 'Note 1: Your health data will be saved in Apple Health, and we will not delete data from Apple Health.'; | ||
| 930 | + | ||
| 931 | + @override | ||
| 932 | + String get deleteAccountWarningNote2 => | ||
| 933 | + 'Note 2: Deleting the account will not affect your subscription status in the App Store. If you need to cancel the subscription, please cancel it manually in the App Store -> Profile -> Subscriptions.'; | ||
| 934 | + | ||
| 935 | + @override | ||
| 904 | String get confirmDeletion => 'Confirm Deletion'; | 936 | String get confirmDeletion => 'Confirm Deletion'; |
| 905 | 937 | ||
| 906 | @override | 938 | @override |
| @@ -1708,4 +1740,114 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1708,4 +1740,114 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1708 | 1740 | ||
| 1709 | @override | 1741 | @override |
| 1710 | String get appReviewIllustrationPlaceholder => 'Illustration Placeholder'; | 1742 | String get appReviewIllustrationPlaceholder => 'Illustration Placeholder'; |
| 1743 | + | ||
| 1744 | + @override | ||
| 1745 | + String get overallStressLevelToday => 'Overall Stress Level Today'; | ||
| 1746 | + | ||
| 1747 | + @override | ||
| 1748 | + String get stressLevelsOnThatDay => 'Stress Levels on That Day'; | ||
| 1749 | + | ||
| 1750 | + @override | ||
| 1751 | + String get noPressureDataAvailableAtThisTime => | ||
| 1752 | + 'No pressure data available at this time'; | ||
| 1753 | + | ||
| 1754 | + @override | ||
| 1755 | + String get membersCanViewTheCompleteData => | ||
| 1756 | + 'Members can view the complete data'; | ||
| 1757 | + | ||
| 1758 | + @override | ||
| 1759 | + String get unlockNow => 'Unlock Now'; | ||
| 1760 | + | ||
| 1761 | + @override | ||
| 1762 | + String get pressureOverload => 'Pressure Overload'; | ||
| 1763 | + | ||
| 1764 | + @override | ||
| 1765 | + String get beMindfulOfStress => 'Be Aware of Stress'; | ||
| 1766 | + | ||
| 1767 | + @override | ||
| 1768 | + String get statusNormal => 'Status: Normal'; | ||
| 1769 | + | ||
| 1770 | + @override | ||
| 1771 | + String get inExcellentCondition => 'In excellent condition'; | ||
| 1772 | + | ||
| 1773 | + @override | ||
| 1774 | + String get waitingForData => 'Waiting for data'; | ||
| 1775 | + | ||
| 1776 | + @override | ||
| 1777 | + String get pressure => 'Pressure'; | ||
| 1778 | + | ||
| 1779 | + @override | ||
| 1780 | + String get mostRecent => 'Most recent'; | ||
| 1781 | + | ||
| 1782 | + @override | ||
| 1783 | + String get theDayBeforeYesterday => 'The day before yesterday'; | ||
| 1784 | + | ||
| 1785 | + @override | ||
| 1786 | + String get uploadPhotos => 'Upload Photos'; | ||
| 1787 | + | ||
| 1788 | + @override | ||
| 1789 | + String get filming => 'Filming'; | ||
| 1790 | + | ||
| 1791 | + @override | ||
| 1792 | + String get unlockTheProVersion => 'Unlock the Pro Version'; | ||
| 1793 | + | ||
| 1794 | + @override | ||
| 1795 | + String get embarkOnAJourneyOfStressAwarenessAndWellnessSupport => | ||
| 1796 | + 'Embark on a Journey of Stress Awareness and Wellness Support'; | ||
| 1797 | + | ||
| 1798 | + @override | ||
| 1799 | + String sharePartnerCodeTemplate(String inviteCode) { | ||
| 1800 | + return 'Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other\'s health in real-time.\nCome join me 👉 https://doublefeel.cn/\nMy friend ID: $inviteCode'; | ||
| 1801 | + } | ||
| 1802 | + | ||
| 1803 | + @override | ||
| 1804 | + String get bindPartnerIdNotExistTitle => 'ID does not exist'; | ||
| 1805 | + | ||
| 1806 | + @override | ||
| 1807 | + String get bindPartnerIdNotExistMessage => | ||
| 1808 | + 'This ID does not exist. Please check and try again'; | ||
| 1809 | + | ||
| 1810 | + @override | ||
| 1811 | + String get bindPartnerDialogGotIt => 'Got it'; | ||
| 1812 | + | ||
| 1813 | + @override | ||
| 1814 | + String get bindPartnerAddFailedTitle => 'Failed to add'; | ||
| 1815 | + | ||
| 1816 | + @override | ||
| 1817 | + String get bindPartnerAddFailedMessage => | ||
| 1818 | + 'This user does not allow adding friends, cannot add them'; | ||
| 1819 | + | ||
| 1820 | + @override | ||
| 1821 | + String get bindPartnerAlreadyFriendTitle => 'They are already your friend'; | ||
| 1822 | + | ||
| 1823 | + @override | ||
| 1824 | + String get bindPartnerAlreadyFriendMessage => 'Please do not add again'; | ||
| 1825 | + | ||
| 1826 | + @override | ||
| 1827 | + String friendStatusTitle(String remarkName) { | ||
| 1828 | + return '$remarkName\'s Status'; | ||
| 1829 | + } | ||
| 1830 | + | ||
| 1831 | + @override | ||
| 1832 | + String get annualMemberDiscounts => 'Annual Member Discounts'; | ||
| 1833 | + | ||
| 1834 | + @override | ||
| 1835 | + String get specialOffers => 'Special Offers'; | ||
| 1836 | + | ||
| 1837 | + @override | ||
| 1838 | + String get currentPrice => 'Current Price'; | ||
| 1839 | + | ||
| 1840 | + @override | ||
| 1841 | + String originalPrice(String price) { | ||
| 1842 | + return 'Original price $price'; | ||
| 1843 | + } | ||
| 1844 | + | ||
| 1845 | + @override | ||
| 1846 | + String get freeRedemptionOffer => 'Free Redemption Offer'; | ||
| 1847 | + | ||
| 1848 | + @override | ||
| 1849 | + String get cellPhoneNumber => 'Cell phone number'; | ||
| 1850 | + | ||
| 1851 | + @override | ||
| 1852 | + String get todayOnWeeklyCalendar => 'today'; | ||
| 1711 | } | 1853 | } |
| @@ -818,6 +818,10 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -818,6 +818,10 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 818 | '如果需要我们回复,请填写联系邮箱'; | 818 | '如果需要我们回复,请填写联系邮箱'; |
| 819 | 819 | ||
| 820 | @override | 820 | @override |
| 821 | + String get feedbackHintText => | ||
| 822 | + '1.请说明问题的界面和使用场景\n2.请提供截图,以便提升解决问题效率\n3.请留下联系方式,我们尽快回复联系您'; | ||
| 823 | + | ||
| 824 | + @override | ||
| 821 | String get uploadProof => '上传凭证'; | 825 | String get uploadProof => '上传凭证'; |
| 822 | 826 | ||
| 823 | @override | 827 | @override |
| @@ -839,6 +843,32 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -839,6 +843,32 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 839 | String get logOut => '退出登录'; | 843 | String get logOut => '退出登录'; |
| 840 | 844 | ||
| 841 | @override | 845 | @override |
| 846 | + String get confirmLogoutPrompt => '确认退出登录吗?'; | ||
| 847 | + | ||
| 848 | + @override | ||
| 849 | + String get confirmLogout => '确认退出'; | ||
| 850 | + | ||
| 851 | + @override | ||
| 852 | + String get loggedOutSuccessfully => '已退出登录'; | ||
| 853 | + | ||
| 854 | + @override | ||
| 855 | + String get accountDeletedSuccessfully => '账号注销成功'; | ||
| 856 | + | ||
| 857 | + @override | ||
| 858 | + String get deleteAccountWarningTitle => '注销账号后将无法找回!请谨慎操作'; | ||
| 859 | + | ||
| 860 | + @override | ||
| 861 | + String get deleteAccountWarningPrompt => | ||
| 862 | + '提示:注销账号将会删除该账号内包括但不限于\n个人资料、情绪记录、统计数据等全部信息。'; | ||
| 863 | + | ||
| 864 | + @override | ||
| 865 | + String get deleteAccountWarningNote1 => '注1:你的健康数据会保存在苹果健康,我们不会删除苹果健康中的数据。'; | ||
| 866 | + | ||
| 867 | + @override | ||
| 868 | + String get deleteAccountWarningNote2 => | ||
| 869 | + '注2:删除账号不会影响你在App Store的订阅状态,如果需要取消订阅,请在APPs Store - 头像 - 订阅中手动取消订阅。'; | ||
| 870 | + | ||
| 871 | + @override | ||
| 842 | String get confirmDeletion => '确认注销'; | 872 | String get confirmDeletion => '确认注销'; |
| 843 | 873 | ||
| 844 | @override | 874 | @override |
| @@ -1615,4 +1645,110 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -1615,4 +1645,110 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 1615 | 1645 | ||
| 1616 | @override | 1646 | @override |
| 1617 | String get appReviewIllustrationPlaceholder => '插图占位'; | 1647 | String get appReviewIllustrationPlaceholder => '插图占位'; |
| 1648 | + | ||
| 1649 | + @override | ||
| 1650 | + String get overallStressLevelToday => '今日的综合压力状态'; | ||
| 1651 | + | ||
| 1652 | + @override | ||
| 1653 | + String get stressLevelsOnThatDay => '该日压力状态'; | ||
| 1654 | + | ||
| 1655 | + @override | ||
| 1656 | + String get noPressureDataAvailableAtThisTime => '暂无压力数据'; | ||
| 1657 | + | ||
| 1658 | + @override | ||
| 1659 | + String get membersCanViewTheCompleteData => '会员可查看完整数据'; | ||
| 1660 | + | ||
| 1661 | + @override | ||
| 1662 | + String get unlockNow => '立即解锁'; | ||
| 1663 | + | ||
| 1664 | + @override | ||
| 1665 | + String get pressureOverload => '压力过载'; | ||
| 1666 | + | ||
| 1667 | + @override | ||
| 1668 | + String get beMindfulOfStress => '注意压力'; | ||
| 1669 | + | ||
| 1670 | + @override | ||
| 1671 | + String get statusNormal => '状态正常'; | ||
| 1672 | + | ||
| 1673 | + @override | ||
| 1674 | + String get inExcellentCondition => '状态优秀'; | ||
| 1675 | + | ||
| 1676 | + @override | ||
| 1677 | + String get waitingForData => '等待数据'; | ||
| 1678 | + | ||
| 1679 | + @override | ||
| 1680 | + String get pressure => '压力'; | ||
| 1681 | + | ||
| 1682 | + @override | ||
| 1683 | + String get mostRecent => '最近一次'; | ||
| 1684 | + | ||
| 1685 | + @override | ||
| 1686 | + String get theDayBeforeYesterday => '前天'; | ||
| 1687 | + | ||
| 1688 | + @override | ||
| 1689 | + String get uploadPhotos => '相册上传'; | ||
| 1690 | + | ||
| 1691 | + @override | ||
| 1692 | + String get filming => '拍摄'; | ||
| 1693 | + | ||
| 1694 | + @override | ||
| 1695 | + String get unlockTheProVersion => '解锁专业版'; | ||
| 1696 | + | ||
| 1697 | + @override | ||
| 1698 | + String get embarkOnAJourneyOfStressAwarenessAndWellnessSupport => | ||
| 1699 | + '开启压力预警与健康陪伴之旅'; | ||
| 1700 | + | ||
| 1701 | + @override | ||
| 1702 | + String sharePartnerCodeTemplate(String inviteCode) { | ||
| 1703 | + return '嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:$inviteCode'; | ||
| 1704 | + } | ||
| 1705 | + | ||
| 1706 | + @override | ||
| 1707 | + String get bindPartnerIdNotExistTitle => '该ID不存在'; | ||
| 1708 | + | ||
| 1709 | + @override | ||
| 1710 | + String get bindPartnerIdNotExistMessage => '这个ID不存在哦,请检查后重新输入'; | ||
| 1711 | + | ||
| 1712 | + @override | ||
| 1713 | + String get bindPartnerDialogGotIt => '我知道了'; | ||
| 1714 | + | ||
| 1715 | + @override | ||
| 1716 | + String get bindPartnerAddFailedTitle => '添加失败'; | ||
| 1717 | + | ||
| 1718 | + @override | ||
| 1719 | + String get bindPartnerAddFailedMessage => '该用户不允许被添加好友,无法添加Ta哦'; | ||
| 1720 | + | ||
| 1721 | + @override | ||
| 1722 | + String get bindPartnerAlreadyFriendTitle => '对方已经是你的好友啦'; | ||
| 1723 | + | ||
| 1724 | + @override | ||
| 1725 | + String get bindPartnerAlreadyFriendMessage => '请不要重复添加哦'; | ||
| 1726 | + | ||
| 1727 | + @override | ||
| 1728 | + String friendStatusTitle(String remarkName) { | ||
| 1729 | + return '$remarkName的状态'; | ||
| 1730 | + } | ||
| 1731 | + | ||
| 1732 | + @override | ||
| 1733 | + String get annualMemberDiscounts => '年度会员优惠'; | ||
| 1734 | + | ||
| 1735 | + @override | ||
| 1736 | + String get specialOffers => '优惠'; | ||
| 1737 | + | ||
| 1738 | + @override | ||
| 1739 | + String get currentPrice => '现价'; | ||
| 1740 | + | ||
| 1741 | + @override | ||
| 1742 | + String originalPrice(String price) { | ||
| 1743 | + return '原价$price'; | ||
| 1744 | + } | ||
| 1745 | + | ||
| 1746 | + @override | ||
| 1747 | + String get freeRedemptionOffer => '免费兑换优惠'; | ||
| 1748 | + | ||
| 1749 | + @override | ||
| 1750 | + String get cellPhoneNumber => '手机号'; | ||
| 1751 | + | ||
| 1752 | + @override | ||
| 1753 | + String get todayOnWeeklyCalendar => '今'; | ||
| 1618 | } | 1754 | } |
-
Please register or login to post a comment