feat: Initial iOS Health Dashboard app (Swift + SwiftUI)

This commit is contained in:
Cosmo
2026-03-25 10:38:58 +00:00
commit 7cda5deaab
14 changed files with 443 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import SwiftUI
struct DashboardView: View {
@EnvironmentObject var authManager: AuthManager
@StateObject private var healthKit = HealthKitService()
@State private var readiness: ReadinessResponse?
@State private var latest: LatestHealthResponse?
@State private var isLoading = true
var body: some View {
ZStack {
LinearGradient(colors: [Color(hex: "1a1a2e"), Color(hex: "16213e")], startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 20) {
HStack {
Text("Привет, \(authManager.userName) 👋").font(.title2.bold()).foregroundColor(.white)
Spacer()
Button(action: { authManager.logout() }) {
Image(systemName: "rectangle.portrait.and.arrow.right").foregroundColor(.white.opacity(0.5))
}
}.padding(.horizontal).padding(.top)
if isLoading {
ProgressView().tint(Color(hex: "00d4aa")).padding(.top, 60)
} else {
if let r = readiness { ReadinessCardView(readiness: r) }
if let l = latest {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
if let sleep = l.sleep {
MetricCardView(icon: "moon.fill", title: "Сон",
value: String(format: "%.1f ч", sleep.totalSleep ?? 0),
subtitle: "Глубокий: \(String(format: "%.0f мин", (sleep.deep ?? 0) * 60))",
color: Color(hex: "6c63ff"))
}
if let rhr = l.restingHeartRate {
MetricCardView(icon: "heart.fill", title: "Пульс покоя",
value: "\(Int(rhr.value ?? 0)) уд/мин", subtitle: "Resting HR",
color: Color(hex: "ff6b6b"))
}
if let hrv = l.hrv {
MetricCardView(icon: "waveform.path.ecg", title: "HRV",
value: "\(Int(hrv.avg ?? 0)) мс", subtitle: "Вариабельность",
color: Color(hex: "00d4aa"))
}
if let steps = l.steps {
MetricCardView(icon: "figure.walk", title: "Шаги",
value: "\(steps.total ?? 0)", subtitle: "Сегодня",
color: Color(hex: "ffa500"))
}
}.padding(.horizontal)
}
}
Spacer(minLength: 20)
}
}
}
.task { await loadData() }
}
func loadData() async {
isLoading = true
async let r = APIService.shared.getReadiness(token: authManager.token)
async let l = APIService.shared.getLatest(token: authManager.token)
readiness = try? await r
latest = try? await l
isLoading = false
}
}