Commit 3cbf45f89f7e34b54afdedd7a514546030f8c49a

Authored by 权海
1 parent 206eb9bf

feat(ui):实时压力通知由客户端通知服务端发送

... ... @@ -62,6 +62,7 @@ void registerUserSessionDeps() {
HealthRawDataCoreService(
environmentConfig: Get.find<AppEnvironmentConfig>(),
userIdProvider: () => userPrefs.preferences.value.meUserInfo?.id ?? 0,
serverHealthApi: HealthApi(dioClient),
),
permanent: true,
);
... ...
... ... @@ -219,6 +219,25 @@ class HealthApi {
);
}
Future<AppResult<void>> pushRealtimeStressNotification({
required int latestDataTime,
required int currentState,
int isHrvChange = 0,
}) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.v2RealtimeStressPush,
data: {
'latest_data_time': latestDataTime,
'current_state': currentState,
'is_hrv_change': isHrvChange,
},
);
},
);
}
Future<AppResult<void>> uploadPulseTypeResult(PulseType pulseType) {
return safeCall(
call: () async {
... ...
... ... @@ -37,6 +37,8 @@ abstract final class ApiPaths {
static const v2HrvTrend = '/client/doublefeel/health/v2/hrv_trend/';
static const v2RealtimeStress =
'/client/doublefeel/health/v2/realtime_stress/';
static const v2RealtimeStressPush =
'/client/doublefeel/health/v2/realtime_stress/push/';
static const v2ActivityTarget =
'/client/doublefeel/health/v2/activity_target/';
static const weatherUploadedData =
... ...
... ... @@ -14,6 +14,8 @@ import '../../pigeon/health_kit_api.g.dart';
import '../../pigeon/health_kit_raw_data_api.g.dart';
import '../config/app_environment_config.dart';
import '../logging/app_logger.dart';
import '../network/api/health_api.dart';
import '../result/app_result.dart';
import 'health_raw_local_notification.dart';
import 'health_raw_stress_calculator.dart';
import 'health_sleep_calculator.dart';
... ... @@ -30,6 +32,7 @@ class HealthRawDataCoreService {
int Function()? userIdProvider,
bool uploadResultsAfterCalculation = true,
HealthRawLocalNotificationDispatcher? localNotificationDispatcher,
HealthApi? serverHealthApi,
}) : _healthApi = healthApi ?? HealthKitHostApi(),
_rawDataApi = rawDataApi ?? HealthKitRawDataHostApi(),
_localStore = localStore ?? HealthRawStressLocalStore(),
... ... @@ -37,7 +40,8 @@ class HealthRawDataCoreService {
_userIdProvider = userIdProvider,
_uploadResultsAfterCalculation = uploadResultsAfterCalculation,
_localNotificationDispatcher = localNotificationDispatcher ??
HealthRawLocalNotificationDispatcher();
HealthRawLocalNotificationDispatcher(),
_serverHealthApi = serverHealthApi;
final HealthKitHostApi _healthApi;
final HealthKitRawDataHostApi _rawDataApi;
... ... @@ -46,6 +50,7 @@ class HealthRawDataCoreService {
final int Function()? _userIdProvider;
final bool _uploadResultsAfterCalculation;
final HealthRawLocalNotificationDispatcher _localNotificationDispatcher;
final HealthApi? _serverHealthApi;
final StreamController<HealthRawDataUpdatedEvent>
_healthDataUpdatedController =
StreamController<HealthRawDataUpdatedEvent>.broadcast();
... ... @@ -424,15 +429,60 @@ class HealthRawDataCoreService {
record: record,
);
if (notifications.isEmpty) return;
await _localNotificationDispatcher.sendAll(
final sentNotifications = await _localNotificationDispatcher.sendAll(
userId: result.userId,
notifications: notifications,
);
_scheduleRealtimeStressServerPush(sentNotifications);
} catch (error, stackTrace) {
_logError('send local health notifications failed', error, stackTrace);
}
}
void _scheduleRealtimeStressServerPush(
Iterable<HealthRawLocalNotification> sentNotifications,
) {
final realtimeNotifications = sentNotifications.where(
(notification) =>
notification.recordType ==
HealthRawLocalNotificationRecordType.realtimeStress &&
notification.currentState != null,
);
for (final notification in realtimeNotifications) {
unawaited(
_pushRealtimeStressServerNotification(notification),
);
}
}
Future<void> _pushRealtimeStressServerNotification(
HealthRawLocalNotification notification,
) async {
final serverHealthApi = _serverHealthApi;
if (serverHealthApi == null) {
return;
}
try {
final result = await serverHealthApi.pushRealtimeStressNotification(
latestDataTime: notification.recordTime,
currentState: notification.currentState!,
);
if (result is AppFailure) {
_logError(
'push realtime stress server notification failed',
result.error,
StackTrace.current,
);
}
} catch (error, stackTrace) {
_logError(
'push realtime stress server notification failed',
error,
stackTrace,
);
}
}
Future<bool> _hasHealthReadAuthorization() async {
final authorization = await _healthApi.checkHealthAppAuthorization();
return authorization.status == 1;
... ...
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:path_provider/path_provider.dart';
... ... @@ -19,6 +18,7 @@ class HealthRawLocalNotification {
required this.link,
required this.recordType,
required this.recordTime,
this.currentState,
});
final String title;
... ... @@ -26,6 +26,7 @@ class HealthRawLocalNotification {
final String link;
final HealthRawLocalNotificationRecordType recordType;
final int recordTime;
final int? currentState;
}
enum HealthRawLocalNotificationRecordType {
... ... @@ -121,10 +122,7 @@ class HealthRawLocalNotificationBuilder {
}
}
final windowStart = math.max(
valid.first.rawEndTime,
latest.rawEndTime - _realtimeWindowSeconds + 1,
);
final windowStart = latest.rawEndTime - _realtimeWindowSeconds;
final average =
valid.map((e) => e.result).reduce((a, b) => a + b) / valid.length;
final state = healthRawRealtimeStressState(average);
... ... @@ -138,6 +136,7 @@ class HealthRawLocalNotificationBuilder {
link: healthRawTodayLink,
recordType: HealthRawLocalNotificationRecordType.realtimeStress,
recordTime: latest.rawEndTime,
currentState: state.value,
);
}
... ... @@ -214,11 +213,12 @@ class HealthRawLocalNotificationDispatcher {
final PlatformHostApi _platformApi;
final HealthRawLocalNotificationRecordStore _recordStore;
Future<void> sendAll({
Future<List<HealthRawLocalNotification>> sendAll({
required int userId,
required Iterable<HealthRawLocalNotification> notifications,
}) async {
var record = await _recordStore.read(userId);
final sentNotifications = <HealthRawLocalNotification>[];
for (final notification in notifications) {
final sent = await _platformApi.sendLocalNotification(
notification.title,
... ... @@ -226,9 +226,11 @@ class HealthRawLocalNotificationDispatcher {
notification.link,
);
if (!sent) continue;
sentNotifications.add(notification);
record = record.withNotification(notification);
await _recordStore.write(userId, record);
}
return sentNotifications;
}
Future<HealthRawLocalNotificationRecord> readRecord(int userId) {
... ...
... ... @@ -95,9 +95,11 @@ void main() {
);
expect(notifications, hasLength(1));
expect(notifications.single.title, '注意压力 · 09:00-09:45');
expect(notifications.single.title, '注意压力 · 08:45-09:45');
expect(notifications.single.content, '你过去60分钟压力偏高,建议适当放松,并注意休息与恢复。');
expect(notifications.single.link, healthRawTodayLink);
expect(notifications.single.currentState,
HealthRawStressState.attention.value);
});
test('skips realtime stress notification during likely sleep', () {
... ...