glp-companion/lib/core/platform/health_gateway.dart

115 lines
4.2 KiB
Dart

import 'health_gateway_fallback.dart'
if (dart.library.io) 'health_gateway_mobile.dart' as impl;
/// Native health ecosystem backing the current device.
///
/// Kept in `core` (not the health_sync feature) so the platform layer never
/// imports feature code; the biometric controller maps this to the domain's
/// `BiometricSource` enum.
enum NativeHealthPlatform {
/// Apple HealthKit (iOS).
appleHealth,
/// Google Health Connect (Android).
healthConnect,
}
/// Scalar sample returned by the health gateway.
///
/// Pure value type: [unit] is a display string (`lb`, `kcal`) — unit
/// normalization (kg → lb, joules → kcal) happens inside the gateway so
/// consumers only ever deal in pounds and Calories.
class HealthMetric {
const HealthMetric({
required this.timestamp,
required this.value,
required this.unit,
});
/// When the sample was recorded.
final DateTime timestamp;
/// Normalized magnitude (pounds for weight, kilocalories for energy).
final double value;
/// Unit of [value] for display (e.g. `lb`, `kcal`).
final String unit;
}
/// Read-only access to biometrics that were logged locally on this device.
///
/// Implemented by the health_sync feature over the encrypted drift cache and
/// injected into the fallback gateway so Web/Windows/macOS builds serve
/// locally logged data instead of returning nothing. Declared in `core` to
/// keep the platform layer free of feature imports (architecture rule 2);
/// the dependency points inward: features implement core contracts.
abstract class ILocalBiometricReader {
/// Weight samples in pounds for `[start, end)`, newest first.
Future<List<HealthMetric>> readWeightRange({
required DateTime start,
required DateTime end,
});
/// Active energy burned (kcal) samples for `[start, end)`, newest first.
Future<List<HealthMetric>> readEnergyRange({
required DateTime start,
required DateTime end,
});
}
/// Cross-platform boundary over the OS health ecosystems (architecture rule
/// 2/3: platform-specific packages never leak past this interface).
///
/// * **Android / iOS:** implemented by `health_gateway_mobile.dart`, which
/// wraps the `health` package (Health Connect / HealthKit) behind
/// `kIsWeb` / `defaultTargetPlatform` guards.
/// * **Windows / macOS / Web:** implemented by `health_gateway_fallback.dart`,
/// a manual-entry mode that never touches platform channels; reads are
/// served from the drift-backed cache through an injected
/// [ILocalBiometricReader].
abstract class IHealthGateway {
/// Whether this device can read/write native health data. `false` on
/// Web/Windows/macOS, which signals the UI to offer manual entry.
bool get supportsNativeHealth;
/// Ecosystem backing [supportsNativeHealth], or `null` in fallback mode.
NativeHealthPlatform? get nativePlatform;
/// Requests read permission for weight and active energy; returns whether
/// access was granted. Always `false` in fallback mode.
Future<bool> requestPermissions();
/// Weight samples in pounds for `[start, end)`, newest first.
///
/// Returns an empty list (never throws) in fallback mode or when the OS
/// denies access.
Future<List<HealthMetric>> fetchWeightHistory({
required DateTime start,
required DateTime end,
});
/// Active energy burned (kcal) samples for `[start, end)`, newest first.
///
/// Returns an empty list (never throws) in fallback mode or when the OS
/// denies access.
Future<List<HealthMetric>> fetchActiveEnergyBurned({
required DateTime start,
required DateTime end,
});
/// Writes a weight measurement back to the native store; returns whether
/// the platform accepted it (always `false` in fallback mode).
Future<bool> writeWeight({
required double weightLbs,
required DateTime timestamp,
});
}
/// Platform-appropriate factory: native `health`-backed gateway on
/// Android/iOS, manual-entry fallback on Web/Windows/macOS.
///
/// [localReader] backs fallback reads with the locally logged biometric
/// cache; mobile platforms ignore it and query the native store directly.
IHealthGateway createHealthGateway({ILocalBiometricReader? localReader}) =>
impl.makeHealthGateway(localReader: localReader);