Showing
11 changed files
with
466 additions
and
431 deletions
| @@ -3,15 +3,15 @@ | @@ -3,15 +3,15 @@ | ||
| 3 | archiveVersion = 1; | 3 | archiveVersion = 1; |
| 4 | classes = { | 4 | classes = { |
| 5 | }; | 5 | }; |
| 6 | - objectVersion = 77; | 6 | + objectVersion = 54; |
| 7 | objects = { | 7 | objects = { |
| 8 | 8 | ||
| 9 | /* Begin PBXBuildFile section */ | 9 | /* Begin PBXBuildFile section */ |
| 10 | 0798E27DB95F2A823881D75A /* Pods_Runner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A488EFC0BD642ED604C4E43 /* Pods_Runner_Watch_App.framework */; }; | 10 | 0798E27DB95F2A823881D75A /* Pods_Runner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A488EFC0BD642ED604C4E43 /* Pods_Runner_Watch_App.framework */; }; |
| 11 | 20A3C4994C9B0F60D1F3BCBE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8DC48B3736B7FEB548D0E65 /* Pods_Runner.framework */; }; | 11 | 20A3C4994C9B0F60D1F3BCBE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8DC48B3736B7FEB548D0E65 /* Pods_Runner.framework */; }; |
| 12 | - 66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */; }; | ||
| 13 | - 66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */; }; | ||
| 14 | - 66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */; }; | 12 | + 66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */; }; |
| 13 | + 66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */; }; | ||
| 14 | + 66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */; }; | ||
| 15 | 66FBE58C2FDA4F0F00F515B4 /* Runner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; | 15 | 66FBE58C2FDA4F0F00F515B4 /* Runner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; |
| 16 | /* End PBXBuildFile section */ | 16 | /* End PBXBuildFile section */ |
| 17 | 17 | ||
| @@ -54,9 +54,9 @@ | @@ -54,9 +54,9 @@ | ||
| 54 | 27E4D5A8995AB1EC91E7330E /* Pods-Runner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App.debug.xcconfig"; sourceTree = "<group>"; }; | 54 | 27E4D5A8995AB1EC91E7330E /* Pods-Runner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App.debug.xcconfig"; sourceTree = "<group>"; }; |
| 55 | 4A9B7C102FDB0A1200F515B4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; | 55 | 4A9B7C102FDB0A1200F515B4 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; |
| 56 | 4A9B7C112FDB0A1200F515B4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; | 56 | 4A9B7C112FDB0A1200F515B4 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; |
| 57 | - 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; }; | ||
| 58 | - 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; }; | ||
| 59 | - 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; }; | 57 | + 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift"; sourceTree = "<group>"; }; |
| 58 | + 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift"; sourceTree = "<group>"; }; | ||
| 59 | + 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift"; sourceTree = "<group>"; }; | ||
| 60 | 66FBE57E2FDA4F0E00F515B4 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | 60 | 66FBE57E2FDA4F0E00F515B4 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 61 | 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Runner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; | 61 | 66FBE58B2FDA4F0F00F515B4 /* Runner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Runner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 62 | 66FBE5A72FDA518C00F515B4 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; | 62 | 66FBE5A72FDA518C00F515B4 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; |
| @@ -97,6 +97,8 @@ | @@ -97,6 +97,8 @@ | ||
| 97 | }; | 97 | }; |
| 98 | 66FBE58F2FDA4F0F00F515B4 /* Runner Watch App */ = { | 98 | 66FBE58F2FDA4F0F00F515B4 /* Runner Watch App */ = { |
| 99 | isa = PBXFileSystemSynchronizedRootGroup; | 99 | isa = PBXFileSystemSynchronizedRootGroup; |
| 100 | + exceptions = ( | ||
| 101 | + ); | ||
| 100 | path = "Runner Watch App"; | 102 | path = "Runner Watch App"; |
| 101 | sourceTree = "<group>"; | 103 | sourceTree = "<group>"; |
| 102 | }; | 104 | }; |
| @@ -134,9 +136,9 @@ | @@ -134,9 +136,9 @@ | ||
| 134 | 66FB19072FDBC80100F515B4 /* FigmaHome Preview Sources */ = { | 136 | 66FB19072FDBC80100F515B4 /* FigmaHome Preview Sources */ = { |
| 135 | isa = PBXGroup; | 137 | isa = PBXGroup; |
| 136 | children = ( | 138 | children = ( |
| 137 | - 66FB19052FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift */, | ||
| 138 | - 66FB19042FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift */, | ||
| 139 | - 66FB19032FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift */, | 139 | + 66FB19052FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift */, |
| 140 | + 66FB19042FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift */, | ||
| 141 | + 66FB19032FDBC80100F515B4 /* DFWatchFigmaTokens.swift */, | ||
| 140 | ); | 142 | ); |
| 141 | name = "FigmaHome Preview Sources"; | 143 | name = "FigmaHome Preview Sources"; |
| 142 | sourceTree = "<group>"; | 144 | sourceTree = "<group>"; |
| @@ -303,14 +305,10 @@ | @@ -303,14 +305,10 @@ | ||
| 303 | inputFileListPaths = ( | 305 | inputFileListPaths = ( |
| 304 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", | 306 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", |
| 305 | ); | 307 | ); |
| 306 | - inputPaths = ( | ||
| 307 | - ); | ||
| 308 | name = "[CP] Copy Pods Resources"; | 308 | name = "[CP] Copy Pods Resources"; |
| 309 | outputFileListPaths = ( | 309 | outputFileListPaths = ( |
| 310 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", | 310 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", |
| 311 | ); | 311 | ); |
| 312 | - outputPaths = ( | ||
| 313 | - ); | ||
| 314 | runOnlyForDeploymentPostprocessing = 0; | 312 | runOnlyForDeploymentPostprocessing = 0; |
| 315 | shellPath = /bin/sh; | 313 | shellPath = /bin/sh; |
| 316 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; | 314 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; |
| @@ -381,14 +379,10 @@ | @@ -381,14 +379,10 @@ | ||
| 381 | inputFileListPaths = ( | 379 | inputFileListPaths = ( |
| 382 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", | 380 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", |
| 383 | ); | 381 | ); |
| 384 | - inputPaths = ( | ||
| 385 | - ); | ||
| 386 | name = "[CP] Embed Pods Frameworks"; | 382 | name = "[CP] Embed Pods Frameworks"; |
| 387 | outputFileListPaths = ( | 383 | outputFileListPaths = ( |
| 388 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", | 384 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", |
| 389 | ); | 385 | ); |
| 390 | - outputPaths = ( | ||
| 391 | - ); | ||
| 392 | runOnlyForDeploymentPostprocessing = 0; | 386 | runOnlyForDeploymentPostprocessing = 0; |
| 393 | shellPath = /bin/sh; | 387 | shellPath = /bin/sh; |
| 394 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | 388 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; |
| @@ -402,14 +396,10 @@ | @@ -402,14 +396,10 @@ | ||
| 402 | inputFileListPaths = ( | 396 | inputFileListPaths = ( |
| 403 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist", | 397 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist", |
| 404 | ); | 398 | ); |
| 405 | - inputPaths = ( | ||
| 406 | - ); | ||
| 407 | name = "[CP] Embed Pods Frameworks"; | 399 | name = "[CP] Embed Pods Frameworks"; |
| 408 | outputFileListPaths = ( | 400 | outputFileListPaths = ( |
| 409 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist", | 401 | "${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist", |
| 410 | ); | 402 | ); |
| 411 | - outputPaths = ( | ||
| 412 | - ); | ||
| 413 | runOnlyForDeploymentPostprocessing = 0; | 403 | runOnlyForDeploymentPostprocessing = 0; |
| 414 | shellPath = /bin/sh; | 404 | shellPath = /bin/sh; |
| 415 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks.sh\"\n"; | 405 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner Watch App/Pods-Runner Watch App-frameworks.sh\"\n"; |
| @@ -451,9 +441,9 @@ | @@ -451,9 +441,9 @@ | ||
| 451 | isa = PBXSourcesBuildPhase; | 441 | isa = PBXSourcesBuildPhase; |
| 452 | buildActionMask = 2147483647; | 442 | buildActionMask = 2147483647; |
| 453 | files = ( | 443 | files = ( |
| 454 | - 66FB19022FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaDetailViews.swift in Sources */, | ||
| 455 | - 66FB19012FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaHomeViews.swift in Sources */, | ||
| 456 | - 66FB19002FDBC80100F515B4 /* Runner Watch App/FigmaHome/DFWatchFigmaTokens.swift in Sources */, | 444 | + 66FB19022FDBC80100F515B4 /* DFWatchFigmaDetailViews.swift in Sources */, |
| 445 | + 66FB19012FDBC80100F515B4 /* DFWatchFigmaHomeViews.swift in Sources */, | ||
| 446 | + 66FB19002FDBC80100F515B4 /* DFWatchFigmaTokens.swift in Sources */, | ||
| 457 | ); | 447 | ); |
| 458 | runOnlyForDeploymentPostprocessing = 0; | 448 | runOnlyForDeploymentPostprocessing = 0; |
| 459 | }; | 449 | }; |
| @@ -28,7 +28,8 @@ class BindPartnerController extends GetxController { | @@ -28,7 +28,8 @@ class BindPartnerController extends GetxController { | ||
| 28 | }); | 28 | }); |
| 29 | 29 | ||
| 30 | final userPrefs = Get.find<UserPreferencesStorage>(); | 30 | final userPrefs = Get.find<UserPreferencesStorage>(); |
| 31 | - myInviteCode.value = userPrefs.preferences.value.meUserInfo?.pairCode ?? ''; | 31 | + myInviteCode.value = |
| 32 | + userPrefs.preferences.value.meUserInfo?.uniqueCode ?? ''; | ||
| 32 | } | 33 | } |
| 33 | 34 | ||
| 34 | @override | 35 | @override |
| 1 | import 'dart:async'; | 1 | import 'dart:async'; |
| 2 | +import 'dart:math'; | ||
| 2 | 3 | ||
| 3 | import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; | 4 | import 'package:doublefeel_flutter/app/apple_health_upload/apple_health_upload_tool.dart'; |
| 5 | +import 'package:doublefeel_flutter/app/modules/friends/controllers/friend_trend_controller.dart'; | ||
| 6 | +import 'package:doublefeel_flutter/app/modules/home/controllers/home_controller.dart'; | ||
| 7 | +import 'package:doublefeel_flutter/app/modules/home/controllers/trend/trend_controller.dart'; | ||
| 4 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart'; | 8 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart'; |
| 9 | +import 'package:doublefeel_flutter/app/routes/app_pages.dart'; | ||
| 5 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; | 10 | import 'package:doublefeel_flutter/core/logging/app_logger.dart'; |
| 6 | import 'package:doublefeel_flutter/core/network/api/health_api.dart'; | 11 | import 'package:doublefeel_flutter/core/network/api/health_api.dart'; |
| 7 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | 12 | import 'package:doublefeel_flutter/core/network/api/user_api.dart'; |
| @@ -10,7 +15,6 @@ import 'package:doublefeel_flutter/core/services/health_kit_upload_service.dart' | @@ -10,7 +15,6 @@ import 'package:doublefeel_flutter/core/services/health_kit_upload_service.dart' | ||
| 10 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; | 15 | import 'package:doublefeel_flutter/core/services/user_state_service.dart'; |
| 11 | import 'package:doublefeel_flutter/data/models/enums/app_enums.dart'; | 16 | import 'package:doublefeel_flutter/data/models/enums/app_enums.dart'; |
| 12 | import 'package:doublefeel_flutter/data/models/health/health_models.dart'; | 17 | import 'package:doublefeel_flutter/data/models/health/health_models.dart'; |
| 13 | -import 'package:doublefeel_flutter/data/models/health/health_upload_models.dart'; | ||
| 14 | import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; | 18 | import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart'; |
| 15 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; | 19 | import 'package:doublefeel_flutter/pigeon/health_kit_api.g.dart'; |
| 16 | import 'package:flutter/material.dart'; | 20 | import 'package:flutter/material.dart'; |
| @@ -75,12 +79,6 @@ class TodayController extends GetxController { | @@ -75,12 +79,6 @@ class TodayController extends GetxController { | ||
| 75 | final showHealthDataAuthCard = true.obs; | 79 | final showHealthDataAuthCard = true.obs; |
| 76 | final stressSubtitle = 'Hi, 你今日的综合压力状态'.obs; | 80 | final stressSubtitle = 'Hi, 你今日的综合压力状态'.obs; |
| 77 | 81 | ||
| 78 | - // ── 压力状态 ───────────────────────────── | ||
| 79 | - final stressLabel = '状态正常'.obs; | ||
| 80 | - | ||
| 81 | - /// 进度条压力分(null=加载中, 0~100=压力分: 0优秀, 100过载) | ||
| 82 | - final stressScore = Rxn<int>(); | ||
| 83 | - | ||
| 84 | // ── HRV 表盘引导 Banner ─────────────────── | 82 | // ── HRV 表盘引导 Banner ─────────────────── |
| 85 | final showHrvAdBanner = true.obs; | 83 | final showHrvAdBanner = true.obs; |
| 86 | final showPartnerAdBanner = true.obs; | 84 | final showPartnerAdBanner = true.obs; |
| @@ -93,13 +91,6 @@ class TodayController extends GetxController { | @@ -93,13 +91,6 @@ class TodayController extends GetxController { | ||
| 93 | final avgHrv = '--'.obs; | 91 | final avgHrv = '--'.obs; |
| 94 | final restingHeartRate = '--'.obs; | 92 | final restingHeartRate = '--'.obs; |
| 95 | 93 | ||
| 96 | - // ── 睡眠卡片 ────────────────────────────── | ||
| 97 | - final sleepHours = '--'.obs; | ||
| 98 | - final sleepMinutes = '--'.obs; | ||
| 99 | - final sleepQuality = '--'.obs; | ||
| 100 | - final sleepAverageHeartRate = '--'.obs; | ||
| 101 | - final sleepProgress = 0.0.obs; | ||
| 102 | - | ||
| 103 | // ── 健身卡片 ────────────────────────────── | 94 | // ── 健身卡片 ────────────────────────────── |
| 104 | final activityCalories = '--'.obs; | 95 | final activityCalories = '--'.obs; |
| 105 | final activityExerciseMinutes = '--'.obs; | 96 | final activityExerciseMinutes = '--'.obs; |
| @@ -219,7 +210,7 @@ class TodayController extends GetxController { | @@ -219,7 +210,7 @@ class TodayController extends GetxController { | ||
| 219 | 210 | ||
| 220 | Future<void> _refreshHealthDataForDate(DateTime date) async { | 211 | Future<void> _refreshHealthDataForDate(DateTime date) async { |
| 221 | _clearRealTimeData(); | 212 | _clearRealTimeData(); |
| 222 | - final intDate = int.parse(DateFormat('yyyyMM01').format(date)); | 213 | + final intDate = int.parse(DateFormat('yyyyMMdd').format(date)); |
| 223 | 214 | ||
| 224 | final isToday = DateUtils.isSameDay(date, lastSelectableDay); | 215 | final isToday = DateUtils.isSameDay(date, lastSelectableDay); |
| 225 | stressSubtitle.value = isToday ? 'Hi, 你今日的综合压力状态' : '该日压力状态'; | 216 | stressSubtitle.value = isToday ? 'Hi, 你今日的综合压力状态' : '该日压力状态'; |
| @@ -261,11 +252,7 @@ class TodayController extends GetxController { | @@ -261,11 +252,7 @@ class TodayController extends GetxController { | ||
| 261 | 252 | ||
| 262 | void _clearRealTimeData() { | 253 | void _clearRealTimeData() { |
| 263 | restingHeartRate.value = '--'; | 254 | restingHeartRate.value = '--'; |
| 264 | - sleepAverageHeartRate.value = '--'; | ||
| 265 | - sleepHours.value = '--'; | ||
| 266 | - sleepMinutes.value = '--'; | ||
| 267 | - sleepQuality.value = '--'; | ||
| 268 | - sleepProgress.value = 0.0; | 255 | + |
| 269 | activityCalories.value = '--'; | 256 | activityCalories.value = '--'; |
| 270 | activityExerciseMinutes.value = '--'; | 257 | activityExerciseMinutes.value = '--'; |
| 271 | activityStandHours.value = '--'; | 258 | activityStandHours.value = '--'; |
| @@ -276,69 +263,6 @@ class TodayController extends GetxController { | @@ -276,69 +263,6 @@ class TodayController extends GetxController { | ||
| 276 | stressChartData.clear(); | 263 | stressChartData.clear(); |
| 277 | hrvAnnotations.clear(); | 264 | hrvAnnotations.clear(); |
| 278 | avgHrv.value = '--'; | 265 | avgHrv.value = '--'; |
| 279 | - stressLabel.value = '状态正常'; | ||
| 280 | - stressScore.value = null; | ||
| 281 | - } | ||
| 282 | - | ||
| 283 | - void _applyTodayData(TodayStatusData today) { | ||
| 284 | - final recent = today.recentData; | ||
| 285 | - if (recent?.heartRate != null) { | ||
| 286 | - restingHeartRate.value = _formatMetric(recent!.heartRate); | ||
| 287 | - if (sleepAverageHeartRate.value == '--') { | ||
| 288 | - sleepAverageHeartRate.value = _formatMetric(recent.heartRate); | ||
| 289 | - } | ||
| 290 | - } | ||
| 291 | - | ||
| 292 | - _applySleepDuration(today.sleepDuration); | ||
| 293 | - _applyActivityRecentData(recent); | ||
| 294 | - | ||
| 295 | - final hrvPoints = _buildHrvChartData(today.hrvDataList); | ||
| 296 | - hrvChartData.assignAll(hrvPoints); | ||
| 297 | - stressChartData.assignAll( | ||
| 298 | - hrvPoints.map((point) { | ||
| 299 | - return HrvDataPoint( | ||
| 300 | - hour: point.hour, | ||
| 301 | - hrv: _stressScoreFromHrv(point.hrv), | ||
| 302 | - ); | ||
| 303 | - }), | ||
| 304 | - ); | ||
| 305 | - | ||
| 306 | - final latest = _latestTodayHrv(today.hrvDataList); | ||
| 307 | - _applyLatestTodayHrv(latest); | ||
| 308 | - | ||
| 309 | - final hrvValues = hrvPoints.map((point) => point.hrv).toList(); | ||
| 310 | - if (hrvValues.isNotEmpty) { | ||
| 311 | - final average = hrvValues.reduce((value, element) => value + element) / | ||
| 312 | - hrvValues.length; | ||
| 313 | - avgHrv.value = _formatMetric(average); | ||
| 314 | - | ||
| 315 | - final latestStatus = latest?.hrvStatus; | ||
| 316 | - if (latestStatus != null) { | ||
| 317 | - stressLabel.value = latestStatus.title; | ||
| 318 | - } | ||
| 319 | - | ||
| 320 | - // 更新进度条压力分 | ||
| 321 | - final latestHrvVal = latest?.value ?? average; | ||
| 322 | - stressScore.value = null; // _computeStressScore(latestHrvVal); | ||
| 323 | - | ||
| 324 | - _updateLatestHrvAnnotation(latest); | ||
| 325 | - } | ||
| 326 | - } | ||
| 327 | - | ||
| 328 | - void _applyLatestHrvData(LatestHrvData latestHrv) { | ||
| 329 | - final userHrv = latestHrv.userHrv; | ||
| 330 | - if (avgHrv.value == '--' && userHrv != null) { | ||
| 331 | - avgHrv.value = _formatMetric(userHrv); | ||
| 332 | - } | ||
| 333 | - if (userHrv != null) { | ||
| 334 | - stressLabel.value = _hrvStatusTitle( | ||
| 335 | - value: userHrv, | ||
| 336 | - baseline: latestHrv.userHrvBaseline, | ||
| 337 | - ); | ||
| 338 | - // 更新进度条压力分 | ||
| 339 | - stressScore.value = null; | ||
| 340 | - //_computeStressScore(userHrv,baseline: latestHrv.userHrvBaseline,); | ||
| 341 | - } | ||
| 342 | } | 266 | } |
| 343 | 267 | ||
| 344 | void _applyHrvStatistics(HrvStatisticsData data, DateTime date) { | 268 | void _applyHrvStatistics(HrvStatisticsData data, DateTime date) { |
| @@ -365,36 +289,6 @@ class TodayController extends GetxController { | @@ -365,36 +289,6 @@ class TodayController extends GetxController { | ||
| 365 | if (data.avgRestingHeartRate != null) { | 289 | if (data.avgRestingHeartRate != null) { |
| 366 | restingHeartRate.value = _formatMetric(data.avgRestingHeartRate); | 290 | restingHeartRate.value = _formatMetric(data.avgRestingHeartRate); |
| 367 | } | 291 | } |
| 368 | - if (data.avgSleepingHeartRate != null) { | ||
| 369 | - sleepAverageHeartRate.value = _formatMetric(data.avgSleepingHeartRate); | ||
| 370 | - } | ||
| 371 | - } | ||
| 372 | - | ||
| 373 | - void _applySleepStatistics(SleepStatisticsData data, DateTime date) { | ||
| 374 | - final dateKey = date.day; | ||
| 375 | - final isToday = DateUtils.isSameDay(date, lastSelectableDay); | ||
| 376 | - | ||
| 377 | - if (isToday) { | ||
| 378 | - if (sleepHours.value == '--' && data.avgSleepDuration != null) { | ||
| 379 | - _applySleepDuration(data.avgSleepDuration); | ||
| 380 | - } | ||
| 381 | - } else { | ||
| 382 | - int? dailySleep; | ||
| 383 | - if (data.sleepTrendList != null) { | ||
| 384 | - for (final trend in data.sleepTrendList!) { | ||
| 385 | - if (trend.timeKey == dateKey) { | ||
| 386 | - dailySleep = trend.average; | ||
| 387 | - break; | ||
| 388 | - } | ||
| 389 | - } | ||
| 390 | - } | ||
| 391 | - _applySleepDuration(dailySleep); | ||
| 392 | - } | ||
| 393 | - | ||
| 394 | - final deepPercentage = data.deepPercentage; | ||
| 395 | - if (deepPercentage != null) { | ||
| 396 | - sleepQuality.value = _sleepQualityFromDeepPercentage(deepPercentage); | ||
| 397 | - } | ||
| 398 | } | 292 | } |
| 399 | 293 | ||
| 400 | void _applyActivityStatistics( | 294 | void _applyActivityStatistics( |
| @@ -475,22 +369,6 @@ class TodayController extends GetxController { | @@ -475,22 +369,6 @@ class TodayController extends GetxController { | ||
| 475 | if (value == null) return; | 369 | if (value == null) return; |
| 476 | } | 370 | } |
| 477 | 371 | ||
| 478 | - void _applySleepDuration(int? rawDuration) { | ||
| 479 | - final minutes = _normalizeDurationMinutes(rawDuration); | ||
| 480 | - if (minutes == null) { | ||
| 481 | - sleepHours.value = '--'; | ||
| 482 | - sleepMinutes.value = '--'; | ||
| 483 | - sleepQuality.value = '--'; | ||
| 484 | - sleepProgress.value = 0; | ||
| 485 | - return; | ||
| 486 | - } | ||
| 487 | - | ||
| 488 | - sleepHours.value = '${minutes ~/ 60}'; | ||
| 489 | - sleepMinutes.value = '${minutes % 60}'; | ||
| 490 | - sleepQuality.value = _sleepQualityFromDuration(minutes); | ||
| 491 | - sleepProgress.value = _progress(minutes.toDouble(), 8 * 60); | ||
| 492 | - } | ||
| 493 | - | ||
| 494 | void _applyActivityRecentData(TodayStatusRecentData? recent) { | 372 | void _applyActivityRecentData(TodayStatusRecentData? recent) { |
| 495 | activityCalories.value = _formatMetric(recent?.move); | 373 | activityCalories.value = _formatMetric(recent?.move); |
| 496 | activityExerciseMinutes.value = _formatMetric(recent?.exercise); | 374 | activityExerciseMinutes.value = _formatMetric(recent?.exercise); |
| @@ -535,45 +413,6 @@ class TodayController extends GetxController { | @@ -535,45 +413,6 @@ class TodayController extends GetxController { | ||
| 535 | return latest; | 413 | return latest; |
| 536 | } | 414 | } |
| 537 | 415 | ||
| 538 | - void _updateLatestHrvAnnotation(TodayHrvData? latest) { | ||
| 539 | - if (latest?.time == null || latest?.value == null) { | ||
| 540 | - hrvAnnotations.clear(); | ||
| 541 | - return; | ||
| 542 | - } | ||
| 543 | - | ||
| 544 | - final hour = _hourFromApiTime(latest!.time!); | ||
| 545 | - hrvAnnotations.assignAll([ | ||
| 546 | - HrvAnnotation( | ||
| 547 | - hour: hour, | ||
| 548 | - hrv: latest.value!, | ||
| 549 | - stateLabel: latest.hrvStatus?.title ?? stressLabel.value, | ||
| 550 | - detail: 'HRV ${_formatMetric(latest.value)}ms · ${_formatHour(hour)}', | ||
| 551 | - ), | ||
| 552 | - ]); | ||
| 553 | - } | ||
| 554 | - | ||
| 555 | - /// 将 HRV 值映射为压力分 0~100(足基线时按相对偏差映射,无基线时按 100-HRV)。 | ||
| 556 | - int _computeStressScore(double hrv, {double? baseline}) { | ||
| 557 | - if ((baseline ?? 0) > 0) { | ||
| 558 | - final ratio = hrv / baseline!; | ||
| 559 | - // ratio≥1.2 → 0~20(优秀) | ||
| 560 | - if (ratio >= 1.2) | ||
| 561 | - return ((1.2 - ratio.clamp(1.2, 1.5)) / 0.3 * 20).round().clamp(0, 20); | ||
| 562 | - // ratio ∈ [0.9,1.2) → 21~50(正常) | ||
| 563 | - if (ratio >= 0.9) | ||
| 564 | - return (((1.2 - ratio) / 0.3) * 29 + 21).round().clamp(21, 50); | ||
| 565 | - // ratio ∈ [0.8,0.9) → 51~80(注意) | ||
| 566 | - if (ratio >= 0.8) | ||
| 567 | - return (((0.9 - ratio) / 0.1) * 29 + 51).round().clamp(51, 80); | ||
| 568 | - // ratio < 0.8 → 81~100(过载) | ||
| 569 | - return ((0.8 - ratio.clamp(0.4, 0.8)) / 0.4 * 19 + 81) | ||
| 570 | - .round() | ||
| 571 | - .clamp(81, 100); | ||
| 572 | - } | ||
| 573 | - // 无基线:100 - HRV,HRV 越高 → 分数越低 | ||
| 574 | - return (100 - hrv).clamp(0, 100).round(); | ||
| 575 | - } | ||
| 576 | - | ||
| 577 | String _hrvStatusTitle({required double value, double? baseline}) { | 416 | String _hrvStatusTitle({required double value, double? baseline}) { |
| 578 | if ((baseline ?? 0) > 0) { | 417 | if ((baseline ?? 0) > 0) { |
| 579 | if (value >= 1.2 * baseline!) return HrvStatus.energetic.title; | 418 | if (value >= 1.2 * baseline!) return HrvStatus.energetic.title; |
| @@ -653,4 +492,49 @@ class TodayController extends GetxController { | @@ -653,4 +492,49 @@ class TodayController extends GetxController { | ||
| 653 | final m = (totalMinutes % 60).toString().padLeft(2, '0'); | 492 | final m = (totalMinutes % 60).toString().padLeft(2, '0'); |
| 654 | return '$h:$m'; | 493 | return '$h:$m'; |
| 655 | } | 494 | } |
| 495 | + | ||
| 496 | + toTrendHrvPage() { | ||
| 497 | + if (isFriend) { | ||
| 498 | + Get.toNamed( | ||
| 499 | + Routes.FRIEND_TREND, | ||
| 500 | + arguments: FriendTrendArguments( | ||
| 501 | + userId: 58, | ||
| 502 | + name: 'xxa', | ||
| 503 | + avatarUrl: 'asdfa', | ||
| 504 | + ), | ||
| 505 | + ); | ||
| 506 | + } else { | ||
| 507 | + Get.find<HomeController>().openTrend(TrendType.hrv); | ||
| 508 | + } | ||
| 509 | + } | ||
| 510 | + | ||
| 511 | + toTrendSleepPage() { | ||
| 512 | + if (isFriend) { | ||
| 513 | + Get.toNamed( | ||
| 514 | + Routes.FRIEND_TREND, | ||
| 515 | + arguments: FriendTrendArguments( | ||
| 516 | + userId: 58, | ||
| 517 | + name: 'xxa', | ||
| 518 | + avatarUrl: 'asdfa', | ||
| 519 | + ), | ||
| 520 | + ); | ||
| 521 | + } else { | ||
| 522 | + Get.find<HomeController>().openTrend(TrendType.sleep); | ||
| 523 | + } | ||
| 524 | + } | ||
| 525 | + | ||
| 526 | + toTrendActivityPage() { | ||
| 527 | + if (isFriend) { | ||
| 528 | + Get.toNamed( | ||
| 529 | + Routes.FRIEND_TREND, | ||
| 530 | + arguments: FriendTrendArguments( | ||
| 531 | + userId: 58, | ||
| 532 | + name: 'xxa', | ||
| 533 | + avatarUrl: 'asdfa', | ||
| 534 | + ), | ||
| 535 | + ); | ||
| 536 | + } else { | ||
| 537 | + Get.find<HomeController>().openTrend(TrendType.activity); | ||
| 538 | + } | ||
| 539 | + } | ||
| 656 | } | 540 | } |
| 1 | +import 'package:cached_network_image/cached_network_image.dart'; | ||
| 1 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measurement_bottom_sheet.dart'; | 2 | import 'package:doublefeel_flutter/app/modules/home/widgets/today/hrv_measurement_bottom_sheet.dart'; |
| 2 | import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; | 3 | import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; |
| 3 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/report_common/widgets/report_date_picker_sheet.dart'; |
| @@ -81,14 +82,17 @@ class TodayTabBody extends StatelessWidget { | @@ -81,14 +82,17 @@ class TodayTabBody extends StatelessWidget { | ||
| 81 | () => Column( | 82 | () => Column( |
| 82 | mainAxisSize: MainAxisSize.min, | 83 | mainAxisSize: MainAxisSize.min, |
| 83 | children: [ | 84 | children: [ |
| 84 | - _TopDateBar( | ||
| 85 | - title: _dateTitle(context, controller.selectedDate.value), | ||
| 86 | - showBackToToday: !isSameDay( | ||
| 87 | - controller.selectedDate.value, | ||
| 88 | - controller.lastSelectableDay, | ||
| 89 | - ), | ||
| 90 | - onBackToToday: _backToToday, | ||
| 91 | - ), | 85 | + controller.isFriend |
| 86 | + ? _TopFriendBar() | ||
| 87 | + : _TopDateBar( | ||
| 88 | + title: _dateTitle( | ||
| 89 | + context, controller.selectedDate.value), | ||
| 90 | + showBackToToday: !isSameDay( | ||
| 91 | + controller.selectedDate.value, | ||
| 92 | + controller.lastSelectableDay, | ||
| 93 | + ), | ||
| 94 | + onBackToToday: _backToToday, | ||
| 95 | + ), | ||
| 92 | _buildWeekCalendar(context), | 96 | _buildWeekCalendar(context), |
| 93 | ], | 97 | ], |
| 94 | ), | 98 | ), |
| @@ -156,7 +160,7 @@ class TodayTabBody extends StatelessWidget { | @@ -156,7 +160,7 @@ class TodayTabBody extends StatelessWidget { | ||
| 156 | ), | 160 | ), |
| 157 | Obx( | 161 | Obx( |
| 158 | () => Text( | 162 | () => Text( |
| 159 | - controller.stressLabel.value, | 163 | + controller.v2StressScore.value?.stateString() ?? '', |
| 160 | textAlign: TextAlign.center, | 164 | textAlign: TextAlign.center, |
| 161 | style: TextStyle( | 165 | style: TextStyle( |
| 162 | color: context.colors.textPrimary, | 166 | color: context.colors.textPrimary, |
| @@ -168,7 +172,9 @@ class TodayTabBody extends StatelessWidget { | @@ -168,7 +172,9 @@ class TodayTabBody extends StatelessWidget { | ||
| 168 | const SizedBox(height: 12), | 172 | const SizedBox(height: 12), |
| 169 | Obx( | 173 | Obx( |
| 170 | () => StressProgressBar( | 174 | () => StressProgressBar( |
| 171 | - score: controller.stressScore.value, | 175 | + score: controller.v2StressScore.value?.state == 0 |
| 176 | + ? null | ||
| 177 | + : controller.v2StressScore.value?.comprehensiveScore, | ||
| 172 | ), | 178 | ), |
| 173 | ), | 179 | ), |
| 174 | ], | 180 | ], |
| @@ -429,6 +435,9 @@ class TodayTabBody extends StatelessWidget { | @@ -429,6 +435,9 @@ class TodayTabBody extends StatelessWidget { | ||
| 429 | if (isSameDay(selectedDay, today.subtract(const Duration(days: 1)))) { | 435 | if (isSameDay(selectedDay, today.subtract(const Duration(days: 1)))) { |
| 430 | return context.l10n.yesterday; | 436 | return context.l10n.yesterday; |
| 431 | } | 437 | } |
| 438 | + if (isSameDay(selectedDay, today.subtract(const Duration(days: 2)))) { | ||
| 439 | + return '前天'; | ||
| 440 | + } | ||
| 432 | return MaterialLocalizations.of(context).formatShortMonthDay(selectedDay); | 441 | return MaterialLocalizations.of(context).formatShortMonthDay(selectedDay); |
| 433 | } | 442 | } |
| 434 | 443 | ||
| @@ -480,6 +489,8 @@ class _LatestHrvCard extends StatelessWidget { | @@ -480,6 +489,8 @@ class _LatestHrvCard extends StatelessWidget { | ||
| 480 | final latestHrv = controller.v2LatestHrv.value?.latestHrv; | 489 | final latestHrv = controller.v2LatestHrv.value?.latestHrv; |
| 481 | final latestDataTime = controller.v2LatestHrv.value?.latestDataTime; | 490 | final latestDataTime = controller.v2LatestHrv.value?.latestDataTime; |
| 482 | final state = controller.v2LatestHrv.value?.state; | 491 | final state = controller.v2LatestHrv.value?.state; |
| 492 | + final stateStr = controller.v2LatestHrv.value?.stateString(); | ||
| 493 | + final stateColor = controller.v2LatestHrv.value?.stateColor(context); | ||
| 483 | final tip = controller.v2LatestHrv.value?.tip; | 494 | final tip = controller.v2LatestHrv.value?.tip; |
| 484 | 495 | ||
| 485 | if (latestHrv == null || | 496 | if (latestHrv == null || |
| @@ -488,11 +499,9 @@ class _LatestHrvCard extends StatelessWidget { | @@ -488,11 +499,9 @@ class _LatestHrvCard extends StatelessWidget { | ||
| 488 | tip == null) { | 499 | tip == null) { |
| 489 | return const SizedBox.shrink(); | 500 | return const SizedBox.shrink(); |
| 490 | } | 501 | } |
| 491 | - final latestHrvText = '$latestHrv ms'; | ||
| 492 | - final latestDataTimeText = | ||
| 493 | - '${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(latestDataTime * 1000))}'; | ||
| 494 | - final stateText = | ||
| 495 | - '${state == 1 ? '状态优秀' : state == 2 ? '状态' : state == 3 ? '状态一般' : state == 4 ? '状态较差' : '状态极差'}'; | 502 | + |
| 503 | + final latestDataTimeText = DateFormat('HH:mm') | ||
| 504 | + .format(DateTime.fromMillisecondsSinceEpoch(latestDataTime * 1000)); | ||
| 496 | return Container( | 505 | return Container( |
| 497 | padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), | 506 | padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), |
| 498 | decoration: BoxDecoration( | 507 | decoration: BoxDecoration( |
| @@ -517,13 +526,13 @@ class _LatestHrvCard extends StatelessWidget { | @@ -517,13 +526,13 @@ class _LatestHrvCard extends StatelessWidget { | ||
| 517 | width: 12, | 526 | width: 12, |
| 518 | height: 12, | 527 | height: 12, |
| 519 | decoration: BoxDecoration( | 528 | decoration: BoxDecoration( |
| 520 | - color: context.colors.chartGreen, | 529 | + color: stateColor, |
| 521 | shape: BoxShape.circle, | 530 | shape: BoxShape.circle, |
| 522 | ), | 531 | ), |
| 523 | ), | 532 | ), |
| 524 | const SizedBox(width: 8), | 533 | const SizedBox(width: 8), |
| 525 | Text( | 534 | Text( |
| 526 | - '${latestHrvText}ms·$stateText', | 535 | + '${latestHrv}ms · $stateStr', |
| 527 | style: TextStyle( | 536 | style: TextStyle( |
| 528 | color: colors.textPrimary, | 537 | color: colors.textPrimary, |
| 529 | fontSize: 18, | 538 | fontSize: 18, |
| @@ -550,6 +559,64 @@ class _LatestHrvCard extends StatelessWidget { | @@ -550,6 +559,64 @@ class _LatestHrvCard extends StatelessWidget { | ||
| 550 | } | 559 | } |
| 551 | } | 560 | } |
| 552 | 561 | ||
| 562 | +class _TopFriendBar extends StatelessWidget { | ||
| 563 | + final _topBarHeight = 48.0; | ||
| 564 | + | ||
| 565 | + @override | ||
| 566 | + Widget build(BuildContext context) { | ||
| 567 | + return SizedBox( | ||
| 568 | + height: _topBarHeight, | ||
| 569 | + child: Padding( | ||
| 570 | + padding: const EdgeInsets.symmetric(vertical: 4), | ||
| 571 | + child: Row( | ||
| 572 | + children: [ | ||
| 573 | + IconButton( | ||
| 574 | + highlightColor: Colors.transparent, | ||
| 575 | + padding: EdgeInsets.zero, | ||
| 576 | + onPressed: Get.back, | ||
| 577 | + icon: Image.asset( | ||
| 578 | + 'assets/images/common/ic_nav_back.webp', | ||
| 579 | + width: 24, | ||
| 580 | + height: 24, | ||
| 581 | + ), | ||
| 582 | + ), | ||
| 583 | + Container( | ||
| 584 | + width: 28, | ||
| 585 | + height: 28, | ||
| 586 | + margin: EdgeInsets.only(left: 4, right: 8), | ||
| 587 | + decoration: ShapeDecoration( | ||
| 588 | + shape: RoundedRectangleBorder( | ||
| 589 | + side: BorderSide(width: 0.78, color: Colors.white), | ||
| 590 | + borderRadius: BorderRadius.circular(35.78), | ||
| 591 | + ), | ||
| 592 | + ), | ||
| 593 | + child: CachedNetworkImage(imageUrl: 'imageUrl'), | ||
| 594 | + ), | ||
| 595 | + Text( | ||
| 596 | + '男朋友的状态', | ||
| 597 | + style: TextStyle( | ||
| 598 | + color: Colors.black, | ||
| 599 | + fontSize: 16, | ||
| 600 | + fontWeight: FontWeight.w500, | ||
| 601 | + ), | ||
| 602 | + ), | ||
| 603 | + Spacer(), | ||
| 604 | + IconButton( | ||
| 605 | + highlightColor: Colors.transparent, | ||
| 606 | + padding: EdgeInsets.zero, | ||
| 607 | + onPressed: Get.back, | ||
| 608 | + icon: Image.asset( | ||
| 609 | + 'assets/images/common/ic_replace.png', | ||
| 610 | + width: 20, | ||
| 611 | + height: 20, | ||
| 612 | + ), | ||
| 613 | + ), | ||
| 614 | + ], | ||
| 615 | + ), | ||
| 616 | + )); | ||
| 617 | + } | ||
| 618 | +} | ||
| 619 | + | ||
| 553 | class _TopDateBar extends StatelessWidget { | 620 | class _TopDateBar extends StatelessWidget { |
| 554 | const _TopDateBar({ | 621 | const _TopDateBar({ |
| 555 | required this.title, | 622 | required this.title, |
| @@ -30,11 +30,10 @@ class AccountSettingView extends GetView<MyController> { | @@ -30,11 +30,10 @@ class AccountSettingView extends GetView<MyController> { | ||
| 30 | highlightColor: Colors.transparent, | 30 | highlightColor: Colors.transparent, |
| 31 | padding: EdgeInsets.zero, | 31 | padding: EdgeInsets.zero, |
| 32 | onPressed: Get.back, | 32 | onPressed: Get.back, |
| 33 | - icon: Image( | 33 | + icon: Image.asset( |
| 34 | + 'assets/images/common/ic_nav_back.webp', | ||
| 34 | width: 24, | 35 | width: 24, |
| 35 | height: 24, | 36 | height: 24, |
| 36 | - alignment: Alignment.centerLeft, | ||
| 37 | - image: AssetImage(R.assetsImagesNavBackIcon), | ||
| 38 | ), | 37 | ), |
| 39 | ), | 38 | ), |
| 40 | ), | 39 | ), |
| @@ -131,19 +131,25 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -131,19 +131,25 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 131 | ), | 131 | ), |
| 132 | ), | 132 | ), |
| 133 | const Spacer(), | 133 | const Spacer(), |
| 134 | - Text( | ||
| 135 | - context.l10n.more, | ||
| 136 | - style: TextStyle( | ||
| 137 | - color: context.colors.textSecondary, | ||
| 138 | - fontSize: 12, | ||
| 139 | - fontWeight: FontWeight.w400, | 134 | + GestureDetector( |
| 135 | + onTap: controller.toTrendHrvPage, | ||
| 136 | + child: Text( | ||
| 137 | + context.l10n.more, | ||
| 138 | + style: TextStyle( | ||
| 139 | + color: context.colors.textSecondary, | ||
| 140 | + fontSize: 12, | ||
| 141 | + fontWeight: FontWeight.w400, | ||
| 142 | + ), | ||
| 140 | ), | 143 | ), |
| 141 | ), | 144 | ), |
| 142 | - Image.asset( | ||
| 143 | - 'assets/images/common/ic_more_gray.png', | ||
| 144 | - width: 16, | ||
| 145 | - height: 16, | ||
| 146 | - color: context.colors.textTertiary, | 145 | + GestureDetector( |
| 146 | + onTap: controller.toTrendHrvPage, | ||
| 147 | + child: Image.asset( | ||
| 148 | + 'assets/images/common/ic_more_gray.png', | ||
| 149 | + width: 16, | ||
| 150 | + height: 16, | ||
| 151 | + color: context.colors.textTertiary, | ||
| 152 | + ), | ||
| 147 | ), | 153 | ), |
| 148 | ], | 154 | ], |
| 149 | ); | 155 | ); |
| @@ -13,75 +13,83 @@ class TodaySleepCard extends StatelessWidget { | @@ -13,75 +13,83 @@ class TodaySleepCard extends StatelessWidget { | ||
| 13 | 13 | ||
| 14 | @override | 14 | @override |
| 15 | Widget build(BuildContext context) { | 15 | Widget build(BuildContext context) { |
| 16 | - return Obx( | ||
| 17 | - () => _TodaySummaryCard( | ||
| 18 | - iconAsset: 'assets/images/common/ic_sleep_stroke.png', | ||
| 19 | - iconColor: context.colors.primary, | ||
| 20 | - title: context.l10n.sleep, | ||
| 21 | - actionText: context.l10n.viewSleepReport, | ||
| 22 | - progressValues: [controller.sleepProgress.value], | ||
| 23 | - progressColors: [context.colors.primary], | ||
| 24 | - metrics: [ | ||
| 25 | - _MetricData( | ||
| 26 | - label: context.l10n.duration, | ||
| 27 | - labelColor: context.colors.primary, | ||
| 28 | - valueSpans: [ | ||
| 29 | - _MetricValueSpan(controller.sleepHours.value, '小时'), | ||
| 30 | - _MetricValueSpan(controller.sleepMinutes.value, '分钟'), | ||
| 31 | - ], | ||
| 32 | - ), | ||
| 33 | - _MetricData( | ||
| 34 | - label: context.l10n.quality, | ||
| 35 | - labelColor: const Color(0xFF7B9BFB), | ||
| 36 | - valueSpans: [ | ||
| 37 | - _MetricValueSpan( | ||
| 38 | - controller.sleepQuality.value, | ||
| 39 | - '', | ||
| 40 | - valueColor: _qualityColor(controller.sleepQuality.value), | ||
| 41 | - ), | ||
| 42 | - ], | ||
| 43 | - ), | ||
| 44 | - _MetricData( | ||
| 45 | - label: context.l10n.averageHeartRate, | ||
| 46 | - labelColor: context.colors.textSecondary, | ||
| 47 | - valueSpans: [ | ||
| 48 | - _MetricValueSpan(controller.sleepAverageHeartRate.value, 'bpm'), | ||
| 49 | - ], | ||
| 50 | - ), | ||
| 51 | - ], | ||
| 52 | - rightWidget: Container( | ||
| 53 | - width: 50, | ||
| 54 | - height: 50, | ||
| 55 | - margin: EdgeInsets.only(right: 6, left: 4), | ||
| 56 | - child: CircularGradientCombineWidget( | ||
| 57 | - size: 50, | ||
| 58 | - duration: const Duration(seconds: 1), | ||
| 59 | - sweepAngles: const [240, 210], | ||
| 60 | - backgroundColors: const [ | ||
| 61 | - Color(0xFFF3F3F3), | ||
| 62 | - Color(0xFFF3F3F3), | 16 | + return GestureDetector( |
| 17 | + onTap: controller.toTrendSleepPage, | ||
| 18 | + child: Obx(() { | ||
| 19 | + var healthData = controller.v2HealthData.value; | ||
| 20 | + | ||
| 21 | + int sleepDuration = healthData?.sleepDuration ?? 0; | ||
| 22 | + int sleepHours = sleepDuration ~/ 3600; | ||
| 23 | + int sleepMinutes = sleepDuration % 60; | ||
| 24 | + Color sleepStateColor = | ||
| 25 | + healthData?.sleepStateColor(context) ?? context.colors.chartGreen; | ||
| 26 | + String sleepStateStr = healthData?.sleepStateStr() ?? '--'; | ||
| 27 | + String sleepAverageHeartRate = | ||
| 28 | + (healthData?.hrAvg == null || healthData?.hrAvg == 0) | ||
| 29 | + ? '--' | ||
| 30 | + : '${healthData?.hrAvg}'; | ||
| 31 | + double sleepDurationProgress = | ||
| 32 | + ((healthData?.sleepDurationScore() ?? 0) * 3.60); | ||
| 33 | + double sleepScoreProgress = (healthData?.sleepScore ?? 0.0) * 3.6; | ||
| 34 | + | ||
| 35 | + return _TodaySummaryCard( | ||
| 36 | + iconAsset: 'assets/images/common/ic_sleep_stroke.png', | ||
| 37 | + iconColor: context.colors.primary, | ||
| 38 | + title: context.l10n.sleep, | ||
| 39 | + actionText: context.l10n.viewSleepReport, | ||
| 40 | + metrics: [ | ||
| 41 | + _MetricData( | ||
| 42 | + label: context.l10n.duration, | ||
| 43 | + labelColor: context.colors.primary, | ||
| 44 | + valueSpans: [ | ||
| 45 | + _MetricValueSpan(sleepHours > 0 ? '$sleepHours' : '--', '小时'), | ||
| 46 | + _MetricValueSpan( | ||
| 47 | + sleepMinutes > 0 ? '$sleepMinutes' : '--', '分钟'), | ||
| 63 | ], | 48 | ], |
| 64 | - gradientColors: [ | ||
| 65 | - [context.colors.primary], | ||
| 66 | - [context.colors.chartBlue], | 49 | + ), |
| 50 | + _MetricData( | ||
| 51 | + label: context.l10n.quality, | ||
| 52 | + labelColor: const Color(0xFF7B9BFB), | ||
| 53 | + valueSpans: [ | ||
| 54 | + _MetricValueSpan( | ||
| 55 | + sleepStateStr, | ||
| 56 | + '', | ||
| 57 | + valueColor: sleepStateColor, | ||
| 58 | + ), | ||
| 67 | ], | 59 | ], |
| 68 | - centerCircleSizeRatio: 0.3, | ||
| 69 | - gapRatio: 0.1, | ||
| 70 | - interpolatedColorRatio: 1), | ||
| 71 | - ), | ||
| 72 | - ), | 60 | + ), |
| 61 | + _MetricData( | ||
| 62 | + label: context.l10n.averageHeartRate, | ||
| 63 | + labelColor: context.colors.textSecondary, | ||
| 64 | + valueSpans: [ | ||
| 65 | + _MetricValueSpan(sleepAverageHeartRate, 'bpm'), | ||
| 66 | + ], | ||
| 67 | + ), | ||
| 68 | + ], | ||
| 69 | + rightWidget: Container( | ||
| 70 | + width: 50, | ||
| 71 | + height: 50, | ||
| 72 | + margin: EdgeInsets.only(right: 6, left: 4), | ||
| 73 | + child: CircularGradientCombineWidget( | ||
| 74 | + size: 50, | ||
| 75 | + duration: const Duration(seconds: 1), | ||
| 76 | + sweepAngles: [sleepDurationProgress, sleepScoreProgress], | ||
| 77 | + backgroundColors: const [ | ||
| 78 | + Color(0xFFF3F3F3), | ||
| 79 | + Color(0xFFF3F3F3), | ||
| 80 | + ], | ||
| 81 | + gradientColors: [ | ||
| 82 | + [context.colors.primary], | ||
| 83 | + [context.colors.chartBlue], | ||
| 84 | + ], | ||
| 85 | + centerCircleSizeRatio: 0.3, | ||
| 86 | + gapRatio: 0.1, | ||
| 87 | + interpolatedColorRatio: 1), | ||
| 88 | + ), | ||
| 89 | + ); | ||
| 90 | + }), | ||
| 73 | ); | 91 | ); |
| 74 | } | 92 | } |
| 75 | - | ||
| 76 | - Color _qualityColor(String quality) { | ||
| 77 | - return switch (quality) { | ||
| 78 | - '优秀' => const Color(0xFF3BD49D), | ||
| 79 | - '良好' => const Color(0xFF7B9BFB), | ||
| 80 | - '一般' => const Color(0xFFFF9A6E), | ||
| 81 | - '偏少' => const Color(0xFFFF5279), | ||
| 82 | - _ => const Color(0xFF0F0F11), | ||
| 83 | - }; | ||
| 84 | - } | ||
| 85 | } | 93 | } |
| 86 | 94 | ||
| 87 | class TodayActivityCard extends StatelessWidget { | 95 | class TodayActivityCard extends StatelessWidget { |
| @@ -91,91 +99,93 @@ class TodayActivityCard extends StatelessWidget { | @@ -91,91 +99,93 @@ class TodayActivityCard extends StatelessWidget { | ||
| 91 | 99 | ||
| 92 | @override | 100 | @override |
| 93 | Widget build(BuildContext context) { | 101 | Widget build(BuildContext context) { |
| 94 | - return Obx( | ||
| 95 | - () => _TodaySummaryCard( | ||
| 96 | - iconAsset: 'assets/images/common/ic_exercise.png', | ||
| 97 | - iconColor: const Color(0xFFFF5279), | ||
| 98 | - title: context.l10n.fitness, | ||
| 99 | - actionText: context.l10n.viewFitnessReport, | ||
| 100 | - progressValues: [ | ||
| 101 | - controller.activityMoveProgress.value, | ||
| 102 | - controller.activityExerciseProgress.value, | ||
| 103 | - controller.activityStandProgress.value, | ||
| 104 | - ], | ||
| 105 | - progressColors: const [ | ||
| 106 | - Color(0xFFFF5279), | ||
| 107 | - Color(0xFF3BD49D), | ||
| 108 | - Color(0xFF7B9BFB), | ||
| 109 | - ], | ||
| 110 | - metrics: [ | ||
| 111 | - _MetricData( | ||
| 112 | - label: context.l10n.event, | ||
| 113 | - labelColor: const Color(0xFFFF5279), | ||
| 114 | - valueSpans: [ | ||
| 115 | - _MetricValueSpan(controller.activityCalories.value, '千卡'), | ||
| 116 | - ], | ||
| 117 | - ), | ||
| 118 | - _MetricData( | ||
| 119 | - label: context.l10n.exercise, | ||
| 120 | - labelColor: const Color(0xFF3BD49D), | ||
| 121 | - valueSpans: [ | ||
| 122 | - _MetricValueSpan( | ||
| 123 | - controller.activityExerciseMinutes.value, | ||
| 124 | - '分钟', | ||
| 125 | - ), | ||
| 126 | - ], | ||
| 127 | - ), | ||
| 128 | - _MetricData( | ||
| 129 | - label: context.l10n.standing, | ||
| 130 | - labelColor: const Color(0xFF7B9BFB), | ||
| 131 | - valueSpans: [ | ||
| 132 | - _MetricValueSpan(controller.activityStandHours.value, '小时'), | ||
| 133 | - ], | ||
| 134 | - ), | ||
| 135 | - ], | ||
| 136 | - rightWidget: Container( | ||
| 137 | - width: 50, | ||
| 138 | - height: 50, | ||
| 139 | - margin: EdgeInsets.only(right: 6, left: 4), | ||
| 140 | - child: Stack( | ||
| 141 | - children: [ | ||
| 142 | - Container( | ||
| 143 | - decoration: BoxDecoration( | ||
| 144 | - shape: BoxShape.circle, | ||
| 145 | - color: context.colors.textPrimary, | 102 | + return GestureDetector( |
| 103 | + onTap: controller.toTrendActivityPage, | ||
| 104 | + child: Obx(() { | ||
| 105 | + var healthData = controller.v2HealthData.value; | ||
| 106 | + var activity = healthData?.move; | ||
| 107 | + var stand = healthData?.stand ?? 0; | ||
| 108 | + var exercise = healthData?.exercise ?? 0; | ||
| 109 | + var standStr = stand > 0 ? '${stand ~/ 3600}' : '--'; | ||
| 110 | + var exerciseStr = exercise > 0 ? '${exercise % 60}' : '--'; | ||
| 111 | + | ||
| 112 | + return _TodaySummaryCard( | ||
| 113 | + iconAsset: 'assets/images/common/ic_exercise.png', | ||
| 114 | + iconColor: const Color(0xFFFF5279), | ||
| 115 | + title: context.l10n.fitness, | ||
| 116 | + actionText: context.l10n.viewFitnessReport, | ||
| 117 | + metrics: [ | ||
| 118 | + _MetricData( | ||
| 119 | + label: context.l10n.event, | ||
| 120 | + labelColor: const Color(0xFFFF5279), | ||
| 121 | + valueSpans: [ | ||
| 122 | + _MetricValueSpan( | ||
| 123 | + (activity == null || activity == 0) ? '--' : '$activity', | ||
| 124 | + '千卡'), | ||
| 125 | + ], | ||
| 126 | + ), | ||
| 127 | + _MetricData( | ||
| 128 | + label: context.l10n.exercise, | ||
| 129 | + labelColor: const Color(0xFF3BD49D), | ||
| 130 | + valueSpans: [ | ||
| 131 | + _MetricValueSpan( | ||
| 132 | + exerciseStr, | ||
| 133 | + '分钟', | ||
| 146 | ), | 134 | ), |
| 147 | - ), | ||
| 148 | - CircularGradientCombineWidget( | ||
| 149 | - size: 50, | ||
| 150 | - duration: const Duration(seconds: 1), | ||
| 151 | - centerCircleSizeRatio: 0.2, | ||
| 152 | - gapRatio: 0.06, | ||
| 153 | - sweepAngles: const [ | ||
| 154 | - 180, | ||
| 155 | - 230, | ||
| 156 | - 180, | ||
| 157 | - ], | ||
| 158 | - backgroundColors: [ | ||
| 159 | - context.colors.chartPink.withOpacity(0.4), | ||
| 160 | - context.colors.chartGreen.withOpacity(0.3), | ||
| 161 | - context.colors.chartBlue.withOpacity(0.4), | ||
| 162 | - ], | ||
| 163 | - gradientColors: [ | ||
| 164 | - [ | ||
| 165 | - context.colors.chartPink, | 135 | + ], |
| 136 | + ), | ||
| 137 | + _MetricData( | ||
| 138 | + label: context.l10n.standing, | ||
| 139 | + labelColor: const Color(0xFF7B9BFB), | ||
| 140 | + valueSpans: [ | ||
| 141 | + _MetricValueSpan(standStr, '小时'), | ||
| 142 | + ], | ||
| 143 | + ), | ||
| 144 | + ], | ||
| 145 | + rightWidget: Container( | ||
| 146 | + width: 50, | ||
| 147 | + height: 50, | ||
| 148 | + margin: EdgeInsets.only(right: 6, left: 4), | ||
| 149 | + child: Stack( | ||
| 150 | + children: [ | ||
| 151 | + Container( | ||
| 152 | + decoration: BoxDecoration( | ||
| 153 | + shape: BoxShape.circle, | ||
| 154 | + color: context.colors.textPrimary, | ||
| 155 | + ), | ||
| 156 | + ), | ||
| 157 | + CircularGradientCombineWidget( | ||
| 158 | + size: 50, | ||
| 159 | + duration: const Duration(seconds: 1), | ||
| 160 | + centerCircleSizeRatio: 0.2, | ||
| 161 | + gapRatio: 0.06, | ||
| 162 | + sweepAngles: const [ | ||
| 163 | + 180, | ||
| 164 | + 230, | ||
| 165 | + 180, | ||
| 166 | ], | 166 | ], |
| 167 | - [ | ||
| 168 | - context.colors.chartGreen, | 167 | + backgroundColors: [ |
| 168 | + context.colors.chartPink.withOpacity(0.4), | ||
| 169 | + context.colors.chartGreen.withOpacity(0.3), | ||
| 170 | + context.colors.chartBlue.withOpacity(0.4), | ||
| 169 | ], | 171 | ], |
| 170 | - [ | ||
| 171 | - context.colors.chartBlue, | 172 | + gradientColors: [ |
| 173 | + [ | ||
| 174 | + context.colors.chartPink, | ||
| 175 | + ], | ||
| 176 | + [ | ||
| 177 | + context.colors.chartGreen, | ||
| 178 | + ], | ||
| 179 | + [ | ||
| 180 | + context.colors.chartBlue, | ||
| 181 | + ], | ||
| 172 | ], | 182 | ], |
| 173 | - ], | ||
| 174 | - ), | ||
| 175 | - ], | 183 | + ), |
| 184 | + ], | ||
| 185 | + ), | ||
| 176 | ), | 186 | ), |
| 177 | - ), | ||
| 178 | - ), | 187 | + ); |
| 188 | + }), | ||
| 179 | ); | 189 | ); |
| 180 | } | 190 | } |
| 181 | } | 191 | } |
| @@ -187,8 +197,6 @@ class _TodaySummaryCard extends StatelessWidget { | @@ -187,8 +197,6 @@ class _TodaySummaryCard extends StatelessWidget { | ||
| 187 | required this.title, | 197 | required this.title, |
| 188 | required this.actionText, | 198 | required this.actionText, |
| 189 | required this.metrics, | 199 | required this.metrics, |
| 190 | - required this.progressValues, | ||
| 191 | - required this.progressColors, | ||
| 192 | required this.rightWidget, | 200 | required this.rightWidget, |
| 193 | }); | 201 | }); |
| 194 | 202 | ||
| @@ -197,8 +205,7 @@ class _TodaySummaryCard extends StatelessWidget { | @@ -197,8 +205,7 @@ class _TodaySummaryCard extends StatelessWidget { | ||
| 197 | final String title; | 205 | final String title; |
| 198 | final String actionText; | 206 | final String actionText; |
| 199 | final List<_MetricData> metrics; | 207 | final List<_MetricData> metrics; |
| 200 | - final List<double> progressValues; | ||
| 201 | - final List<Color> progressColors; | 208 | + |
| 202 | final Widget rightWidget; | 209 | final Widget rightWidget; |
| 203 | 210 | ||
| 204 | @override | 211 | @override |
| @@ -63,7 +63,9 @@ class LoginController extends GetxController { | @@ -63,7 +63,9 @@ class LoginController extends GetxController { | ||
| 63 | } | 63 | } |
| 64 | final result = await PlatformHostApi().requestAppleSignIn(); | 64 | final result = await PlatformHostApi().requestAppleSignIn(); |
| 65 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 | 65 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 |
| 66 | - if (result == null || result.identityToken?.isNotEmpty != true) { | 66 | + if (result == null || |
| 67 | + result.identityToken?.isNotEmpty != true || | ||
| 68 | + result.userId.isNotEmpty != true) { | ||
| 67 | appleSignInModel = null; | 69 | appleSignInModel = null; |
| 68 | return; | 70 | return; |
| 69 | } | 71 | } |
| @@ -184,26 +186,31 @@ class LoginController extends GetxController { | @@ -184,26 +186,31 @@ class LoginController extends GetxController { | ||
| 184 | final loginRes = await _userApi.loginWithApple(appleModel); | 186 | final loginRes = await _userApi.loginWithApple(appleModel); |
| 185 | 187 | ||
| 186 | if (loginRes is AppSuccess<LoginResponse>) { | 188 | if (loginRes is AppSuccess<LoginResponse>) { |
| 187 | - await _handleLoginSuccess(loginRes.data, isApple: true); | 189 | + await _handleLoginSuccess(loginRes.data, appleModel: appleModel); |
| 188 | } else { | 190 | } else { |
| 189 | isLoggingIn.value = false; | 191 | isLoggingIn.value = false; |
| 190 | } | 192 | } |
| 191 | } | 193 | } |
| 192 | 194 | ||
| 193 | /// 登录/注册成功后的公共处理逻辑(手机号登录与苹果登录共用) | 195 | /// 登录/注册成功后的公共处理逻辑(手机号登录与苹果登录共用) |
| 194 | - Future<void> _handleLoginSuccess( | ||
| 195 | - LoginResponse loginData, { | ||
| 196 | - bool isApple = false, | ||
| 197 | - }) async { | 196 | + Future<void> _handleLoginSuccess(LoginResponse loginData, |
| 197 | + {AppleSignInModel? appleModel}) async { | ||
| 198 | + bool isApple = appleModel != null; | ||
| 198 | final isRegister = loginData.isNewUser == true; | 199 | final isRegister = loginData.isNewUser == true; |
| 199 | String accessToken = loginData.accessToken; | 200 | String accessToken = loginData.accessToken; |
| 200 | 201 | ||
| 201 | - if (isRegister && !isApple) { | ||
| 202 | - // 苹果登录时后端直接完成注册,无需二次注册请求 | ||
| 203 | - final regRes = await _userApi.register( | ||
| 204 | - telephone: cleanPhone, | ||
| 205 | - verifyCode: codeInput.value, | ||
| 206 | - ); | 202 | + if (isRegister) { |
| 203 | + final regRes = await _userApi.register(isApple | ||
| 204 | + ? { | ||
| 205 | + 'login_type': 'apple', | ||
| 206 | + 'apple_user_id': appleModel.userId, | ||
| 207 | + 'identity_token': appleModel.identityToken, | ||
| 208 | + } | ||
| 209 | + : { | ||
| 210 | + 'login_type': 'verification_code', | ||
| 211 | + 'telephone': cleanPhone, | ||
| 212 | + 'verification_code': codeInput.value, | ||
| 213 | + }); | ||
| 207 | if (regRes is AppSuccess<RegisterResponse>) { | 214 | if (regRes is AppSuccess<RegisterResponse>) { |
| 208 | accessToken = regRes.data.accessToken; | 215 | accessToken = regRes.data.accessToken; |
| 209 | } else { | 216 | } else { |
| @@ -47,14 +47,18 @@ class UserApi { | @@ -47,14 +47,18 @@ class UserApi { | ||
| 47 | Future<AppResult<LoginResponse>> loginWithApple(AppleSignInModel appleModel) { | 47 | Future<AppResult<LoginResponse>> loginWithApple(AppleSignInModel appleModel) { |
| 48 | return safeCall( | 48 | return safeCall( |
| 49 | call: () async { | 49 | call: () async { |
| 50 | + var appleLoginRequest = AppleLoginRequest( | ||
| 51 | + identityToken: appleModel.identityToken ?? '', | ||
| 52 | + userId: appleModel.userId); | ||
| 53 | + if (appleModel.nickname != null) { | ||
| 54 | + appleLoginRequest.nickname = appleModel.nickname; | ||
| 55 | + } | ||
| 56 | + if (appleModel.email != null) { | ||
| 57 | + appleLoginRequest.email = appleModel.email; | ||
| 58 | + } | ||
| 50 | final response = await _dioClient.dio.post( | 59 | final response = await _dioClient.dio.post( |
| 51 | ApiPaths.userLogin, | 60 | ApiPaths.userLogin, |
| 52 | - data: AppleLoginRequest( | ||
| 53 | - identityToken: appleModel.identityToken ?? '', | ||
| 54 | - email: appleModel.email ?? '', | ||
| 55 | - nickname: appleModel.nickname ?? '', | ||
| 56 | - userId: appleModel.userId) | ||
| 57 | - .toJson(), | 61 | + data: appleLoginRequest.toJson(), |
| 58 | options: _dioClient.noTokenOptions(), | 62 | options: _dioClient.noTokenOptions(), |
| 59 | ); | 63 | ); |
| 60 | return LoginResponse.fromJson(response.data as Map<String, dynamic>); | 64 | return LoginResponse.fromJson(response.data as Map<String, dynamic>); |
| @@ -62,18 +66,12 @@ class UserApi { | @@ -62,18 +66,12 @@ class UserApi { | ||
| 62 | ); | 66 | ); |
| 63 | } | 67 | } |
| 64 | 68 | ||
| 65 | - Future<AppResult<RegisterResponse>> register({ | ||
| 66 | - required String telephone, | ||
| 67 | - required String verifyCode, | ||
| 68 | - }) { | 69 | + Future<AppResult<RegisterResponse>> register(Object? registerData) { |
| 69 | return safeCall( | 70 | return safeCall( |
| 70 | call: () async { | 71 | call: () async { |
| 71 | final response = await _dioClient.dio.post( | 72 | final response = await _dioClient.dio.post( |
| 72 | ApiPaths.userRegister, | 73 | ApiPaths.userRegister, |
| 73 | - data: RegisterRequest( | ||
| 74 | - telephone: telephone, | ||
| 75 | - verificationCode: verifyCode, | ||
| 76 | - ).toJson(), | 74 | + data: registerData, |
| 77 | options: _dioClient.noTokenOptions(), | 75 | options: _dioClient.noTokenOptions(), |
| 78 | ); | 76 | ); |
| 79 | return RegisterResponse.fromJson(response.data as Map<String, dynamic>); | 77 | return RegisterResponse.fromJson(response.data as Map<String, dynamic>); |
| 1 | +import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | ||
| 2 | +import 'package:flutter/src/widgets/framework.dart'; | ||
| 3 | + | ||
| 1 | int? _parseInt(dynamic value) { | 4 | int? _parseInt(dynamic value) { |
| 2 | if (value == null) return null; | 5 | if (value == null) return null; |
| 3 | if (value is int) return value; | 6 | if (value is int) return value; |
| @@ -28,6 +31,34 @@ class V2LatestHrvData { | @@ -28,6 +31,34 @@ class V2LatestHrvData { | ||
| 28 | final int? state; | 31 | final int? state; |
| 29 | final String? tip; | 32 | final String? tip; |
| 30 | 33 | ||
| 34 | + String stateString() { | ||
| 35 | + switch (state) { | ||
| 36 | + case 1: | ||
| 37 | + return '压力过载'; | ||
| 38 | + case 2: | ||
| 39 | + return '注意压力'; | ||
| 40 | + case 3: | ||
| 41 | + return '状态正常'; | ||
| 42 | + case 4: | ||
| 43 | + return '状态优秀'; | ||
| 44 | + } | ||
| 45 | + return '等待数据'; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + stateColor(BuildContext context) { | ||
| 49 | + switch (state) { | ||
| 50 | + case 1: | ||
| 51 | + return context.colors.chartPink; | ||
| 52 | + case 2: | ||
| 53 | + return context.colors.chartOrange; | ||
| 54 | + case 3: | ||
| 55 | + return context.colors.chartGreen; | ||
| 56 | + case 4: | ||
| 57 | + return context.colors.chartBlue; | ||
| 58 | + } | ||
| 59 | + return context.colors.chartGreen; | ||
| 60 | + } | ||
| 61 | + | ||
| 31 | factory V2LatestHrvData.fromJson(Map<String, dynamic> json) { | 62 | factory V2LatestHrvData.fromJson(Map<String, dynamic> json) { |
| 32 | return V2LatestHrvData( | 63 | return V2LatestHrvData( |
| 33 | latestHrv: _parseDouble(json['latest_hrv']), | 64 | latestHrv: _parseDouble(json['latest_hrv']), |
| @@ -95,6 +126,43 @@ class V2HealthData { | @@ -95,6 +126,43 @@ class V2HealthData { | ||
| 95 | if (sleepState != null) val['sleep_state'] = sleepState; | 126 | if (sleepState != null) val['sleep_state'] = sleepState; |
| 96 | return val; | 127 | return val; |
| 97 | } | 128 | } |
| 129 | + | ||
| 130 | + /// 根据睡眠时长(秒)计算得分。 | ||
| 131 | + int sleepDurationScore() { | ||
| 132 | + final duration = sleepDuration; | ||
| 133 | + if (duration == null || duration <= 0) return 0; | ||
| 134 | + final hours = duration / 3600.0; | ||
| 135 | + if (hours >= 7 && hours <= 9) return 100; | ||
| 136 | + if (hours > 9 && hours <= 10) return 80; | ||
| 137 | + if (hours >= 6 && hours < 7) return 80; | ||
| 138 | + if (hours >= 5 && hours < 6) return 60; | ||
| 139 | + if (hours > 10) return 50; | ||
| 140 | + return 30; // < 5h | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + sleepStateColor(BuildContext context) { | ||
| 144 | + switch (sleepState) { | ||
| 145 | + case 1: | ||
| 146 | + return context.colors.chartBlue; | ||
| 147 | + case 2: | ||
| 148 | + return context.colors.chartGreen; | ||
| 149 | + case 3: | ||
| 150 | + return context.colors.chartPink; | ||
| 151 | + } | ||
| 152 | + return context.colors.chartGreen; | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + sleepStateStr() { | ||
| 156 | + switch (sleepState) { | ||
| 157 | + case 1: | ||
| 158 | + return '睡得很棒'; | ||
| 159 | + case 2: | ||
| 160 | + return '睡得不错'; | ||
| 161 | + case 3: | ||
| 162 | + return '睡得差'; | ||
| 163 | + } | ||
| 164 | + return '--'; | ||
| 165 | + } | ||
| 98 | } | 166 | } |
| 99 | 167 | ||
| 100 | /// v2/stress_score/ response | 168 | /// v2/stress_score/ response |
| @@ -134,6 +202,20 @@ class V2StressScore { | @@ -134,6 +202,20 @@ class V2StressScore { | ||
| 134 | } | 202 | } |
| 135 | return val; | 203 | return val; |
| 136 | } | 204 | } |
| 205 | + | ||
| 206 | + String stateString() { | ||
| 207 | + switch (state) { | ||
| 208 | + case 1: | ||
| 209 | + return '压力过载'; | ||
| 210 | + case 2: | ||
| 211 | + return '注意压力'; | ||
| 212 | + case 3: | ||
| 213 | + return '状态正常'; | ||
| 214 | + case 4: | ||
| 215 | + return '状态优秀'; | ||
| 216 | + } | ||
| 217 | + return '等待数据'; | ||
| 218 | + } | ||
| 137 | } | 219 | } |
| 138 | 220 | ||
| 139 | /// Single item in v2/hrv_trend/ list | 221 | /// Single item in v2/hrv_trend/ list |
| @@ -19,7 +19,7 @@ class LoginRequest { | @@ -19,7 +19,7 @@ class LoginRequest { | ||
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | class AppleLoginRequest { | 21 | class AppleLoginRequest { |
| 22 | - const AppleLoginRequest({ | 22 | + AppleLoginRequest({ |
| 23 | this.loginType = 'apple', | 23 | this.loginType = 'apple', |
| 24 | required this.identityToken, | 24 | required this.identityToken, |
| 25 | required this.userId, | 25 | required this.userId, |
| @@ -30,8 +30,8 @@ class AppleLoginRequest { | @@ -30,8 +30,8 @@ class AppleLoginRequest { | ||
| 30 | final String loginType; | 30 | final String loginType; |
| 31 | final String identityToken; | 31 | final String identityToken; |
| 32 | final String userId; | 32 | final String userId; |
| 33 | - final String? email; | ||
| 34 | - final String? nickname; | 33 | + String? email; |
| 34 | + String? nickname; | ||
| 35 | 35 | ||
| 36 | Map<String, dynamic> toJson() => { | 36 | Map<String, dynamic> toJson() => { |
| 37 | 'login_type': loginType, | 37 | 'login_type': loginType, |
| @@ -42,21 +42,6 @@ class AppleLoginRequest { | @@ -42,21 +42,6 @@ class AppleLoginRequest { | ||
| 42 | }; | 42 | }; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | -class RegisterRequest { | ||
| 46 | - const RegisterRequest({ | ||
| 47 | - required this.telephone, | ||
| 48 | - required this.verificationCode, | ||
| 49 | - }); | ||
| 50 | - | ||
| 51 | - final String telephone; | ||
| 52 | - final String verificationCode; | ||
| 53 | - | ||
| 54 | - Map<String, dynamic> toJson() => { | ||
| 55 | - 'telephone': telephone, | ||
| 56 | - 'verification_code': verificationCode, | ||
| 57 | - }; | ||
| 58 | -} | ||
| 59 | - | ||
| 60 | class VerifyCodeRequest { | 45 | class VerifyCodeRequest { |
| 61 | const VerifyCodeRequest({required this.telephone, required this.scene}); | 46 | const VerifyCodeRequest({required this.telephone, required this.scene}); |
| 62 | 47 | ||
| @@ -193,19 +178,20 @@ class RegisterResponse { | @@ -193,19 +178,20 @@ class RegisterResponse { | ||
| 193 | } | 178 | } |
| 194 | 179 | ||
| 195 | class UserInfoResponse { | 180 | class UserInfoResponse { |
| 196 | - const UserInfoResponse({ | ||
| 197 | - this.id, | ||
| 198 | - this.pairCode, | ||
| 199 | - this.pairId, | ||
| 200 | - this.telephone, | ||
| 201 | - this.nickname, | ||
| 202 | - this.avatar, | ||
| 203 | - this.persona, | ||
| 204 | - this.status, | ||
| 205 | - this.lastLoginTime, | ||
| 206 | - this.createTime, | ||
| 207 | - this.isBot, | ||
| 208 | - }); | 181 | + const UserInfoResponse( |
| 182 | + {this.id, | ||
| 183 | + this.pairCode, | ||
| 184 | + this.pairId, | ||
| 185 | + this.telephone, | ||
| 186 | + this.nickname, | ||
| 187 | + this.avatar, | ||
| 188 | + this.persona, | ||
| 189 | + this.status, | ||
| 190 | + this.lastLoginTime, | ||
| 191 | + this.createTime, | ||
| 192 | + this.isBot, | ||
| 193 | + this.uniqueCode, | ||
| 194 | + this.enableAddWithUCode}); | ||
| 209 | 195 | ||
| 210 | final int? id; | 196 | final int? id; |
| 211 | final String? pairCode; | 197 | final String? pairCode; |
| @@ -218,6 +204,8 @@ class UserInfoResponse { | @@ -218,6 +204,8 @@ class UserInfoResponse { | ||
| 218 | final int? lastLoginTime; | 204 | final int? lastLoginTime; |
| 219 | final int? createTime; | 205 | final int? createTime; |
| 220 | final int? isBot; | 206 | final int? isBot; |
| 207 | + final String? uniqueCode; | ||
| 208 | + final int? enableAddWithUCode; | ||
| 221 | 209 | ||
| 222 | factory UserInfoResponse.fromJson(Map<String, dynamic> json) { | 210 | factory UserInfoResponse.fromJson(Map<String, dynamic> json) { |
| 223 | return UserInfoResponse( | 211 | return UserInfoResponse( |
| @@ -232,6 +220,8 @@ class UserInfoResponse { | @@ -232,6 +220,8 @@ class UserInfoResponse { | ||
| 232 | lastLoginTime: json['last_login_time'] as int?, | 220 | lastLoginTime: json['last_login_time'] as int?, |
| 233 | createTime: json['create_time'] as int?, | 221 | createTime: json['create_time'] as int?, |
| 234 | isBot: json['is_bot'] as int?, | 222 | isBot: json['is_bot'] as int?, |
| 223 | + uniqueCode: json['unique_code'] as String?, | ||
| 224 | + enableAddWithUCode: json['enable_add_with_ucode'] as int?, | ||
| 235 | ); | 225 | ); |
| 236 | } | 226 | } |
| 237 | 227 | ||
| @@ -248,6 +238,10 @@ class UserInfoResponse { | @@ -248,6 +238,10 @@ class UserInfoResponse { | ||
| 248 | if (lastLoginTime != null) val['last_login_time'] = lastLoginTime; | 238 | if (lastLoginTime != null) val['last_login_time'] = lastLoginTime; |
| 249 | if (createTime != null) val['create_time'] = createTime; | 239 | if (createTime != null) val['create_time'] = createTime; |
| 250 | if (isBot != null) val['is_bot'] = isBot; | 240 | if (isBot != null) val['is_bot'] = isBot; |
| 241 | + if (uniqueCode != null) val['unique_code'] = uniqueCode; | ||
| 242 | + if (enableAddWithUCode != null) { | ||
| 243 | + val['enable_add_with_ucode'] = enableAddWithUCode; | ||
| 244 | + } | ||
| 251 | return val; | 245 | return val; |
| 252 | } | 246 | } |
| 253 | } | 247 | } |
-
Please register or login to post a comment