1 contributor
165 lines | 5.816kb
import SwiftUI
import SwiftData
import HealthKit
import UIKit

struct DashboardView: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(AppSettings.self) private var appSettings
    @Query(sort: \HealthSnapshot.timestamp, order: .reverse) private var snapshots: [HealthSnapshot]
    @State private var viewModel = DashboardViewModel()

    init() {
        let id = UIDevice.current.identifierForVendor?.uuidString ?? ""
        _snapshots = Query(
            filter: #Predicate<HealthSnapshot> { $0.deviceID == id },
            sort: \HealthSnapshot.timestamp,
            order: .reverse
        )
    }

    private var latest: HealthSnapshot?   { snapshots.first }
    private var previous: HealthSnapshot? { snapshots.dropFirst().first }

    var body: some View {
        NavigationStack {
            List {
                statusSection
                anomalySummarySection
                actionsSection
                if let msg = viewModel.authError ?? viewModel.snapshotError {
                    Section {
                        Label(msg, systemImage: "exclamationmark.circle")
                            .foregroundStyle(Color.criticalRed)
                            .font(.caption)
                    }
                }
            }
            .navigationTitle("HealthProbe")
        }
        .sheet(isPresented: $viewModel.showProgressSheet) {
            SnapshotProgressSheet(viewModel: viewModel)
        }
    }

    // MARK: - Sections

    private var statusSection: some View {
        Section("Status") {
            if let latest {
                InfoRow(label: "Last Snapshot") {
                    Text(latest.timestamp, style: .relative)
                        .foregroundStyle(.secondary)
                }
                InfoRow(label: "Device") {
                    Text(latest.deviceName)
                        .foregroundStyle(.secondary)
                }
                if latest.snapshotQuality != SnapshotQuality.complete {
                    Label("Incomplete snapshot", systemImage: "exclamationmark.triangle")
                        .font(.caption)
                        .foregroundStyle(Color.warningAmber)
                }
            } else {
                Label("No snapshots yet", systemImage: "camera.viewfinder")
                    .foregroundStyle(.secondary)
            }

            InfoRow(label: "Monitored Types") {
                Text("\(appSettings.selectedTypeIDs.count)")
                    .foregroundStyle(.secondary)
            }

            if let latest, let previous {
                let delta = viewModel.totalChanges(latest: latest, previous: previous)
                InfoRow(label: "Changes vs Previous") {
                    Text(delta == 0 ? "None" : "\(delta) records")
                        .foregroundStyle(delta == 0 ? Color.healthyGreen : Color.warningAmber)
                }
            }
        }
    }

    @ViewBuilder
    private var anomalySummarySection: some View {
        AnomalySummarySection()
    }

    private var actionsSection: some View {
        Section("Actions") {
            Button {
                Task { await viewModel.requestAuthorization() }
            } label: {
                HStack {
                    Label("Request Health Access", systemImage: "heart.text.square")
                    Spacer()
                    if viewModel.isRequestingAuth { ProgressView() }
                }
            }
            .disabled(viewModel.isRequestingAuth)
            .accessibilityLabel("Request HealthKit read authorization")

            Button {
                Task {
                    await viewModel.createSnapshot(
                        context: modelContext,
                        selectedTypeIDs: appSettings.selectedTypeIDs
                    )
                }
            } label: {
                HStack {
                    Label("Create Snapshot", systemImage: "camera.viewfinder")
                    Spacer()
                    if viewModel.isCreatingSnapshot { ProgressView() }
                }
            }
            .disabled(viewModel.isCreatingSnapshot)
            .accessibilityLabel("Create a new data snapshot")
        }
    }
}

// Avoids LabeledContent's TableRowContent ambiguity in List/Section contexts.
private struct InfoRow<Content: View>: View {
    let label: String
    @ViewBuilder let content: () -> Content

    var body: some View {
        HStack {
            Text(label)
            Spacer()
            content()
        }
    }
}

private struct AnomalySummarySection: View {
    @Query(filter: #Predicate<AnomalyRecord> { !$0.isResolved })
    private var unresolved: [AnomalyRecord]

    private var criticalCount: Int { unresolved.filter { $0.severityRaw == Severity.critical.rawValue }.count }
    private var warningCount:  Int { unresolved.filter { $0.severityRaw == Severity.warning.rawValue }.count }

    var body: some View {
        if !unresolved.isEmpty {
            Section("Anomalies") {
                if criticalCount > 0 {
                    Label("\(criticalCount) critical \(criticalCount == 1 ? "anomaly" : "anomalies")",
                          systemImage: "exclamationmark.circle.fill")
                        .foregroundStyle(Color.criticalRed)
                }
                if warningCount > 0 {
                    Label("\(warningCount) \(warningCount == 1 ? "warning" : "warnings")",
                          systemImage: "exclamationmark.triangle.fill")
                        .foregroundStyle(Color.warningAmber)
                }
            }
        }
    }
}

#Preview {
    DashboardView()
        .modelContainer(for: [HealthSnapshot.self, TypeCount.self, YearlyCount.self], inMemory: true)
        .environment(AppSettings())
}