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
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user