|
...
|
...
|
@@ -444,9 +444,30 @@ class AppleHealthRawDataCoreService { |
|
|
|
'latest_sleep': _latestSleepResultPayload(result.sleepResults),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (result.hrvStressPoints.isEmpty &&
|
|
|
|
result.realtimeStressPoints.isEmpty &&
|
|
|
|
result.sleepResults.isEmpty) {
|
|
|
|
final record = await _readLocalNotificationRecordSafely(result.userId);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationRecordRead',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'record': _localNotificationRecordPayload(record),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
final candidateData = await _resolveLocalNotificationCandidates(
|
|
|
|
result: result,
|
|
|
|
record: record,
|
|
|
|
);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationLatestDataTimeChecked',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'record': _localNotificationRecordPayload(record),
|
|
|
|
'latest_data_time': candidateData.latestDataTimePayload,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
final candidateResult = candidateData.result;
|
|
|
|
if (candidateResult.hrvStressPoints.isEmpty &&
|
|
|
|
candidateResult.realtimeStressPoints.isEmpty &&
|
|
|
|
candidateResult.sleepResults.isEmpty) {
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationNotBuilt',
|
|
...
|
...
|
@@ -457,12 +478,13 @@ class AppleHealthRawDataCoreService { |
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final latestRealtimeStressPoint = result.realtimeStressPoints.isEmpty
|
|
|
|
? null
|
|
|
|
: result.realtimeStressPoints.reduce(
|
|
|
|
(a, b) => a.rawEndTime >= b.rawEndTime ? a : b,
|
|
|
|
);
|
|
|
|
var notificationResult = result;
|
|
|
|
final latestRealtimeStressPoint =
|
|
|
|
candidateResult.realtimeStressPoints.isEmpty
|
|
|
|
? null
|
|
|
|
: candidateResult.realtimeStressPoints.reduce(
|
|
|
|
(a, b) => a.rawEndTime >= b.rawEndTime ? a : b,
|
|
|
|
);
|
|
|
|
var notificationResult = candidateResult;
|
|
|
|
var realtimeWindow = const <HealthRawRealtimeStressPoint>[];
|
|
|
|
if (latestRealtimeStressPoint != null) {
|
|
|
|
final shouldResetRealtimeStressPushTime =
|
|
...
|
...
|
@@ -479,36 +501,28 @@ class AppleHealthRawDataCoreService { |
|
|
|
},
|
|
|
|
);
|
|
|
|
await _recordRealtimeStressTimeSafely(
|
|
|
|
userId: result.userId,
|
|
|
|
userId: candidateResult.userId,
|
|
|
|
recordTime: latestRealtimeStressPoint.rawEndTime,
|
|
|
|
);
|
|
|
|
notificationResult = result.copyWith(
|
|
|
|
notificationResult = candidateResult.copyWith(
|
|
|
|
realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
realtimeWindow = await _queryRealtimeStressNotificationWindowSafely(
|
|
|
|
userId: result.userId,
|
|
|
|
userId: candidateResult.userId,
|
|
|
|
latestRawEndTime: latestRealtimeStressPoint.rawEndTime,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final record = await _readLocalNotificationRecordSafely(result.userId);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationRecordRead',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'record': _localNotificationRecordPayload(record),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationBuildDecisions',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'user_id': candidateResult.userId,
|
|
|
|
'decisions': _localNotificationDecisionPayloads(
|
|
|
|
result: notificationResult,
|
|
|
|
hasExistingHrv: hasExistingHrv,
|
|
|
|
hasExistingSleep: hasExistingSleep,
|
|
|
|
hasExistingHrv: candidateData.hasExistingHrvDataTime,
|
|
|
|
hasExistingSleep: candidateData.hasExistingSleepDataTime,
|
|
|
|
realtimeWindow: realtimeWindow,
|
|
|
|
record: record,
|
|
|
|
),
|
|
...
|
...
|
@@ -516,15 +530,15 @@ class AppleHealthRawDataCoreService { |
|
|
|
);
|
|
|
|
final notifications = HealthRawLocalNotificationBuilder(l10n).build(
|
|
|
|
result: notificationResult,
|
|
|
|
hasExistingHrv: hasExistingHrv,
|
|
|
|
hasExistingSleep: hasExistingSleep,
|
|
|
|
hasExistingHrv: candidateData.hasExistingHrvDataTime,
|
|
|
|
hasExistingSleep: candidateData.hasExistingSleepDataTime,
|
|
|
|
realtimeWindow: realtimeWindow,
|
|
|
|
record: record,
|
|
|
|
);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationBuildFinished',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'user_id': candidateResult.userId,
|
|
|
|
'notification_count': notifications.length,
|
|
|
|
'notifications': notifications.map(_localNotificationPayload).toList(),
|
|
|
|
},
|
|
...
|
...
|
@@ -534,7 +548,7 @@ class AppleHealthRawDataCoreService { |
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationNotSent',
|
|
|
|
'reason': 'no_built_notifications',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'user_id': candidateResult.userId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
return;
|
|
...
|
...
|
@@ -545,18 +559,18 @@ class AppleHealthRawDataCoreService { |
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationSendStart',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'user_id': candidateResult.userId,
|
|
|
|
'notification': _localNotificationPayload(notification),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
final sent = await _sendLocalNotificationSafely(
|
|
|
|
userId: result.userId,
|
|
|
|
userId: candidateResult.userId,
|
|
|
|
notification: notification,
|
|
|
|
);
|
|
|
|
await _saveLocalNotificationDebugEvent(
|
|
|
|
<String, Object?>{
|
|
|
|
'event': 'flutterNotificationSendFinished',
|
|
|
|
'user_id': result.userId,
|
|
|
|
'user_id': candidateResult.userId,
|
|
|
|
'sent': sent,
|
|
|
|
'reason': sent ? null : 'native_or_dispatcher_returned_false',
|
|
|
|
'notification': _localNotificationPayload(notification),
|
|
...
|
...
|
@@ -569,6 +583,93 @@ class AppleHealthRawDataCoreService { |
|
|
|
_scheduleRealtimeStressServerPush(sentNotifications);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<_HealthRawLocalNotificationCandidateData>
|
|
|
|
_resolveLocalNotificationCandidates({
|
|
|
|
required HealthRawStressCalculationResult result,
|
|
|
|
required HealthRawLocalNotificationRecord record,
|
|
|
|
}) async {
|
|
|
|
final userId = result.userId;
|
|
|
|
final latestHrvRawEndTime = await _localStore.latestHrvRawEndTime(userId);
|
|
|
|
final latestRealtimeRawEndTime =
|
|
|
|
await _localStore.latestRealtimeRawEndTime(userId);
|
|
|
|
final latestSleepDataTime = await _localStore.latestSleepResultTime(userId);
|
|
|
|
|
|
|
|
var hrvPoints = const <HealthRawHrvStressPoint>[];
|
|
|
|
var realtimePoints = const <HealthRawRealtimeStressPoint>[];
|
|
|
|
var sleepResults = const <HealthRawSleepResult>[];
|
|
|
|
int? nextHrvDataTime;
|
|
|
|
int? nextRealtimeStressDataTime;
|
|
|
|
int? nextSleepDataTime;
|
|
|
|
|
|
|
|
final hrvDataTime = record.latestHrvDataTime;
|
|
|
|
if (latestHrvRawEndTime != null) {
|
|
|
|
if (hrvDataTime == null) {
|
|
|
|
nextHrvDataTime = latestHrvRawEndTime;
|
|
|
|
} else if (hrvDataTime < latestHrvRawEndTime) {
|
|
|
|
hrvPoints = await _localStore.queryHrvStressPoints(
|
|
|
|
userId: userId,
|
|
|
|
startTime: hrvDataTime + 1,
|
|
|
|
endTime: latestHrvRawEndTime,
|
|
|
|
);
|
|
|
|
nextHrvDataTime = latestHrvRawEndTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final realtimeDataTime = record.latestRealtimeStressDataTime;
|
|
|
|
if (latestRealtimeRawEndTime != null) {
|
|
|
|
if (realtimeDataTime == null) {
|
|
|
|
nextRealtimeStressDataTime = latestRealtimeRawEndTime;
|
|
|
|
} else if (realtimeDataTime < latestRealtimeRawEndTime) {
|
|
|
|
realtimePoints = await _localStore.queryRealtimeStressPoints(
|
|
|
|
userId: userId,
|
|
|
|
startTime: realtimeDataTime + 1,
|
|
|
|
endTime: latestRealtimeRawEndTime,
|
|
|
|
);
|
|
|
|
nextRealtimeStressDataTime = latestRealtimeRawEndTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final sleepDataTime = record.latestSleepDataTime;
|
|
|
|
if (latestSleepDataTime != null) {
|
|
|
|
if (sleepDataTime == null) {
|
|
|
|
nextSleepDataTime = latestSleepDataTime;
|
|
|
|
} else if (sleepDataTime < latestSleepDataTime) {
|
|
|
|
sleepResults = await _localStore.querySleepResults(
|
|
|
|
userId: userId,
|
|
|
|
startTime: sleepDataTime + 1,
|
|
|
|
endTime: latestSleepDataTime,
|
|
|
|
);
|
|
|
|
nextSleepDataTime = latestSleepDataTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await _updateProcessedDataTimesSafely(
|
|
|
|
userId: userId,
|
|
|
|
latestHrvDataTime: nextHrvDataTime,
|
|
|
|
latestRealtimeStressDataTime: nextRealtimeStressDataTime,
|
|
|
|
latestSleepDataTime: nextSleepDataTime,
|
|
|
|
);
|
|
|
|
|
|
|
|
return _HealthRawLocalNotificationCandidateData(
|
|
|
|
result: HealthRawStressCalculationResult(
|
|
|
|
userId: userId,
|
|
|
|
hrvStressPoints: hrvPoints,
|
|
|
|
realtimeStressPoints: realtimePoints,
|
|
|
|
dailyStressPoints: result.dailyStressPoints,
|
|
|
|
sleepResults: sleepResults,
|
|
|
|
),
|
|
|
|
hasExistingHrvDataTime: hrvDataTime != null,
|
|
|
|
hasExistingRealtimeStressDataTime: realtimeDataTime != null,
|
|
|
|
hasExistingSleepDataTime: sleepDataTime != null,
|
|
|
|
currentHrvDataTime: latestHrvRawEndTime,
|
|
|
|
currentRealtimeStressDataTime: latestRealtimeRawEndTime,
|
|
|
|
currentSleepDataTime: latestSleepDataTime,
|
|
|
|
nextHrvDataTime: nextHrvDataTime,
|
|
|
|
nextRealtimeStressDataTime: nextRealtimeStressDataTime,
|
|
|
|
nextSleepDataTime: nextSleepDataTime,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<HealthRawLocalNotificationRecord> _readLocalNotificationRecordSafely(
|
|
|
|
int userId,
|
|
|
|
) async {
|
|
...
|
...
|
@@ -580,6 +681,28 @@ class AppleHealthRawDataCoreService { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _updateProcessedDataTimesSafely({
|
|
|
|
required int userId,
|
|
|
|
int? latestHrvDataTime,
|
|
|
|
int? latestRealtimeStressDataTime,
|
|
|
|
int? latestSleepDataTime,
|
|
|
|
}) async {
|
|
|
|
try {
|
|
|
|
await _localNotificationDispatcher.updateProcessedDataTimes(
|
|
|
|
userId: userId,
|
|
|
|
latestHrvDataTime: latestHrvDataTime,
|
|
|
|
latestRealtimeStressDataTime: latestRealtimeStressDataTime,
|
|
|
|
latestSleepDataTime: latestSleepDataTime,
|
|
|
|
);
|
|
|
|
} catch (error, stackTrace) {
|
|
|
|
_logError(
|
|
|
|
'update local notification processed data time failed',
|
|
|
|
error,
|
|
|
|
stackTrace,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _recordRealtimeStressTimeSafely({
|
|
|
|
required int userId,
|
|
|
|
required int recordTime,
|
|
...
|
...
|
@@ -864,6 +987,9 @@ class AppleHealthRawDataCoreService { |
|
|
|
'last_sleep_time': record.lastSleepTime,
|
|
|
|
'last_hrv_time': record.lastHrvTime,
|
|
|
|
'last_realtime_stress_time': record.lastRealtimeStressTime,
|
|
|
|
'latest_hrv_data_time': record.latestHrvDataTime,
|
|
|
|
'latest_realtime_stress_data_time': record.latestRealtimeStressDataTime,
|
|
|
|
'latest_sleep_data_time': record.latestSleepDataTime,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -1675,6 +1801,50 @@ class HealthRawStressDbRows { |
|
|
|
final List<Map<String, Object?>> sleepRows;
|
|
|
|
}
|
|
|
|
|
|
|
|
class _HealthRawLocalNotificationCandidateData {
|
|
|
|
const _HealthRawLocalNotificationCandidateData({
|
|
|
|
required this.result,
|
|
|
|
required this.hasExistingHrvDataTime,
|
|
|
|
required this.hasExistingRealtimeStressDataTime,
|
|
|
|
required this.hasExistingSleepDataTime,
|
|
|
|
required this.currentHrvDataTime,
|
|
|
|
required this.currentRealtimeStressDataTime,
|
|
|
|
required this.currentSleepDataTime,
|
|
|
|
required this.nextHrvDataTime,
|
|
|
|
required this.nextRealtimeStressDataTime,
|
|
|
|
required this.nextSleepDataTime,
|
|
|
|
});
|
|
|
|
|
|
|
|
final HealthRawStressCalculationResult result;
|
|
|
|
final bool hasExistingHrvDataTime;
|
|
|
|
final bool hasExistingRealtimeStressDataTime;
|
|
|
|
final bool hasExistingSleepDataTime;
|
|
|
|
final int? currentHrvDataTime;
|
|
|
|
final int? currentRealtimeStressDataTime;
|
|
|
|
final int? currentSleepDataTime;
|
|
|
|
final int? nextHrvDataTime;
|
|
|
|
final int? nextRealtimeStressDataTime;
|
|
|
|
final int? nextSleepDataTime;
|
|
|
|
|
|
|
|
Map<String, Object?> get latestDataTimePayload {
|
|
|
|
return <String, Object?>{
|
|
|
|
'has_existing_hrv_data_time': hasExistingHrvDataTime,
|
|
|
|
'has_existing_realtime_stress_data_time':
|
|
|
|
hasExistingRealtimeStressDataTime,
|
|
|
|
'has_existing_sleep_data_time': hasExistingSleepDataTime,
|
|
|
|
'current_hrv_data_time': currentHrvDataTime,
|
|
|
|
'current_realtime_stress_data_time': currentRealtimeStressDataTime,
|
|
|
|
'current_sleep_data_time': currentSleepDataTime,
|
|
|
|
'next_hrv_data_time': nextHrvDataTime,
|
|
|
|
'next_realtime_stress_data_time': nextRealtimeStressDataTime,
|
|
|
|
'next_sleep_data_time': nextSleepDataTime,
|
|
|
|
'candidate_hrv_count': result.hrvStressPoints.length,
|
|
|
|
'candidate_realtime_count': result.realtimeStressPoints.length,
|
|
|
|
'candidate_sleep_count': result.sleepResults.length,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
double _integerDouble(num value) => value.toInt().toDouble();
|
|
|
|
|
|
|
|
class HealthRawStressLocalStore {
|
...
|
...
|
|