Commit a68331488cba1f5715917abecee8bd5f41a2c6a4

Authored by 权海
1 parent 987f24d7

feat(ui):本地Health数据对接

... ... @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
... ... @@ -342,14 +342,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
... ... @@ -420,14 +416,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
... ...
... ... @@ -66,33 +66,32 @@ final class HealthKitService {
guard isHealthDataAvailable else { return false }
let endDate = Date()
let startDate = requestedStartDate
?? Calendar.current.date(byAdding: .year, value: -1, to: endDate)
?? Date(timeInterval: -1 * 365 * 24 * 60 * 60, since: endDate)
if let sleepType = NativeHealthTypeCatalog.category(.sleepAnalysis),
await hasAnyReadableSample(
of: [sleepType],
startDate: startDate,
let sampleTypes = NativeHealthTypeCatalog.readTypes.compactMap { $0 as? HKSampleType }
if await hasAnyReadableSample(
of: sampleTypes,
startDate: requestedStartDate,
endDate: endDate
) {
return true
}
if NativeHealthTypeCatalog.readTypes.contains(.activitySummaryType()),
await hasAnyReadableActivitySummary(startDate: requestedStartDate, endDate: endDate) {
return true
}
return false
}
private func hasAnyReadableSample(
of sampleTypes: [HKSampleType],
startDate: Date,
startDate: Date?,
endDate: Date
) async -> Bool {
guard !sampleTypes.isEmpty else { return false }
let predicate = HKQuery.predicateForSamples(
withStart: startDate,
end: endDate,
options: []
)
let predicate = startDate.map {
HKQuery.predicateForSamples(withStart: $0, end: endDate, options: [])
}
let state = HealthReadableDataProbeState()
return await withCheckedContinuation { continuation in
... ... @@ -119,6 +118,27 @@ final class HealthKitService {
}
}
private func hasAnyReadableActivitySummary(startDate: Date?, endDate: Date) async -> Bool {
let calendar = Calendar.current
let start = startDate ?? Date(timeIntervalSince1970: 0)
var startComponents = calendar.dateComponents([.era, .year, .month, .day], from: start)
var endComponents = calendar.dateComponents([.era, .year, .month, .day], from: endDate)
startComponents.calendar = calendar
endComponents.calendar = calendar
let predicate = HKQuery.predicate(
forActivitySummariesBetweenStart: startComponents,
end: endComponents
)
return await withCheckedContinuation { continuation in
let query = HKActivitySummaryQuery(predicate: predicate) { _, summaries, _ in
continuation.resume(returning: summaries?.isEmpty == false)
}
healthStore.execute(query)
}
}
func startBackgroundObserversIfNeeded() {
Task { @MainActor [weak self] in
self?.observerController.startIfNeeded()
... ...
... ... @@ -364,6 +364,8 @@ class HealthKitRawDataApiPigeonCodec: FlutterStandardMessageCodec, @unchecked Se
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol HealthKitRawDataHostApi {
/// 是否有AppleHealth数据
func hasHealthData(completion: @escaping (Result<Bool, Error>) -> Void)
/// 从AppleHealth 中读取原始数据
func getHealthKitRawData(dataType: Int64, startTime: Int64, endTime: Int64, completion: @escaping (Result<[HealthKitRawDataPoint], Error>) -> Void)
/// 从从AppleHealth中读取睡眠数据
... ... @@ -382,6 +384,22 @@ class HealthKitRawDataHostApiSetup {
/// Sets up an instance of `HealthKitRawDataHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: HealthKitRawDataHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
/// 是否有AppleHealth数据
let hasHealthDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.hasHealthData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
hasHealthDataChannel.setMessageHandler { _, reply in
api.hasHealthData { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
hasHealthDataChannel.setMessageHandler(nil)
}
/// 从AppleHealth 中读取原始数据
let getHealthKitRawDataChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawData\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
... ...
import Foundation
final class HealthKitRawDataHostApiImpl: HealthKitRawDataHostApi {
private let service: HealthKitService
init(service: HealthKitService = .shared) {
self.service = service
}
func hasHealthData(completion: @escaping (Result<Bool, Error>) -> Void) {
Task {
let hasData = await service.hasAnyReadableData()
completion(.success(hasData))
}
}
func getHealthKitRawData(
dataType: Int64,
... ...
... ... @@ -57,6 +57,10 @@ class HealthRawDataCoreService {
return _localStore.closeDatabase(userId: _userId);
}
Future<bool> hasHealthData() {
return _rawDataApi.hasHealthData();
}
Stream<HealthRawDataUpdatedEvent> get healthDataUpdatedStream =>
_healthDataUpdatedController.stream;
... ... @@ -732,6 +736,7 @@ class HealthRawDataCoreService {
HealthRawHrvStressPoint(
userId: userId,
rawEndTime: point.endTime,
rawHrv: pointValue,
result: trendHrv,
sourceStartTime: sourceRange.start,
sourceEndTime: sourceRange.end,
... ... @@ -1263,6 +1268,7 @@ class HealthRawHrvStressPoint {
const HealthRawHrvStressPoint({
required this.userId,
required this.rawEndTime,
required this.rawHrv,
required this.result,
required this.sourceStartTime,
required this.sourceEndTime,
... ... @@ -1277,6 +1283,7 @@ class HealthRawHrvStressPoint {
final int userId;
final int rawEndTime;
final double rawHrv;
final double result;
final int sourceStartTime;
final int sourceEndTime;
... ... @@ -1290,16 +1297,18 @@ class HealthRawHrvStressPoint {
factory HealthRawHrvStressPoint.fromDb(Map<String, Object?> row) {
final result = (row['result'] as num).toDouble();
final rawHrv = _positiveDouble(row['raw_hrv']) ?? result;
final baselineHrv = _positiveDouble(row['baseline_hrv']) ?? result;
final baselineAwakeHrv =
_positiveDouble(row['baseline_awake_hrv']) ?? baselineHrv;
return HealthRawHrvStressPoint(
userId: row['user_id'] as int,
rawEndTime: row['raw_end_time'] as int,
rawHrv: rawHrv,
result: result,
sourceStartTime: row['source_start_time'] as int,
sourceEndTime: row['source_end_time'] as int,
state: HealthRawStressState.normal,
state: _stressStateFromDb(row['state']) ?? HealthRawStressState.normal,
baselineHrv: baselineHrv,
baselineAwakeHrv: baselineAwakeHrv,
baselineSleepHrv: _positiveDouble(row['baseline_sleep_hrv']),
... ... @@ -1329,6 +1338,8 @@ class HealthRawRealtimeStressPoint {
final HealthRawPointFlags flags;
final bool uploaded;
HealthRawStressState get state => healthRawRealtimeStressState(result);
factory HealthRawRealtimeStressPoint.fromDb(Map<String, Object?> row) {
return HealthRawRealtimeStressPoint(
userId: row['user_id'] as int,
... ... @@ -1342,6 +1353,13 @@ class HealthRawRealtimeStressPoint {
}
}
HealthRawStressState healthRawRealtimeStressState(num value) {
if (value >= 81) return HealthRawStressState.overload;
if (value >= 61) return HealthRawStressState.attention;
if (value >= 21) return HealthRawStressState.normal;
return HealthRawStressState.excellent;
}
HealthRawPointFlags _flagsFromDb(Map<String, Object?> row) {
return HealthRawPointFlags(
isWorkout: (row['is_workout'] as int? ?? 0) == 1,
... ... @@ -1357,6 +1375,15 @@ double? _positiveDouble(Object? value) {
return result;
}
HealthRawStressState? _stressStateFromDb(Object? value) {
final stateValue = value as int?;
if (stateValue == null) return null;
for (final state in HealthRawStressState.values) {
if (state.value == stateValue) return state;
}
return null;
}
class HealthRawStressDbRows {
const HealthRawStressDbRows({
required this.hrvRows,
... ... @@ -1542,7 +1569,7 @@ class HealthRawStressLocalStore {
final db = await factory.openDatabase(
path,
options: OpenDatabaseOptions(
version: 3,
version: 4,
onCreate: (db, version) async {
await _createTables(db);
},
... ... @@ -1554,6 +1581,9 @@ class HealthRawStressLocalStore {
if (oldVersion < 3) {
await _addHrvBaselineColumns(db);
}
if (oldVersion < 4) {
await _addHrvRawAndStateColumns(db);
}
},
),
);
... ... @@ -1566,9 +1596,11 @@ class HealthRawStressLocalStore {
CREATE TABLE IF NOT EXISTS $hrvResultsTable (
raw_end_time INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
raw_hrv REAL NOT NULL DEFAULT 0,
result REAL NOT NULL,
source_start_time INTEGER NOT NULL,
source_end_time INTEGER NOT NULL,
state INTEGER NOT NULL DEFAULT 3,
baseline_hrv REAL NOT NULL DEFAULT 0,
baseline_awake_hrv REAL NOT NULL DEFAULT 0,
baseline_sleep_hrv REAL,
... ... @@ -1630,6 +1662,21 @@ CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable (
}
}
Future<void> _addHrvRawAndStateColumns(DatabaseExecutor db) async {
for (final columnSql in const [
'raw_hrv REAL NOT NULL DEFAULT 0',
'state INTEGER NOT NULL DEFAULT 3',
]) {
try {
await db.execute(
'ALTER TABLE $hrvResultsTable ADD COLUMN $columnSql',
);
} on DatabaseException catch (error) {
if (!error.isDuplicateColumnError()) rethrow;
}
}
}
Future<int?> _latestSourceStartTime(int userId, String table) async {
final db = await _database(userId);
final rows = await db.query(
... ... @@ -1662,9 +1709,11 @@ CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable (
return <String, Object?>{
'raw_end_time': point.rawEndTime,
'user_id': point.userId,
'raw_hrv': point.rawHrv,
'result': point.result,
'source_start_time': point.sourceStartTime,
'source_end_time': point.sourceEndTime,
'state': point.state.value,
'baseline_hrv': point.baselineHrv,
'baseline_awake_hrv': point.baselineAwakeHrv,
'baseline_sleep_hrv': point.baselineSleepHrv,
... ... @@ -1709,9 +1758,11 @@ CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable (
table,
<String, Object?>{
'user_id': row['user_id'],
if (row.containsKey('raw_hrv')) 'raw_hrv': row['raw_hrv'],
'result': row['result'],
'source_start_time': row['source_start_time'],
'source_end_time': row['source_end_time'],
if (row.containsKey('state')) 'state': row['state'],
if (row.containsKey('baseline_hrv'))
'baseline_hrv': row['baseline_hrv'],
if (row.containsKey('baseline_awake_hrv'))
... ...
... ... @@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:doublefeel_flutter/core/services/health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
import 'package:doublefeel_flutter/data/models/health/activity/activity_burn_statistics_data_v2.dart';
import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
import 'package:doublefeel_flutter/data/models/health/hrv/hrv_statistics_data.dart';
import 'package:doublefeel_flutter/data/models/health/sleep/sleep_statistics_data.dart';
import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart';
... ... @@ -253,6 +254,135 @@ class LocalHealthDataConvert {
);
}
static V2ActivityTarget v2ActivityTarget({
required int userId,
required List<HealthKitRawActivityDataPoint> activity,
}) {
final latestWithGoal = activity
.where(_hasActivityGoal)
.fold<HealthKitRawActivityDataPoint?>(
null,
(latest, item) =>
latest == null || item.endTime > latest.endTime ? item : latest,
);
return V2ActivityTarget(
userId: userId,
move: latestWithGoal?.activeEnergyBurnedGoal?.round(),
step: latestWithGoal?.appleMoveTimeGoal?.round(),
stand: latestWithGoal?.standHoursGoal?.round(),
exercise: latestWithGoal?.exerciseTimeGoal == null
? null
: (latestWithGoal!.exerciseTimeGoal! * 60).round(),
updateTime: latestWithGoal?.endTime,
sleepTargetDuration: _sleepGoalMinutes.round(),
);
}
static V2HealthData v2HealthData({
required DateTime day,
required List<HealthRawHrvStressPoint> hrvPoints,
required List<HealthKitRawActivityDataPoint> activity,
required List<HealthKitRawDataPoint> heartRate,
required List<HealthKitRawDataPoint> restingHeartRate,
required List<HealthKitRawDataPoint> sleepIntervals,
}) {
final activitySummary = _activityDaySummary(day, activity);
final sleepSummary = _sleepDaySummary(day, sleepIntervals, heartRate);
final dayStart = unixSeconds(day);
final dayEnd = unixSeconds(day.add(const Duration(days: 1)));
final sleepWindowStart =
unixSeconds(day.subtract(const Duration(hours: 12)));
final sleepWindowEnd = unixSeconds(day.add(const Duration(hours: 12)));
final sleepTimeList = sleepIntervals
.where(
(e) => e.endTime > sleepWindowStart && e.startTime < sleepWindowEnd)
.map(
(e) => V2SleepTimeRange(
fromTime: math.max(e.startTime, sleepWindowStart),
toTime: math.min(e.endTime, sleepWindowEnd),
),
)
.toList();
final dayHr = heartRate
.where((e) => e.endTime >= dayStart && e.endTime < dayEnd)
.toList();
final dayRestingHr = restingHeartRate
.where((e) => e.endTime >= dayStart && e.endTime < dayEnd)
.toList()
..sort((a, b) => a.endTime.compareTo(b.endTime));
return V2HealthData(
hrvAvg: _averageOrNull(hrvPoints.map((e) => e.result).toList())?.round(),
lastRestingHrValue:
dayRestingHr.isEmpty ? null : dayRestingHr.last.value?.round(),
hrAvg: _averageOrNull(dayHr.map((e) => e.value).whereType<num>().toList())
?.round(),
move: activitySummary.move.round(),
exercise: activitySummary.exerciseSeconds.round(),
stand: activitySummary.standHours.round(),
steps: activitySummary.steps.round(),
sleepDuration: sleepSummary.durationSeconds.round(),
sleepScore: sleepSummary.score?.toDouble(),
sleepState: sleepSummary.evaluate?.round(),
sleepTimeList: sleepTimeList,
);
}
static V2HrvTrendData v2HrvTrendData(
List<HealthRawHrvStressPoint> hrvPoints,
) {
return V2HrvTrendData(
list: [
for (final point in hrvPoints)
V2HrvTrendItem(
time: point.rawEndTime,
rawHrv: point.rawHrv,
trendHrv: point.result.round(),
state: point.state.value,
),
],
);
}
static V2LatestHrvData v2LatestHrvData(
List<HealthRawHrvStressPoint> hrvPoints,
) {
final latest = _latestHrvByRawEndTime(hrvPoints);
return V2LatestHrvData(
latestHrv: latest?.result,
latestDataTime: latest?.rawEndTime,
state: latest?.state.value,
);
}
static V2RealtimeStressData v2RealtimeStressData(
List<HealthRawRealtimeStressPoint> realtimePoints,
) {
return V2RealtimeStressData(
list: [
for (final point in realtimePoints)
V2RealtimeStressItem(
time: point.rawEndTime,
value: point.result.round(),
state: point.state.value,
),
],
);
}
static V2StressScore v2StressScore(
List<HealthRawRealtimeStressPoint> realtimePoints,
) {
final averageStress = _averageOrNull(
realtimePoints.map((e) => e.result).toList(),
);
final score = averageStress?.round();
return V2StressScore(
state: score == null ? 0 : healthRawRealtimeStressState(score).value,
comprehensiveScore: score,
);
}
static _SleepDaySummary _sleepDaySummary(
DateTime day,
List<HealthKitRawDataPoint> sleepIntervals,
... ... @@ -311,6 +441,23 @@ class LocalHealthDataConvert {
);
}
static bool _hasActivityGoal(HealthKitRawActivityDataPoint item) {
return item.activeEnergyBurnedGoal != null ||
item.appleMoveTimeGoal != null ||
item.standHoursGoal != null ||
item.exerciseTimeGoal != null;
}
static HealthRawHrvStressPoint? _latestHrvByRawEndTime(
List<HealthRawHrvStressPoint> points,
) {
return points.fold<HealthRawHrvStressPoint?>(
null,
(latest, item) =>
latest == null || item.rawEndTime > latest.rawEndTime ? item : latest,
);
}
static _ActivityDaySummary? _latestActivitySummaryWithGoal(
List<_ActivityDaySummary> daily,
) {
... ...
... ... @@ -144,51 +144,159 @@ class LocalHealthDataSource implements HealthDataSource {
@override
Future<AppResult<V2HealthEverUploaded>> getHealthDataEverUploaded() async {
var data = V2HealthEverUploaded(flag: true);
return AppSuccess(data);
try {
final hasHealthData = await coreService.hasHealthData();
return AppSuccess(V2HealthEverUploaded(flag: hasHealthData));
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2ActivityTarget>> getV2ActivityTarget(
int? queryUserId) async {
var data = V2ActivityTarget(
sleepTargetDuration: 480,
);
return AppSuccess(data);
try {
final now = DateTime.now();
final queryStart = LocalHealthDataConvert.unixSeconds(
now.subtract(
const Duration(days: HealthRawDataCoreService.defaultLookbackDays)),
);
final queryEnd = LocalHealthDataConvert.unixSeconds(now);
final activity = await coreService.queryRawActivitySummaries(
startTime: queryStart,
endTime: queryEnd,
);
return AppSuccess(
LocalHealthDataConvert.v2ActivityTarget(
userId: 0,
activity: activity,
),
);
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2HealthData>> getV2HealthData(
int? queryUserId, int intDate) async {
var data = V2HealthData(hrvAvg: 10);
return AppSuccess(data);
try {
final day = LocalHealthDataConvert.dateFromKey(intDate);
final dayStart = LocalHealthDataConvert.unixSeconds(day);
final dayEnd = LocalHealthDataConvert.unixSeconds(
day.add(const Duration(days: 1)),
) -
1;
final sleepStart = LocalHealthDataConvert.unixSeconds(
day.subtract(const Duration(hours: 12)),
);
final sleepEnd = LocalHealthDataConvert.unixSeconds(
day.add(const Duration(hours: 12)),
);
final hrvPoints = await coreService.queryHrvStressPoints(
startTime: dayStart,
endTime: dayEnd,
);
final activity = await coreService.queryRawActivitySummaries(
startTime: dayStart,
endTime: dayEnd,
);
final heartRate = await coreService.queryRawDataPoints(
dataType: HealthDataUploadType.heartRate.type,
startTime: sleepStart,
endTime: dayEnd,
);
final restingHeartRate = await coreService.queryRawDataPoints(
dataType: HealthDataUploadType.restingHeartRate.type,
startTime: dayStart,
endTime: dayEnd,
);
final sleepIntervals = await coreService.queryRawSleepIntervals(
startTime: sleepStart,
endTime: sleepEnd,
);
return AppSuccess(
LocalHealthDataConvert.v2HealthData(
day: day,
hrvPoints: hrvPoints,
activity: activity,
heartRate: heartRate,
restingHeartRate: restingHeartRate,
sleepIntervals: sleepIntervals,
),
);
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2HrvTrendData>> getV2HrvTrend(
int? queryUserId, int intDate) async {
var data = V2HrvTrendData();
return AppSuccess(data);
try {
final (startTime, endTime) = _dayRange(intDate);
final hrvPoints = await coreService.queryHrvStressPoints(
startTime: startTime,
endTime: endTime,
);
return AppSuccess(LocalHealthDataConvert.v2HrvTrendData(hrvPoints));
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2LatestHrvData>> getV2LatestHrv(
int? queryUserId, int intDate) async {
var data = V2LatestHrvData();
return AppSuccess(data);
try {
final (startTime, endTime) = _dayRange(intDate);
final hrvPoints = await coreService.queryHrvStressPoints(
startTime: startTime,
endTime: endTime,
);
return AppSuccess(LocalHealthDataConvert.v2LatestHrvData(hrvPoints));
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2RealtimeStressData>> getV2RealtimeStress(
int? queryUserId, int intDate) async {
var data = V2RealtimeStressData();
return AppSuccess(data);
try {
final (startTime, endTime) = _dayRange(intDate);
final realtimePoints = await coreService.queryRealtimeStressPoints(
startTime: startTime,
endTime: endTime,
);
return AppSuccess(
LocalHealthDataConvert.v2RealtimeStressData(realtimePoints),
);
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
@override
Future<AppResult<V2StressScore>> getV2StressScore(
int? queryUserId, int intDate) async {
var data = V2StressScore(state: 1, comprehensiveScore: 18);
return AppSuccess(data);
try {
final (startTime, endTime) = _dayRange(intDate);
final realtimePoints = await coreService.queryRealtimeStressPoints(
startTime: startTime,
endTime: endTime,
);
return AppSuccess(LocalHealthDataConvert.v2StressScore(realtimePoints));
} catch (error) {
return AppFailure(AppUnknownError(error));
}
}
(int startTime, int endTime) _dayRange(int intDate) {
final day = LocalHealthDataConvert.dateFromKey(intDate);
return (
LocalHealthDataConvert.unixSeconds(day),
LocalHealthDataConvert.unixSeconds(day.add(const Duration(days: 1))) - 1,
);
}
}
... ...
... ... @@ -380,6 +380,35 @@ class HealthKitRawDataHostApi {
final String pigeonVar_messageChannelSuffix;
/// 是否有AppleHealth数据
Future<bool> hasHealthData() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.hasHealthData$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as bool?)!;
}
}
/// 从AppleHealth 中读取原始数据
Future<List<HealthKitRawDataPoint>> getHealthKitRawData(int dataType, int startTime, int endTime) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.HealthKitRawDataHostApi.getHealthKitRawData$pigeonVar_messageChannelSuffix';
... ...
... ... @@ -89,6 +89,10 @@ class HealthKitRawActivityDataPoint {
)
@HostApi()
abstract class HealthKitRawDataHostApi {
/// 是否有AppleHealth数据
@async
bool hasHealthData();
/// 从AppleHealth 中读取原始数据
@async
List<HealthKitRawDataPoint> getHealthKitRawData(
... ...
... ... @@ -283,6 +283,7 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore {
hrvByTime[point.rawEndTime] = HealthRawHrvStressPoint(
userId: point.userId,
rawEndTime: point.rawEndTime,
rawHrv: point.rawHrv,
result: point.result,
sourceStartTime: point.sourceStartTime,
sourceEndTime: point.sourceEndTime,
... ... @@ -374,6 +375,7 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore {
return HealthRawHrvStressPoint(
userId: point.userId,
rawEndTime: point.rawEndTime,
rawHrv: point.rawHrv,
result: point.result,
sourceStartTime: point.sourceStartTime,
sourceEndTime: point.sourceEndTime,
... ... @@ -413,9 +415,11 @@ class _MemoryHealthRawStressLocalStore extends HealthRawStressLocalStore {
return <String, Object?>{
'raw_end_time': point.rawEndTime,
'user_id': point.userId,
'raw_hrv': point.rawHrv,
'result': point.result,
'source_start_time': point.sourceStartTime,
'source_end_time': point.sourceEndTime,
'state': point.state.value,
'baseline_hrv': point.baselineHrv,
'baseline_awake_hrv': point.baselineAwakeHrv,
'baseline_sleep_hrv': point.baselineSleepHrv,
... ...