Commit 335cf3f5c04ae84e973bba0f17fdf476e94db479

Authored by 权海
1 parent 41f680fe

feat(ui):修复睡眠平均评分、themepreview页面错误

@@ -39,7 +39,6 @@ class CustomWatchThemePreviewView @@ -39,7 +39,6 @@ class CustomWatchThemePreviewView
39 return WatchThemeHeader( 39 return WatchThemeHeader(
40 title: controller.themeItem.title, 40 title: controller.themeItem.title,
41 themeImageUrl: controller.themeItem.energeticImage, 41 themeImageUrl: controller.themeItem.energeticImage,
42 - errorDesc: null,  
43 ); 42 );
44 }), 43 }),
45 const SizedBox(height: 26), 44 const SizedBox(height: 26),
@@ -26,7 +26,6 @@ class WatchThemePreviewView extends GetView<WatchThemePreviewController> { @@ -26,7 +26,6 @@ class WatchThemePreviewView extends GetView<WatchThemePreviewController> {
26 Obx(() { 26 Obx(() {
27 return WatchThemeHeader( 27 return WatchThemeHeader(
28 themeImageUrl: controller.themeItem.energeticImage, 28 themeImageUrl: controller.themeItem.energeticImage,
29 - errorDesc: null,  
30 ); 29 );
31 }), 30 }),
32 const SizedBox(height: 26), 31 const SizedBox(height: 26),
1 -import 'package:doublefeel_flutter/core/config/app_environment_config.dart';  
2 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
3 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; 2 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
4 -import 'package:get/get.dart';  
5 3
6 import 'watch_face_preview.dart'; 4 import 'watch_face_preview.dart';
7 import 'watch_theme_colors.dart'; 5 import 'watch_theme_colors.dart';
@@ -10,19 +8,15 @@ class WatchThemeHeader extends StatelessWidget { @@ -10,19 +8,15 @@ class WatchThemeHeader extends StatelessWidget {
10 const WatchThemeHeader({ 8 const WatchThemeHeader({
11 super.key, 9 super.key,
12 this.title, 10 this.title,
13 - this.errorDesc,  
14 this.themeImageUrl, 11 this.themeImageUrl,
15 }); 12 });
16 13
17 14
18 final String? title; 15 final String? title;
19 - final String? errorDesc;  
20 final String? themeImageUrl; 16 final String? themeImageUrl;
21 17
22 @override 18 @override
23 Widget build(BuildContext context) { 19 Widget build(BuildContext context) {
24 - final environmentConfig = Get.find<AppEnvironmentConfig>();  
25 -  
26 return Column( 20 return Column(
27 children: [ 21 children: [
28 SizedBox(height: 12), 22 SizedBox(height: 12),
@@ -42,18 +36,6 @@ class WatchThemeHeader extends StatelessWidget { @@ -42,18 +36,6 @@ class WatchThemeHeader extends StatelessWidget {
42 height: 1.25, 36 height: 1.25,
43 ), 37 ),
44 ), 38 ),
45 - if (environmentConfig.isDebug && errorDesc != null) ...[  
46 - SizedBox(height: 8),  
47 - Text(  
48 - errorDesc!,  
49 - style: TextStyle(  
50 - color: WatchThemeColors.pro,  
51 - fontSize: 14,  
52 - fontWeight: FontWeight.w400,  
53 - ),  
54 - maxLines: 2,  
55 - ),  
56 - ],  
57 ], 39 ],
58 ); 40 );
59 } 41 }
@@ -90,8 +90,8 @@ class LocalHealthDataConvert { @@ -90,8 +90,8 @@ class LocalHealthDataConvert {
90 avgSleepScore: _averageOrNull( 90 avgSleepScore: _averageOrNull(
91 validSleep.map((e) => e.score).whereType<num>().toList(), 91 validSleep.map((e) => e.score).whereType<num>().toList(),
92 ), 92 ),
93 - avgSleepEvaluate: _averageOrNull(  
94 - validSleep.map((e) => e.state?.value).whereType<num>().toList(), 93 + avgSleepEvaluate: _sleepEvaluateFromAverageScore(
  94 + validSleep.map((e) => e.score).whereType<num>().toList(),
95 ), 95 ),
96 qoqAvgSleepDuration: _averageOrNull( 96 qoqAvgSleepDuration: _averageOrNull(
97 previousValidSleep.map((e) => e.durationSeconds).toList(), 97 previousValidSleep.map((e) => e.durationSeconds).toList(),
@@ -99,8 +99,8 @@ class LocalHealthDataConvert { @@ -99,8 +99,8 @@ class LocalHealthDataConvert {
99 qoqSleepScore: _averageOrNull( 99 qoqSleepScore: _averageOrNull(
100 previousValidSleep.map((e) => e.score).whereType<num>().toList(), 100 previousValidSleep.map((e) => e.score).whereType<num>().toList(),
101 ), 101 ),
102 - qoqSleepEvaluate: _averageOrNull(  
103 - previousValidSleep.map((e) => e.state?.value).whereType<num>().toList(), 102 + qoqSleepEvaluate: _sleepEvaluateFromAverageScore(
  103 + previousValidSleep.map((e) => e.score).whereType<num>().toList(),
104 ), 104 ),
105 sleepTrendList: [ 105 sleepTrendList: [
106 for (final item in daily) 106 for (final item in daily)
@@ -176,8 +176,8 @@ class LocalHealthDataConvert { @@ -176,8 +176,8 @@ class LocalHealthDataConvert {
176 avgSleepScore: _averageOrNull( 176 avgSleepScore: _averageOrNull(
177 validSleep.map((e) => e.sleepScore).toList(), 177 validSleep.map((e) => e.sleepScore).toList(),
178 ), 178 ),
179 - avgSleepEvaluate: _averageOrNull(  
180 - validSleep.map((e) => e.sleepStateValue).whereType<num>().toList(), 179 + avgSleepEvaluate: _sleepEvaluateFromAverageScore(
  180 + validSleep.map((e) => e.sleepScore).toList(),
181 ), 181 ),
182 qoqAvgSleepDuration: _averageOrNull( 182 qoqAvgSleepDuration: _averageOrNull(
183 previousValidSleep.map((e) => e.sleepMinutes * 60).toList(), 183 previousValidSleep.map((e) => e.sleepMinutes * 60).toList(),
@@ -185,11 +185,8 @@ class LocalHealthDataConvert { @@ -185,11 +185,8 @@ class LocalHealthDataConvert {
185 qoqSleepScore: _averageOrNull( 185 qoqSleepScore: _averageOrNull(
186 previousValidSleep.map((e) => e.sleepScore).toList(), 186 previousValidSleep.map((e) => e.sleepScore).toList(),
187 ), 187 ),
188 - qoqSleepEvaluate: _averageOrNull(  
189 - previousValidSleep  
190 - .map((e) => e.sleepStateValue)  
191 - .whereType<num>()  
192 - .toList(), 188 + qoqSleepEvaluate: _sleepEvaluateFromAverageScore(
  189 + previousValidSleep.map((e) => e.sleepScore).toList(),
193 ), 190 ),
194 sleepTrendList: [ 191 sleepTrendList: [
195 for (var index = 0; index < days.length; index++) 192 for (var index = 0; index < days.length; index++)
@@ -993,6 +990,12 @@ class LocalHealthDataConvert { @@ -993,6 +990,12 @@ class LocalHealthDataConvert {
993 return _sum(values) / values.length; 990 return _sum(values) / values.length;
994 } 991 }
995 992
  993 + static int? _sleepEvaluateFromAverageScore(List<num> scores) {
  994 + final averageScore = _averageOrNull(scores);
  995 + if (averageScore == null) return null;
  996 + return HealthSleepCalculator.sleepState(averageScore.round())?.value;
  997 + }
  998 +
996 static num? _maxOrNull(List<num> values) { 999 static num? _maxOrNull(List<num> values) {
997 if (values.isEmpty) return null; 1000 if (values.isEmpty) return null;
998 return values.reduce(math.max); 1001 return values.reduce(math.max);
  1 +import 'package:doublefeel_flutter/core/services/health_sleep_calculator.dart';
  2 +import 'package:flutter_test/flutter_test.dart';
  3 +
  4 +void main() {
  5 + group('HealthSleepCalculator sleep state', () {
  6 + test('uses 60 and 85 as score boundaries', () {
  7 + expect(HealthSleepCalculator.sleepState(0), isNull);
  8 + expect(HealthSleepCalculator.sleepState(59), HealthSleepState.poor);
  9 + expect(HealthSleepCalculator.sleepState(60), HealthSleepState.good);
  10 + expect(HealthSleepCalculator.sleepState(84), HealthSleepState.good);
  11 + expect(HealthSleepCalculator.sleepState(85), HealthSleepState.great);
  12 + });
  13 + });
  14 +}
@@ -82,6 +82,43 @@ void main() { @@ -82,6 +82,43 @@ void main() {
82 expect(statistics.avgSleepDuration, isNull); 82 expect(statistics.avgSleepDuration, isNull);
83 }); 83 });
84 84
  85 + test('maps average sleep score to average sleep evaluate', () {
  86 + final firstDay = DateTime(2026, 7, 14);
  87 + final secondDay = DateTime(2026, 7, 15);
  88 + final thirdDay = DateTime(2026, 7, 16);
  89 +
  90 + HealthRawSleepResult result(DateTime day, int score, int state) {
  91 + final endTime = LocalHealthDataConvert.unixSeconds(
  92 + day.add(const Duration(hours: 7)),
  93 + );
  94 + return HealthRawSleepResult(
  95 + userId: 1,
  96 + date: endTime,
  97 + startDate: endTime - const Duration(hours: 7).inSeconds,
  98 + sleepScore: score,
  99 + sleepState: state,
  100 + inBedMinutes: 430,
  101 + awakMinutes: 20,
  102 + sleepMinutes: 400,
  103 + );
  104 + }
  105 +
  106 + final statistics = LocalHealthDataConvert.sleepStatisticsFromResults(
  107 + dateRangeType: 0,
  108 + days: [firstDay, secondDay, thirdDay],
  109 + previousDays: const [],
  110 + sleepResults: [
  111 + result(firstDay, 50, 3),
  112 + result(secondDay, 50, 3),
  113 + result(thirdDay, 90, 1),
  114 + ],
  115 + heartRate: const [],
  116 + );
  117 +
  118 + expect(statistics.avgSleepScore, closeTo(63.333, 0.001));
  119 + expect(statistics.avgSleepEvaluate, 2);
  120 + });
  121 +
85 test('sleep duration excludes awake intervals inside sleep range', () { 122 test('sleep duration excludes awake intervals inside sleep range', () {
86 final day = DateTime(2026, 7, 16); 123 final day = DateTime(2026, 7, 16);
87 final sleepStart = DateTime(2026, 7, 15, 23, 56); 124 final sleepStart = DateTime(2026, 7, 15, 23, 56);