Commit ab7dd1907089e92f01ae41f2cb81cc7a3e600406

Authored by 权海
1 parent 6529d91b

feat(ui):修复首页睡眠显示问题

@@ -165,11 +165,11 @@ class LocalHealthDataConvert { @@ -165,11 +165,11 @@ class LocalHealthDataConvert {
165 required List<HealthKitRawDataPoint> heartRate, 165 required List<HealthKitRawDataPoint> heartRate,
166 }) { 166 }) {
167 final daily = [ 167 final daily = [
168 - for (final day in days) _sleepResultForDay(day, sleepResults), 168 + for (final day in days) sleepResultForDay(day, sleepResults),
169 ]; 169 ];
170 final validSleep = daily.whereType<HealthRawSleepResult>().toList(); 170 final validSleep = daily.whereType<HealthRawSleepResult>().toList();
171 final previousDaily = [ 171 final previousDaily = [
172 - for (final day in previousDays) _sleepResultForDay(day, sleepResults), 172 + for (final day in previousDays) sleepResultForDay(day, sleepResults),
173 ]; 173 ];
174 final previousValidSleep = 174 final previousValidSleep =
175 previousDaily.whereType<HealthRawSleepResult>().toList(); 175 previousDaily.whereType<HealthRawSleepResult>().toList();
@@ -493,6 +493,7 @@ class LocalHealthDataConvert { @@ -493,6 +493,7 @@ class LocalHealthDataConvert {
493 required List<HealthKitRawDataPoint> sleepingHeartRate, 493 required List<HealthKitRawDataPoint> sleepingHeartRate,
494 required List<HealthKitRawDataPoint> restingHeartRate, 494 required List<HealthKitRawDataPoint> restingHeartRate,
495 required HealthRawSleepResult? sleepResult, 495 required HealthRawSleepResult? sleepResult,
  496 + SleepTrendList? sleepTrend,
496 }) { 497 }) {
497 final activitySummary = _activityDaySummary(day, activity); 498 final activitySummary = _activityDaySummary(day, activity);
498 final dayStart = unixSeconds(day); 499 final dayStart = unixSeconds(day);
@@ -524,13 +525,26 @@ class LocalHealthDataConvert { @@ -524,13 +525,26 @@ class LocalHealthDataConvert {
524 exercise: activitySummary.exerciseSeconds.round(), 525 exercise: activitySummary.exerciseSeconds.round(),
525 stand: activitySummary.standHours.round(), 526 stand: activitySummary.standHours.round(),
526 steps: 0, 527 steps: 0,
527 - sleepDuration: sleepResult == null ? 0 : sleepResult.sleepMinutes * 60,  
528 - sleepScore: sleepResult?.sleepScore.toDouble(),  
529 - sleepState: sleepResult?.sleepStateValue, 528 + sleepDuration: sleepTrend?.totalTime?.round() ??
  529 + sleepDurationSecondsFromResult(sleepResult),
  530 + sleepScore:
  531 + sleepTrend?.score?.toDouble() ?? sleepResult?.sleepScore.toDouble(),
  532 + sleepState:
  533 + sleepTrend?.sleepEvaluate?.round() ?? sleepResult?.sleepStateValue,
530 sleepTimeList: sleepTimeList, 534 sleepTimeList: sleepTimeList,
531 ); 535 );
532 } 536 }
533 537
  538 + static int sleepDurationSecondsFromResult(HealthRawSleepResult? sleepResult) {
  539 + if (sleepResult == null ||
  540 + sleepResult.sleepScore <= 0 ||
  541 + sleepResult.sleepStateValue <= 0 ||
  542 + sleepResult.sleepMinutes <= 0) {
  543 + return 0;
  544 + }
  545 + return sleepResult.sleepMinutes * 60;
  546 + }
  547 +
534 static V2HrvTrendData v2HrvTrendData( 548 static V2HrvTrendData v2HrvTrendData(
535 List<HealthRawHrvStressPoint> hrvPoints, 549 List<HealthRawHrvStressPoint> hrvPoints,
536 ) { 550 ) {
@@ -624,23 +638,6 @@ class LocalHealthDataConvert { @@ -624,23 +638,6 @@ class LocalHealthDataConvert {
624 ); 638 );
625 } 639 }
626 640
627 - static HealthRawHrvStressPoint? _previousHrvPoint({  
628 - required HealthRawHrvStressPoint? latest,  
629 - required List<HealthRawHrvStressPoint> hrvPoints,  
630 - required List<HealthRawHrvStressPoint> previousHrvPoints,  
631 - }) {  
632 - if (latest == null) return null;  
633 - return [...previousHrvPoints, ...hrvPoints]  
634 - .where((item) => item.rawEndTime < latest.rawEndTime)  
635 - .fold<HealthRawHrvStressPoint?>(  
636 - null,  
637 - (previous, item) =>  
638 - previous == null || item.rawEndTime > previous.rawEndTime  
639 - ? item  
640 - : previous,  
641 - );  
642 - }  
643 -  
644 static String? _latestHrvTip({required HealthRawHrvStressPoint? latest}) { 641 static String? _latestHrvTip({required HealthRawHrvStressPoint? latest}) {
645 if (latest == null) return null; 642 if (latest == null) return null;
646 643
@@ -1079,7 +1076,7 @@ class LocalHealthDataConvert { @@ -1079,7 +1076,7 @@ class LocalHealthDataConvert {
1079 .toList(); 1076 .toList();
1080 } 1077 }
1081 1078
1082 - static HealthRawSleepResult? _sleepResultForDay( 1079 + static HealthRawSleepResult? sleepResultForDay(
1083 DateTime day, 1080 DateTime day,
1084 List<HealthRawSleepResult> sleepResults, 1081 List<HealthRawSleepResult> sleepResults,
1085 ) { 1082 ) {
@@ -215,10 +215,23 @@ class LocalHealthDataSource implements HealthDataSource { @@ -215,10 +215,23 @@ class LocalHealthDataSource implements HealthDataSource {
215 startTime: dayStart, 215 startTime: dayStart,
216 endTime: dayEnd, 216 endTime: dayEnd,
217 ); 217 );
218 - final sleepResult = sleepResults.isEmpty ? null : sleepResults.last; 218 + final sleepResult = LocalHealthDataConvert.sleepResultForDay(
  219 + day,
  220 + sleepResults,
  221 + );
219 final sleepingHeartRate = await _querySleepHeartRate( 222 final sleepingHeartRate = await _querySleepHeartRate(
220 sleepResult == null ? const [] : [sleepResult], 223 sleepResult == null ? const [] : [sleepResult],
221 ); 224 );
  225 + final sleepStatistics = LocalHealthDataConvert.sleepStatisticsFromResults(
  226 + dateRangeType: 3,
  227 + days: [day],
  228 + previousDays: const [],
  229 + sleepResults: sleepResults,
  230 + heartRate: sleepingHeartRate,
  231 + );
  232 + final sleepTrend = sleepStatistics.sleepTrendList?.isEmpty ?? true
  233 + ? null
  234 + : sleepStatistics.sleepTrendList!.first;
222 final restingHeartRate = await coreService.queryRawDataPoints( 235 final restingHeartRate = await coreService.queryRawDataPoints(
223 dataType: HealthDataUploadType.restingHeartRate.type, 236 dataType: HealthDataUploadType.restingHeartRate.type,
224 startTime: dayStart, 237 startTime: dayStart,
@@ -232,6 +245,7 @@ class LocalHealthDataSource implements HealthDataSource { @@ -232,6 +245,7 @@ class LocalHealthDataSource implements HealthDataSource {
232 sleepingHeartRate: sleepingHeartRate, 245 sleepingHeartRate: sleepingHeartRate,
233 restingHeartRate: restingHeartRate, 246 restingHeartRate: restingHeartRate,
234 sleepResult: sleepResult, 247 sleepResult: sleepResult,
  248 + sleepTrend: sleepTrend,
235 ), 249 ),
236 ); 250 );
237 } catch (error) { 251 } catch (error) {
1 import 'package:doublefeel_flutter/core/services/health_raw_data_core_service.dart'; 1 import 'package:doublefeel_flutter/core/services/health_raw_data_core_service.dart';
2 import 'package:doublefeel_flutter/data/datasource/health/health_local_data_convert.dart'; 2 import 'package:doublefeel_flutter/data/datasource/health/health_local_data_convert.dart';
  3 +import 'package:doublefeel_flutter/data/models/health/sleep/sleep_statistics_data.dart';
3 import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart'; 4 import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart';
4 import 'package:flutter_test/flutter_test.dart'; 5 import 'package:flutter_test/flutter_test.dart';
5 6
@@ -119,6 +120,83 @@ void main() { @@ -119,6 +120,83 @@ void main() {
119 expect(statistics.avgSleepEvaluate, 2); 120 expect(statistics.avgSleepEvaluate, 2);
120 }); 121 });
121 122
  123 + test('v2 health sleep duration matches sleep statistics result duration',
  124 + () {
  125 + final day = DateTime(2026, 7, 16);
  126 + final sleepStart = DateTime(2026, 7, 15, 23);
  127 + final sleepEnd = DateTime(2026, 7, 16, 7);
  128 + final result = HealthRawSleepResult(
  129 + userId: 1,
  130 + date: LocalHealthDataConvert.unixSeconds(sleepEnd),
  131 + startDate: LocalHealthDataConvert.unixSeconds(sleepStart),
  132 + sleepScore: 85,
  133 + sleepState: 1,
  134 + inBedMinutes: 480,
  135 + awakMinutes: 80,
  136 + sleepMinutes: 400,
  137 + );
  138 + final statistics = LocalHealthDataConvert.sleepStatisticsFromResults(
  139 + dateRangeType: 3,
  140 + days: [day],
  141 + previousDays: const [],
  142 + sleepResults: [result],
  143 + heartRate: const [],
  144 + );
  145 + final selected = LocalHealthDataConvert.sleepResultForDay(day, [result]);
  146 + final healthData = LocalHealthDataConvert.v2HealthDataFromSleepResult(
  147 + day: day,
  148 + hrvPoints: const [],
  149 + activity: const [],
  150 + sleepingHeartRate: const [],
  151 + restingHeartRate: const [],
  152 + sleepResult: selected,
  153 + );
  154 +
  155 + expect(statistics.sleepTrendList?.single.totalTime, 400 * 60);
  156 + expect(healthData.sleepDuration,
  157 + statistics.sleepTrendList?.single.totalTime);
  158 + expect(
  159 + healthData.sleepDuration,
  160 + isNot(result.date - result.startDate),
  161 + );
  162 + });
  163 +
  164 + test('v2 health sleep values prefer sleep statistics trend result', () {
  165 + final day = DateTime(2026, 7, 16);
  166 + final result = HealthRawSleepResult(
  167 + userId: 1,
  168 + date: LocalHealthDataConvert.unixSeconds(
  169 + day.add(const Duration(hours: 7)),
  170 + ),
  171 + startDate: LocalHealthDataConvert.unixSeconds(
  172 + day.subtract(const Duration(hours: 1)),
  173 + ),
  174 + sleepScore: 90,
  175 + sleepState: 1,
  176 + inBedMinutes: 520,
  177 + awakMinutes: 40,
  178 + sleepMinutes: 480,
  179 + );
  180 + final healthData = LocalHealthDataConvert.v2HealthDataFromSleepResult(
  181 + day: day,
  182 + hrvPoints: const [],
  183 + activity: const [],
  184 + sleepingHeartRate: const [],
  185 + restingHeartRate: const [],
  186 + sleepResult: result,
  187 + sleepTrend: SleepTrendList(
  188 + timeKey: 20260716,
  189 + totalTime: 400 * 60,
  190 + score: 72,
  191 + sleepEvaluate: 2,
  192 + ),
  193 + );
  194 +
  195 + expect(healthData.sleepDuration, 400 * 60);
  196 + expect(healthData.sleepScore, 72);
  197 + expect(healthData.sleepState, 2);
  198 + });
  199 +
122 test('compares result bedtime extremes by sleep-end-day start offset', () { 200 test('compares result bedtime extremes by sleep-end-day start offset', () {
123 final july27 = DateTime(2026, 7, 27); 201 final july27 = DateTime(2026, 7, 27);
124 final july28 = DateTime(2026, 7, 28); 202 final july28 = DateTime(2026, 7, 28);