Commit 3e164451656962ffecdae8c3c019e4328c096c69

Authored by 权海
1 parent 71aab30e

feat(ui):华为目标值获取数据源修改为原生,并增加上传

xx

feat(ui):修复ohos目标写法

xx
... ... @@ -533,9 +533,12 @@ final class WatchHealthObserverUploader {
}
private func uploadActivityTarget(_ summary: HKActivitySummary) async throws {
let exerciseGoal = Int(summary.appleExerciseTimeGoal.doubleValue(for: .second()))
var body: [String: Any] = [
"move": Int(summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie())),
"stand": Int(summary.appleStandHoursGoal.doubleValue(for: .count())),
"exercise": exerciseGoal,
"exercise_time_goal": exerciseGoal,
"active_energy_burned": summary.activeEnergyBurned.doubleValue(for: .kilocalorie()),
"active_energy_burned_goal": summary.activeEnergyBurnedGoal.doubleValue(for: .kilocalorie()),
"apple_exercise_time": summary.appleExerciseTime.doubleValue(for: .second()),
... ... @@ -546,7 +549,6 @@ final class WatchHealthObserverUploader {
body["activity_move_mode"] = summary.activityMoveMode.rawValue
body["apple_move_time"] = summary.appleMoveTime.doubleValue(for: .second())
body["apple_move_time_goal"] = summary.appleMoveTimeGoal.doubleValue(for: .second())
body["exercise_time_goal"] = summary.exerciseTimeGoal?.doubleValue(for: .second())
body["stand_hours_goal"] = summary.standHoursGoal?.doubleValue(for: .count())
_ = try await healthService.uploadActivityTarget(data: body)
}
... ...
... ... @@ -422,6 +422,19 @@ class HealthApi {
);
}
Future<AppResult<void>> uploadV2ActivityTarget(
V2ActivityTarget target,
) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.v2ActivityTarget,
data: target.toJson(),
);
},
);
}
Future<AppResult<V2HealthEverUploaded>> getHealthDataEverUploaded() {
return safeCall(
call: () async {
... ...
import 'dart:async';
import 'package:doublefeel_flutter/core/logging/app_logger.dart';
import 'package:doublefeel_flutter/core/network/api/health_api.dart';
import 'package:doublefeel_flutter/core/network/api/harmony_api.dart';
import 'package:doublefeel_flutter/core/network/dio_client.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
... ... @@ -31,15 +35,37 @@ OhosHealthRawDataSyncService createDefaultOhosHealthRawDataSyncService({
return OhosHealthRawDataSyncService(
localStore: localStore,
remoteDataSource: OhosHarmonyHealthRawDataRemoteDataSource(
HarmonyApiOhosRawDataClient(Get.find<HarmonyApi>()),
HarmonyApiOhosRawDataClient(
Get.find<HarmonyApi>(),
healthApi: _resolveHealthApi(),
),
),
);
}
/// HealthApi is normally lazy-registered. Activity-goal synchronization can
/// start before that lazy dependency is materialized, so construct the same
/// lightweight API wrapper from the app-wide DioClient as a fallback.
HealthApi? _resolveHealthApi() {
if (Get.isRegistered<HealthApi>()) {
return Get.find<HealthApi>();
}
if (Get.isRegistered<DioClient>()) {
return HealthApi(Get.find<DioClient>());
}
return null;
}
class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient {
const HarmonyApiOhosRawDataClient(this._harmonyApi, [this._healthKitApi]);
const HarmonyApiOhosRawDataClient(
this._harmonyApi, {
HealthApi? healthApi,
AppHealthKitHostApi? healthKitApi,
}) : _healthApi = healthApi,
_healthKitApi = healthKitApi;
final HarmonyApi _harmonyApi;
final HealthApi? _healthApi;
final AppHealthKitHostApi? _healthKitApi;
bool get _preferNativeHealthData =>
... ... @@ -118,11 +144,6 @@ class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient {
@override
Future<AppResult<V2ActivityTarget>> getActivityGoal() async {
if (!_preferNativeHealthData) {
AppLogger.i('[OHOS_ACTIVITY_GOAL] source=remote native_enabled=false');
return _harmonyApi.getActivityGoal();
}
try {
final goal =
await (_healthKitApi ?? AppHealthKitHostApi()).readActivityGoal();
... ... @@ -138,6 +159,13 @@ class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient {
'[OHOS_ACTIVITY_GOAL] '
'source=native value=${_goalLogValue(nativeGoal)}',
);
AppLogger.i(
'[OHOS_ACTIVITY_GOAL] '
'upload_scheduled value=${_goalLogValue(nativeGoal)}',
);
// Deliberately do not await: a failed request must not prevent the
// user from entering the app or the health-data sync from continuing.
unawaited(_uploadNativeActivityGoal(nativeGoal));
return AppSuccess(nativeGoal);
}
}
... ... @@ -163,6 +191,29 @@ class HarmonyApiOhosRawDataClient implements OhosHarmonyRawDataClient {
return remoteResult;
}
Future<void> _uploadNativeActivityGoal(V2ActivityTarget goal) async {
final healthApi = _healthApi;
if (healthApi == null) {
AppLogger.i(
'[OHOS_ACTIVITY_GOAL] upload_skipped reason=health_api_missing');
return;
}
final result = await healthApi.uploadV2ActivityTarget(goal);
switch (result) {
case AppSuccess():
AppLogger.i(
'[OHOS_ACTIVITY_GOAL] '
'upload_success value=${_goalLogValue(goal)}',
);
case AppFailure(:final error):
AppLogger.i(
'[OHOS_ACTIVITY_GOAL] '
'upload_failed error=$error value=${_goalLogValue(goal)}',
);
}
}
static bool _hasGoal(V2ActivityTarget goal) {
return goal.move != null ||
goal.step != null ||
... ...
... ... @@ -404,7 +404,8 @@ class V2RealtimeStressData {
/// v2/activity_target/ response
/// {"id":0,"create_time":0,"update_time":0,"user_id":131,
/// "move":300,"step":8000,"stand":12,"exercise":3600}
/// "move":300,"step":8000,"stand":12,"exercise":3600,
/// "exercise_time_goal":3600}
class V2ActivityTarget {
const V2ActivityTarget({
this.id,
... ... @@ -430,7 +431,8 @@ class V2ActivityTarget {
/// 站立小时目标
final int? stand;
/// 锻炼时长目标(秒)
/// 锻炼时长目标(秒)。上传时也会同步写入同义字段
/// [exercise_time_goal],两者始终使用同一个值。
final int? exercise;
final int? createTime;
... ... @@ -444,7 +446,8 @@ class V2ActivityTarget {
move: _parseInt(json['move']),
step: _parseInt(json['step']),
stand: _parseInt(json['stand']),
exercise: _parseInt(json['exercise']),
exercise:
_parseInt(json['exercise']) ?? _parseInt(json['exercise_time_goal']),
createTime: _parseInt(json['create_time']),
updateTime: _parseInt(json['update_time']),
sleepTargetDuration: _parseInt(json['sleep_target_duration']),
... ... @@ -458,7 +461,10 @@ class V2ActivityTarget {
if (move != null) val['move'] = move;
if (step != null) val['step'] = step;
if (stand != null) val['stand'] = stand;
if (exercise != null) val['exercise'] = exercise;
if (exercise != null) {
val['exercise'] = exercise;
val['exercise_time_goal'] = exercise;
}
if (createTime != null) val['create_time'] = createTime;
if (updateTime != null) val['update_time'] = updateTime;
if (sleepTargetDuration != null) {
... ...
... ... @@ -99,7 +99,7 @@ export class HealthKitHostApiImpl extends HealthKitHostApi {
result.success(new HealthActivityGoal(
this.activeCaloriesToMoveGoal(report.activeCaloriesGoal),
report.stepsGoal,
report.activeHoursGoal,
this.activeHoursToStandGoal(report.activeHoursGoal),
this.exerciseMinutesToSeconds(report.exerciseGoal),
));
})
... ... @@ -141,13 +141,22 @@ export class HealthKitHostApiImpl extends HealthKitHostApi {
}
private exerciseMinutesToSeconds(value: number | undefined): number | undefined {
return value === undefined ? undefined : Math.round(value * 60);
// Health Service Kit reports the goal in minutes; the activity-target API
// uses seconds. `Int(Double)` on iOS truncates, so use the same rule here.
return value === undefined ? undefined : Math.floor(value * 60);
}
private activeCaloriesToMoveGoal(value: number | undefined): number | undefined {
// Health Service Kit uses calories, while the shared activity-target API
// (and HealthKit) use kilocalories.
return value === undefined ? undefined : Math.floor(value / 1000);
}
private activeHoursToStandGoal(value: number | undefined): number | undefined {
// Both platforms upload an integral count of stand hours.
return value === undefined ? undefined : Math.floor(value);
}
private async requestAuthorizations(): Promise<healthStore.AuthorizationResponse> {
await this.ensureInitialized();
return healthStore.requestAuthorizations(this.context, this.authorizationRequest);
... ...