Files
Ai/HealthBridge/ViewModels/DashboardViewModel.swift
Claude b953908f58 Add HealthBridge iOS app for intelligent health data synchronization
Complete implementation of a SwiftUI iOS app that serves as a "Single Source
of Truth" for health data. The app reads from all Apple Health sources,
detects conflicts between devices, merges data using configurable strategies,
and writes cleaned data back.

Features:
- Phase 1: HealthKit integration with automatic source discovery
- Phase 2: DataReader with conflict detection (time-window based)
- Phase 3: RuleEngine with 8 merge strategies (exclusive, priority, higher wins, etc.)
- Phase 4: MergeEngine for conflict resolution + DataWriter for HealthKit writes
- Phase 5: SwiftUI UI for dashboard, conflicts, rules, and sources management
- Phase 6: Background sync with configurable intervals and push notifications
- Phase 7: Complete rule editor and polished UI components

Supported data types:
- Steps, Heart Rate, Blood Pressure, SpO2, Sleep
- Distance, Floors Climbed, Active Energy, HRV, Respiratory Rate

Architecture: SourceManager -> DataReader -> RuleEngine -> MergeEngine -> DataWriter
2025-12-25 16:59:48 +00:00

104 lines
2.9 KiB
Swift

import Foundation
import Combine
@MainActor
class DashboardViewModel: ObservableObject {
private let syncCoordinator = SyncCoordinator.shared
private let dataReader = DataReader.shared
private let sourceManager = SourceManager.shared
@Published var dailySummary: DailySummary?
@Published var isLoading = false
@Published var selectedDate = Date()
@Published var errorMessage: String?
private var cancellables = Set<AnyCancellable>()
init() {
setupBindings()
}
private func setupBindings() {
$selectedDate
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.sink { [weak self] date in
Task {
await self?.loadData(for: date)
}
}
.store(in: &cancellables)
}
func loadData(for date: Date = Date()) async {
isLoading = true
errorMessage = nil
do {
dailySummary = try await dataReader.fetchDailySummary(for: date)
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
func performSync() async {
do {
try await syncCoordinator.performSync(for: selectedDate)
await loadData(for: selectedDate)
} catch {
errorMessage = "Sync fehlgeschlagen: \(error.localizedDescription)"
}
}
func refresh() async {
await sourceManager.discoverSources()
await loadData(for: selectedDate)
}
var syncStatus: SyncStatus {
if syncCoordinator.isSyncing {
return .syncing
} else if let lastSync = syncCoordinator.lastSyncDate {
let hoursSinceSync = Date().timeIntervalSince(lastSync) / 3600
if hoursSinceSync < 1 {
return .synced
} else if hoursSinceSync < 24 {
return .stale
} else {
return .veryStale
}
} else {
return .neverSynced
}
}
enum SyncStatus {
case syncing
case synced
case stale
case veryStale
case neverSynced
var description: String {
switch self {
case .syncing: return "Synchronisiere..."
case .synced: return "Synchronisiert"
case .stale: return "Sync empfohlen"
case .veryStale: return "Sync überfällig"
case .neverSynced: return "Nie synchronisiert"
}
}
var icon: String {
switch self {
case .syncing: return "arrow.triangle.2.circlepath"
case .synced: return "checkmark.circle.fill"
case .stale: return "exclamationmark.circle"
case .veryStale: return "exclamationmark.triangle"
case .neverSynced: return "xmark.circle"
}
}
}
}