Showing
4 changed files
with
58 additions
and
15 deletions
| @@ -410,10 +410,7 @@ class HealthRawDataCoreService { | @@ -410,10 +410,7 @@ class HealthRawDataCoreService { | ||
| 410 | : result.realtimeStressPoints.reduce( | 410 | : result.realtimeStressPoints.reduce( |
| 411 | (a, b) => a.rawEndTime >= b.rawEndTime ? a : b, | 411 | (a, b) => a.rawEndTime >= b.rawEndTime ? a : b, |
| 412 | ); | 412 | ); |
| 413 | - final shouldSkipRealtimeStressNotification = | ||
| 414 | - latestRealtimeStressPoint?.isWorkout ?? false; | ||
| 415 | - final realtimeWindow = latestRealtimeStressPoint == null || | ||
| 416 | - shouldSkipRealtimeStressNotification | 413 | + final realtimeWindow = latestRealtimeStressPoint == null |
| 417 | ? const <HealthRawRealtimeStressPoint>[] | 414 | ? const <HealthRawRealtimeStressPoint>[] |
| 418 | : await _localStore.queryRealtimeStressPoints( | 415 | : await _localStore.queryRealtimeStressPoints( |
| 419 | userId: result.userId, | 416 | userId: result.userId, |
| @@ -108,10 +108,16 @@ class HealthRawLocalNotificationBuilder { | @@ -108,10 +108,16 @@ class HealthRawLocalNotificationBuilder { | ||
| 108 | List<HealthRawRealtimeStressPoint> realtimeWindow, | 108 | List<HealthRawRealtimeStressPoint> realtimeWindow, |
| 109 | HealthRawLocalNotificationRecord? record, | 109 | HealthRawLocalNotificationRecord? record, |
| 110 | ) { | 110 | ) { |
| 111 | - final valid = realtimeWindow | ||
| 112 | - .where((e) => e.result >= 1 && e.result <= 100) | ||
| 113 | - .toList() | 111 | + final window = [...realtimeWindow] |
| 114 | ..sort((a, b) => a.rawEndTime.compareTo(b.rawEndTime)); | 112 | ..sort((a, b) => a.rawEndTime.compareTo(b.rawEndTime)); |
| 113 | + final lastWorkoutIndex = window.lastIndexWhere( | ||
| 114 | + (point) => point.isWorkout || point.isWorkoutRecovery, | ||
| 115 | + ); | ||
| 116 | + final stressWindow = lastWorkoutIndex < 0 | ||
| 117 | + ? window | ||
| 118 | + : window.skip(lastWorkoutIndex + 1).toList(); | ||
| 119 | + final valid = | ||
| 120 | + stressWindow.where((e) => e.result >= 1 && e.result <= 100).toList(); | ||
| 115 | if (valid.length < _realtimeMinPointCount) return null; | 121 | if (valid.length < _realtimeMinPointCount) return null; |
| 116 | 122 | ||
| 117 | final latest = valid.last; | 123 | final latest = valid.last; |
| @@ -80,7 +80,6 @@ void main() { | @@ -80,7 +80,6 @@ void main() { | ||
| 80 | ); | 80 | ); |
| 81 | 81 | ||
| 82 | api.calls.clear(); | 82 | api.calls.clear(); |
| 83 | - store.realtimeNotificationWindowQueryCount = 0; | ||
| 84 | final second = await service.startCoreCaculate( | 83 | final second = await service.startCoreCaculate( |
| 85 | endTime: base + 800, | 84 | endTime: base + 800, |
| 86 | readChunkDays: 1, | 85 | readChunkDays: 1, |
| @@ -91,7 +90,6 @@ void main() { | @@ -91,7 +90,6 @@ void main() { | ||
| 91 | second.realtimeStressPoints.map((e) => e.rawEndTime), | 90 | second.realtimeStressPoints.map((e) => e.rawEndTime), |
| 92 | [base + 720], | 91 | [base + 720], |
| 93 | ); | 92 | ); |
| 94 | - expect(store.realtimeNotificationWindowQueryCount, 0); | ||
| 95 | expect( | 93 | expect( |
| 96 | api.firstCallStart(HealthDataUploadType.hrv.type), | 94 | api.firstCallStart(HealthDataUploadType.hrv.type), |
| 97 | lessThanOrEqualTo(base + 120), | 95 | lessThanOrEqualTo(base + 120), |
| @@ -871,7 +869,6 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore { | @@ -871,7 +869,6 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore { | ||
| 871 | final Map<int, List<HealthRawDailyStressPoint>> _daily = {}; | 869 | final Map<int, List<HealthRawDailyStressPoint>> _daily = {}; |
| 872 | final Map<int, List<HealthRawSleepResult>> _sleep = {}; | 870 | final Map<int, List<HealthRawSleepResult>> _sleep = {}; |
| 873 | final List<String> operationLog; | 871 | final List<String> operationLog; |
| 874 | - var realtimeNotificationWindowQueryCount = 0; | ||
| 875 | 872 | ||
| 876 | void insertDailyStress(HealthRawDailyStressPoint point) { | 873 | void insertDailyStress(HealthRawDailyStressPoint point) { |
| 877 | final byDate = <int, HealthRawDailyStressPoint>{ | 874 | final byDate = <int, HealthRawDailyStressPoint>{ |
| @@ -1042,9 +1039,6 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore { | @@ -1042,9 +1039,6 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore { | ||
| 1042 | required int endTime, | 1039 | required int endTime, |
| 1043 | }) async { | 1040 | }) async { |
| 1044 | operationLog.add('queryRealtimeStressPoints'); | 1041 | operationLog.add('queryRealtimeStressPoints'); |
| 1045 | - if (endTime - startTime <= Duration.secondsPerHour) { | ||
| 1046 | - realtimeNotificationWindowQueryCount += 1; | ||
| 1047 | - } | ||
| 1048 | return (_realtime[userId] ?? <HealthRawRealtimeStressPoint>[]) | 1042 | return (_realtime[userId] ?? <HealthRawRealtimeStressPoint>[]) |
| 1049 | .where((e) => e.rawEndTime >= startTime && e.rawEndTime <= endTime) | 1043 | .where((e) => e.rawEndTime >= startTime && e.rawEndTime <= endTime) |
| 1050 | .toList(); | 1044 | .toList(); |
| @@ -123,6 +123,50 @@ void main() { | @@ -123,6 +123,50 @@ void main() { | ||
| 123 | 123 | ||
| 124 | expect(notifications, isEmpty); | 124 | expect(notifications, isEmpty); |
| 125 | }); | 125 | }); |
| 126 | + | ||
| 127 | + test('resets realtime stress window after workout point', () { | ||
| 128 | + final base = _seconds(DateTime(2026, 1, 1, 9)); | ||
| 129 | + final notifications = builder.build( | ||
| 130 | + result: HealthRawStressCalculationResult( | ||
| 131 | + userId: 1, | ||
| 132 | + hrvStressPoints: const [], | ||
| 133 | + realtimeStressPoints: [_realtimePoint(base + 12 * 300, 70)], | ||
| 134 | + dailyStressPoints: const [], | ||
| 135 | + ), | ||
| 136 | + hasExistingHrv: false, | ||
| 137 | + realtimeWindow: [ | ||
| 138 | + for (var i = 0; i < 3; i++) _realtimePoint(base + i * 300, 70), | ||
| 139 | + _realtimePoint(base + 3 * 300, 70, isWorkout: true), | ||
| 140 | + for (var i = 4; i < 13; i++) _realtimePoint(base + i * 300, 70), | ||
| 141 | + ], | ||
| 142 | + record: const HealthRawLocalNotificationRecord(), | ||
| 143 | + ); | ||
| 144 | + | ||
| 145 | + expect(notifications, isEmpty); | ||
| 146 | + }); | ||
| 147 | + | ||
| 148 | + test('builds realtime stress notification after workout reset window refills', | ||
| 149 | + () { | ||
| 150 | + final base = _seconds(DateTime(2026, 1, 1, 9)); | ||
| 151 | + final notifications = builder.build( | ||
| 152 | + result: HealthRawStressCalculationResult( | ||
| 153 | + userId: 1, | ||
| 154 | + hrvStressPoints: const [], | ||
| 155 | + realtimeStressPoints: [_realtimePoint(base + 13 * 300, 70)], | ||
| 156 | + dailyStressPoints: const [], | ||
| 157 | + ), | ||
| 158 | + hasExistingHrv: false, | ||
| 159 | + realtimeWindow: [ | ||
| 160 | + for (var i = 0; i < 3; i++) _realtimePoint(base + i * 300, 30), | ||
| 161 | + _realtimePoint(base + 3 * 300, 30, isWorkoutRecovery: true), | ||
| 162 | + for (var i = 4; i < 14; i++) _realtimePoint(base + i * 300, 70), | ||
| 163 | + ], | ||
| 164 | + record: const HealthRawLocalNotificationRecord(), | ||
| 165 | + ); | ||
| 166 | + | ||
| 167 | + expect(notifications, hasLength(1)); | ||
| 168 | + expect(notifications.single.title, '注意压力 · 09:05-10:05'); | ||
| 169 | + }); | ||
| 126 | } | 170 | } |
| 127 | 171 | ||
| 128 | int _seconds(DateTime time) => time.millisecondsSinceEpoch ~/ 1000; | 172 | int _seconds(DateTime time) => time.millisecondsSinceEpoch ~/ 1000; |
| @@ -147,6 +191,8 @@ HealthRawRealtimeStressPoint _realtimePoint( | @@ -147,6 +191,8 @@ HealthRawRealtimeStressPoint _realtimePoint( | ||
| 147 | int rawEndTime, | 191 | int rawEndTime, |
| 148 | double result, { | 192 | double result, { |
| 149 | bool isSleepLikely = false, | 193 | bool isSleepLikely = false, |
| 194 | + bool isWorkout = false, | ||
| 195 | + bool isWorkoutRecovery = false, | ||
| 150 | }) { | 196 | }) { |
| 151 | return HealthRawRealtimeStressPoint( | 197 | return HealthRawRealtimeStressPoint( |
| 152 | userId: 1, | 198 | userId: 1, |
| @@ -157,8 +203,8 @@ HealthRawRealtimeStressPoint _realtimePoint( | @@ -157,8 +203,8 @@ HealthRawRealtimeStressPoint _realtimePoint( | ||
| 157 | sourceEndTime: rawEndTime, | 203 | sourceEndTime: rawEndTime, |
| 158 | flags: HealthRawPointFlags( | 204 | flags: HealthRawPointFlags( |
| 159 | isSleepLikely: isSleepLikely, | 205 | isSleepLikely: isSleepLikely, |
| 160 | - isWorkout: false, | ||
| 161 | - isWorkoutRecovery: false, | 206 | + isWorkout: isWorkout, |
| 207 | + isWorkoutRecovery: isWorkoutRecovery, | ||
| 162 | isSuspectedActivity: false, | 208 | isSuspectedActivity: false, |
| 163 | ), | 209 | ), |
| 164 | ); | 210 | ); |
-
mentioned in commit 570248cd
-
Please register or login to post a comment