|
...
|
...
|
@@ -8,18 +8,19 @@ import 'package:flutter/foundation.dart'; |
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:sqflite/sqflite.dart';
|
|
|
|
|
|
|
|
import '../../data/models/enums/app_enums.dart';
|
|
|
|
import '../../l10n/l10n_extensions.dart';
|
|
|
|
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 '../../../../data/models/enums/app_enums.dart';
|
|
|
|
import '../../../../l10n/l10n_extensions.dart';
|
|
|
|
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_models.dart';
|
|
|
|
import 'health_raw_local_notification_debug_store.dart';
|
|
|
|
import 'health_raw_local_notification.dart';
|
|
|
|
import 'health_raw_stress_calculator.dart';
|
|
|
|
import 'health_sleep_calculator.dart';
|
|
|
|
import '../health_raw_stress_calculator.dart';
|
|
|
|
import '../health_sleep_calculator.dart';
|
|
|
|
|
|
|
|
class HealthRawDataCoreService {
|
|
|
|
static const int defaultLookbackDays = 183;
|
|
...
|
...
|
@@ -1643,460 +1644,6 @@ class HealthRawDataCoreService { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawStressCalculationResult {
|
|
|
|
const HealthRawStressCalculationResult({
|
|
|
|
required this.userId,
|
|
|
|
required this.hrvStressPoints,
|
|
|
|
required this.realtimeStressPoints,
|
|
|
|
required this.dailyStressPoints,
|
|
|
|
this.sleepResults = const <HealthRawSleepResult>[],
|
|
|
|
});
|
|
|
|
|
|
|
|
final int userId;
|
|
|
|
final List<HealthRawHrvStressPoint> hrvStressPoints;
|
|
|
|
final List<HealthRawRealtimeStressPoint> realtimeStressPoints;
|
|
|
|
final List<HealthRawDailyStressPoint> dailyStressPoints;
|
|
|
|
final List<HealthRawSleepResult> sleepResults;
|
|
|
|
|
|
|
|
HealthRawStressCalculationResult copyWith({
|
|
|
|
List<HealthRawHrvStressPoint>? hrvStressPoints,
|
|
|
|
List<HealthRawRealtimeStressPoint>? realtimeStressPoints,
|
|
|
|
List<HealthRawDailyStressPoint>? dailyStressPoints,
|
|
|
|
List<HealthRawSleepResult>? sleepResults,
|
|
|
|
}) {
|
|
|
|
return HealthRawStressCalculationResult(
|
|
|
|
userId: userId,
|
|
|
|
hrvStressPoints: hrvStressPoints ?? this.hrvStressPoints,
|
|
|
|
realtimeStressPoints: realtimeStressPoints ?? this.realtimeStressPoints,
|
|
|
|
dailyStressPoints: dailyStressPoints ?? this.dailyStressPoints,
|
|
|
|
sleepResults: sleepResults ?? this.sleepResults,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawDataUpdatedEvent {
|
|
|
|
const HealthRawDataUpdatedEvent({required this.dataTypes});
|
|
|
|
|
|
|
|
final List<int> dataTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawMarkedDataSet {
|
|
|
|
const HealthRawMarkedDataSet({
|
|
|
|
required this.hrv,
|
|
|
|
required this.heartRate,
|
|
|
|
required this.restingHeartRate,
|
|
|
|
});
|
|
|
|
|
|
|
|
final List<HealthRawMarkedDataPoint> hrv;
|
|
|
|
final List<HealthRawMarkedDataPoint> heartRate;
|
|
|
|
final List<HealthRawMarkedDataPoint> restingHeartRate;
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawMarkedDataPoint {
|
|
|
|
HealthRawMarkedDataPoint({
|
|
|
|
required this.raw,
|
|
|
|
required this.flags,
|
|
|
|
});
|
|
|
|
|
|
|
|
final HealthKitRawDataPoint raw;
|
|
|
|
HealthRawPointFlags flags;
|
|
|
|
|
|
|
|
int get startTime => raw.startTime;
|
|
|
|
int get endTime => raw.endTime;
|
|
|
|
double? get value => raw.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum HealthRawPointContext {
|
|
|
|
workout,
|
|
|
|
workoutRecovery,
|
|
|
|
sleep,
|
|
|
|
suspectedActivity,
|
|
|
|
awake,
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawPointFlags {
|
|
|
|
const HealthRawPointFlags({
|
|
|
|
required this.isWorkout,
|
|
|
|
required this.isWorkoutRecovery,
|
|
|
|
required this.isSleepLikely,
|
|
|
|
required this.isSuspectedActivity,
|
|
|
|
});
|
|
|
|
|
|
|
|
const HealthRawPointFlags.none()
|
|
|
|
: isWorkout = false,
|
|
|
|
isWorkoutRecovery = false,
|
|
|
|
isSleepLikely = false,
|
|
|
|
isSuspectedActivity = false;
|
|
|
|
|
|
|
|
final bool isWorkout;
|
|
|
|
final bool isWorkoutRecovery;
|
|
|
|
final bool isSleepLikely;
|
|
|
|
final bool isSuspectedActivity;
|
|
|
|
|
|
|
|
HealthRawPointContext get context {
|
|
|
|
if (isWorkout) return HealthRawPointContext.workout;
|
|
|
|
if (isWorkoutRecovery) return HealthRawPointContext.workoutRecovery;
|
|
|
|
if (isSleepLikely) return HealthRawPointContext.sleep;
|
|
|
|
if (isSuspectedActivity) return HealthRawPointContext.suspectedActivity;
|
|
|
|
return HealthRawPointContext.awake;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get isAwakeBaselineEligible =>
|
|
|
|
!isSleepLikely && !isWorkout && !isWorkoutRecovery;
|
|
|
|
|
|
|
|
bool get isSleepHrvBaselineEligible =>
|
|
|
|
isSleepLikely && !isWorkout && !isWorkoutRecovery;
|
|
|
|
|
|
|
|
bool get isAwakeHrBaselineEligible =>
|
|
|
|
!isSleepLikely && !isWorkout && !isWorkoutRecovery;
|
|
|
|
|
|
|
|
bool get isSleepHrBaselineEligible =>
|
|
|
|
isSleepLikely && !isWorkout && !isWorkoutRecovery;
|
|
|
|
|
|
|
|
bool get isCurrentAwakeHrEligible => isAwakeHrBaselineEligible;
|
|
|
|
|
|
|
|
bool matchesContext(HealthRawPointContext pointContext) {
|
|
|
|
return context == pointContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
HealthRawPointFlags copyWith({
|
|
|
|
bool? isWorkout,
|
|
|
|
bool? isWorkoutRecovery,
|
|
|
|
bool? isSleepLikely,
|
|
|
|
bool? isSuspectedActivity,
|
|
|
|
}) {
|
|
|
|
return HealthRawPointFlags(
|
|
|
|
isWorkout: isWorkout ?? this.isWorkout,
|
|
|
|
isWorkoutRecovery: isWorkoutRecovery ?? this.isWorkoutRecovery,
|
|
|
|
isSleepLikely: isSleepLikely ?? this.isSleepLikely,
|
|
|
|
isSuspectedActivity: isSuspectedActivity ?? this.isSuspectedActivity,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum HealthRawStressState {
|
|
|
|
overload(1),
|
|
|
|
attention(2),
|
|
|
|
normal(3),
|
|
|
|
excellent(4);
|
|
|
|
|
|
|
|
const HealthRawStressState(this.value);
|
|
|
|
|
|
|
|
final int value;
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawHrvStressPoint {
|
|
|
|
const HealthRawHrvStressPoint({
|
|
|
|
required this.userId,
|
|
|
|
required this.rawEndTime,
|
|
|
|
required this.rawHrv,
|
|
|
|
required this.result,
|
|
|
|
required this.sourceStartTime,
|
|
|
|
required this.sourceEndTime,
|
|
|
|
required this.state,
|
|
|
|
required this.baselineHrv,
|
|
|
|
required this.baselineAwakeHrv,
|
|
|
|
required this.baselineSleepHrv,
|
|
|
|
required this.baselineRestingHr,
|
|
|
|
this.flags = const HealthRawPointFlags.none(),
|
|
|
|
this.uploaded = false,
|
|
|
|
});
|
|
|
|
|
|
|
|
final int userId;
|
|
|
|
final int rawEndTime;
|
|
|
|
final double rawHrv;
|
|
|
|
final double result;
|
|
|
|
final int sourceStartTime;
|
|
|
|
final int sourceEndTime;
|
|
|
|
final HealthRawStressState state;
|
|
|
|
final double baselineHrv;
|
|
|
|
final double baselineAwakeHrv;
|
|
|
|
final double? baselineSleepHrv;
|
|
|
|
final double baselineRestingHr;
|
|
|
|
final HealthRawPointFlags flags;
|
|
|
|
final bool uploaded;
|
|
|
|
|
|
|
|
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: _stressStateFromDb(row['state']) ?? HealthRawStressState.normal,
|
|
|
|
baselineHrv: baselineHrv,
|
|
|
|
baselineAwakeHrv: baselineAwakeHrv,
|
|
|
|
baselineSleepHrv: _positiveDouble(row['baseline_sleep_hrv']),
|
|
|
|
baselineRestingHr: _positiveDouble(row['baseline_resting_hr']) ?? 65,
|
|
|
|
flags: _flagsFromDb(row),
|
|
|
|
uploaded: (row['uploaded'] as int) == 1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawRealtimeStressPoint {
|
|
|
|
const HealthRawRealtimeStressPoint({
|
|
|
|
required this.userId,
|
|
|
|
required this.rawEndTime,
|
|
|
|
required this.rawHr,
|
|
|
|
required this.result,
|
|
|
|
required this.sourceStartTime,
|
|
|
|
required this.sourceEndTime,
|
|
|
|
this.flags = const HealthRawPointFlags.none(),
|
|
|
|
this.uploaded = false,
|
|
|
|
});
|
|
|
|
|
|
|
|
final int userId;
|
|
|
|
final int rawEndTime;
|
|
|
|
final double rawHr;
|
|
|
|
final double result;
|
|
|
|
final int sourceStartTime;
|
|
|
|
final int sourceEndTime;
|
|
|
|
final HealthRawPointFlags flags;
|
|
|
|
final bool uploaded;
|
|
|
|
|
|
|
|
HealthRawStressState get state => healthRawRealtimeStressState(result);
|
|
|
|
bool get isWorkout => flags.isWorkout;
|
|
|
|
bool get isWorkoutRecovery => flags.isWorkoutRecovery;
|
|
|
|
bool get isSleepLikely => flags.isSleepLikely;
|
|
|
|
bool get isSuspectedActivity => flags.isSuspectedActivity;
|
|
|
|
|
|
|
|
factory HealthRawRealtimeStressPoint.fromDb(Map<String, Object?> row) {
|
|
|
|
return HealthRawRealtimeStressPoint(
|
|
|
|
userId: row['user_id'] as int,
|
|
|
|
rawEndTime: row['raw_end_time'] as int,
|
|
|
|
rawHr: (row['raw_hr'] as num?)?.toDouble() ?? 0,
|
|
|
|
result: (row['result'] as num).toDouble(),
|
|
|
|
sourceStartTime: row['source_start_time'] as int,
|
|
|
|
sourceEndTime: row['source_end_time'] as int,
|
|
|
|
flags: _flagsFromDb(row),
|
|
|
|
uploaded: (row['uploaded'] as int) == 1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawDailyStressPoint {
|
|
|
|
const HealthRawDailyStressPoint({
|
|
|
|
required this.userId,
|
|
|
|
required this.date,
|
|
|
|
required this.stressValue,
|
|
|
|
required this.stressScore,
|
|
|
|
required this.state,
|
|
|
|
required this.dataTime,
|
|
|
|
this.uploaded = false,
|
|
|
|
});
|
|
|
|
|
|
|
|
final int userId;
|
|
|
|
final int date;
|
|
|
|
final double stressValue;
|
|
|
|
final int stressScore;
|
|
|
|
final HealthRawStressState state;
|
|
|
|
final int dataTime;
|
|
|
|
final bool uploaded;
|
|
|
|
|
|
|
|
factory HealthRawDailyStressPoint.fromDb(Map<String, Object?> row) {
|
|
|
|
final stressScore = row['stress_score'] as int;
|
|
|
|
return HealthRawDailyStressPoint(
|
|
|
|
userId: row['user_id'] as int,
|
|
|
|
date: row['date'] as int,
|
|
|
|
stressValue: (row['stress_value'] as num).toDouble(),
|
|
|
|
stressScore: stressScore,
|
|
|
|
state: _stressStateFromDb(row['state']) ??
|
|
|
|
healthRawRealtimeStressState(stressScore),
|
|
|
|
dataTime: row['data_time'] as int,
|
|
|
|
uploaded: (row['uploaded'] as int) == 1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawSleepResult {
|
|
|
|
const HealthRawSleepResult({
|
|
|
|
required this.userId,
|
|
|
|
required this.date,
|
|
|
|
required this.startDate,
|
|
|
|
required this.sleepScore,
|
|
|
|
required this.sleepState,
|
|
|
|
required this.inBedMinutes,
|
|
|
|
required this.awakMinutes,
|
|
|
|
required this.sleepMinutes,
|
|
|
|
this.uploaded = false,
|
|
|
|
});
|
|
|
|
|
|
|
|
final int userId;
|
|
|
|
final int date;
|
|
|
|
final int startDate;
|
|
|
|
final int sleepScore;
|
|
|
|
final int sleepState;
|
|
|
|
final int inBedMinutes;
|
|
|
|
final int awakMinutes;
|
|
|
|
final int sleepMinutes;
|
|
|
|
final bool uploaded;
|
|
|
|
|
|
|
|
factory HealthRawSleepResult.fromDb(Map<String, Object?> row) {
|
|
|
|
return HealthRawSleepResult(
|
|
|
|
userId: row['user_id'] as int,
|
|
|
|
date: row['date'] as int,
|
|
|
|
startDate: row['start_date'] as int,
|
|
|
|
sleepScore: row['sleep_score'] as int,
|
|
|
|
sleepState: row['sleep_state'] as int,
|
|
|
|
inBedMinutes: row['in_bed_minutes'] as int,
|
|
|
|
awakMinutes: row['awak_minutes'] as int,
|
|
|
|
sleepMinutes: row['sleep_minutes'] as int,
|
|
|
|
uploaded: (row['uploaded'] as int? ?? 0) == 1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
int get sleepStateValue => sleepState;
|
|
|
|
|
|
|
|
String? get sleepStateDescription {
|
|
|
|
for (final state in HealthSleepState.values) {
|
|
|
|
if (state.value == sleepState) return state.description;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class HealthRawDailyStressCalculator {
|
|
|
|
const HealthRawDailyStressCalculator._();
|
|
|
|
|
|
|
|
static const int _confirmedActivityBufferSeconds = 12 * 60;
|
|
|
|
|
|
|
|
static HealthRawDailyStressPoint? calculate({
|
|
|
|
required int userId,
|
|
|
|
required int date,
|
|
|
|
required List<HealthRawRealtimeStressPoint> realtimePoints,
|
|
|
|
required int dataTime,
|
|
|
|
}) {
|
|
|
|
final validValuePoints =
|
|
|
|
realtimePoints.where((e) => _isValidStressValue(e.result)).toList();
|
|
|
|
final activityBuffers = _confirmedActivityBuffers(realtimePoints);
|
|
|
|
final values = validValuePoints
|
|
|
|
.where((e) => !_isInTimeRanges(e.rawEndTime, activityBuffers))
|
|
|
|
.map((e) => e.result)
|
|
|
|
.toList()
|
|
|
|
..sort();
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
final stressValue = _integerDouble(_average(values));
|
|
|
|
final stressScore = _dailyStressScore(values).round().clamp(1, 100).toInt();
|
|
|
|
return HealthRawDailyStressPoint(
|
|
|
|
userId: userId,
|
|
|
|
date: date,
|
|
|
|
stressValue: stressValue,
|
|
|
|
stressScore: stressScore,
|
|
|
|
state: healthRawRealtimeStressState(stressScore),
|
|
|
|
dataTime: dataTime,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _dailyStressScore(List<double> sortedValues) {
|
|
|
|
final median = _median(sortedValues);
|
|
|
|
final p75 = _percentile75(sortedValues);
|
|
|
|
return _round(_clamp(median * 0.60 + p75 * 0.40, 1, 100));
|
|
|
|
}
|
|
|
|
|
|
|
|
static List<({int start, int end})> _confirmedActivityBuffers(
|
|
|
|
List<HealthRawRealtimeStressPoint> realtimePoints,
|
|
|
|
) {
|
|
|
|
final ranges = realtimePoints
|
|
|
|
.where((e) =>
|
|
|
|
_isValidStressValue(e.result) &&
|
|
|
|
(e.isWorkout || e.isWorkoutRecovery))
|
|
|
|
.map((e) => (
|
|
|
|
start: e.rawEndTime - _confirmedActivityBufferSeconds,
|
|
|
|
end: e.rawEndTime + _confirmedActivityBufferSeconds,
|
|
|
|
))
|
|
|
|
.toList()
|
|
|
|
..sort((a, b) => a.start.compareTo(b.start));
|
|
|
|
if (ranges.isEmpty) return const <({int start, int end})>[];
|
|
|
|
|
|
|
|
final merged = <({int start, int end})>[];
|
|
|
|
var current = ranges.first;
|
|
|
|
for (final range in ranges.skip(1)) {
|
|
|
|
if (range.start <= current.end) {
|
|
|
|
current = (
|
|
|
|
start: current.start,
|
|
|
|
end: math.max(current.end, range.end),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
merged.add(current);
|
|
|
|
current = range;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
merged.add(current);
|
|
|
|
return merged;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool _isInTimeRanges(int time, List<({int start, int end})> ranges) {
|
|
|
|
return ranges.any((e) => time >= e.start && time <= e.end);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool _isValidStressValue(num value) => value >= 1 && value <= 100;
|
|
|
|
|
|
|
|
static double _average(List<double> values) =>
|
|
|
|
values.reduce((a, b) => a + b) / values.length;
|
|
|
|
|
|
|
|
static double _median(List<double> sortedValues) {
|
|
|
|
final length = sortedValues.length;
|
|
|
|
final middle = length ~/ 2;
|
|
|
|
if (length.isOdd) return sortedValues[middle];
|
|
|
|
return (sortedValues[middle - 1] + sortedValues[middle]) / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _percentile75(List<double> sortedValues) {
|
|
|
|
final rank = (sortedValues.length * 0.75).ceil().clamp(
|
|
|
|
1,
|
|
|
|
sortedValues.length,
|
|
|
|
);
|
|
|
|
return sortedValues[rank - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _clamp(num value, num min, num max) =>
|
|
|
|
math.max(min.toDouble(), math.min(max.toDouble(), value.toDouble()));
|
|
|
|
|
|
|
|
static double _round(double value) => (value * 10).round() / 10;
|
|
|
|
|
|
|
|
static double _integerDouble(num value) => value.toInt().toDouble();
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
isWorkoutRecovery: (row['is_workout_recovery'] as int? ?? 0) == 1,
|
|
|
|
isSleepLikely: (row['is_sleep_likely'] as int? ?? 0) == 1,
|
|
|
|
isSuspectedActivity: (row['is_suspected_activity'] as int? ?? 0) == 1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
double? _positiveDouble(Object? value) {
|
|
|
|
final result = (value as num?)?.toDouble();
|
|
|
|
if (result == null || result <= 0) return null;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
double _integerDouble(num value) => value.toInt().toDouble();
|
|
|
|
|
|
|
|
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,
|
|
...
|
...
|
@@ -2111,6 +1658,8 @@ class HealthRawStressDbRows { |
|
|
|
final List<Map<String, Object?>> sleepRows;
|
|
|
|
}
|
|
|
|
|
|
|
|
double _integerDouble(num value) => value.toInt().toDouble();
|
|
|
|
|
|
|
|
class HealthRawStressLocalStore {
|
|
|
|
HealthRawStressLocalStore({
|
|
|
|
Directory? rootDirectory,
|
...
|
...
|
|