import SwiftUI struct ReadinessCardView: View { let readiness: ReadinessResponse @State private var animatedScore: CGFloat = 0 @State private var appeared = false var statusColor: Color { if readiness.score >= 80 { return Color(hex: "00d4aa") } if readiness.score >= 60 { return Color(hex: "ffa502") } return Color(hex: "ff4757") } var statusText: String { if readiness.score >= 80 { return "Отличная готовность 💪" } if readiness.score >= 60 { return "Умеренная активность 🚶" } return "День отдыха 😴" } var body: some View { VStack(spacing: 20) { // Score Ring ZStack { // Background ring Circle() .stroke(Color.white.opacity(0.08), lineWidth: 14) .frame(width: 150, height: 150) // Animated ring Circle() .trim(from: 0, to: animatedScore / 100) .stroke( AngularGradient( colors: [statusColor.opacity(0.5), statusColor, statusColor.opacity(0.8)], center: .center, startAngle: .degrees(0), endAngle: .degrees(360) ), style: StrokeStyle(lineWidth: 14, lineCap: .round) ) .frame(width: 150, height: 150) .rotationEffect(.degrees(-90)) // Score text VStack(spacing: 2) { Text("\(readiness.score)") .font(.system(size: 48, weight: .bold, design: .rounded)) .foregroundColor(statusColor) Text("из 100") .font(.caption2) .foregroundColor(Color(hex: "8888aa")) } } // Status VStack(spacing: 6) { Text(statusText) .font(.title3.weight(.semibold)) .foregroundColor(.white) Text(readiness.recommendation) .font(.subheadline) .foregroundColor(Color(hex: "8888aa")) .multilineTextAlignment(.center) .lineLimit(3) .padding(.horizontal, 8) } // Factor bars if let f = readiness.factors { VStack(spacing: 10) { Divider().background(Color.white.opacity(0.1)) FactorRow(name: "Сон", icon: "moon.fill", score: f.sleep.score, value: f.sleep.value, color: Color(hex: "7c3aed")) FactorRow(name: "HRV", icon: "waveform.path.ecg", score: f.hrv.score, value: f.hrv.value, color: Color(hex: "00d4aa")) FactorRow(name: "Пульс", icon: "heart.fill", score: f.rhr.score, value: f.rhr.value, color: Color(hex: "ff4757")) FactorRow(name: "Активность", icon: "flame.fill", score: f.activity.score, value: f.activity.value, color: Color(hex: "ffa502")) } } } .padding(24) .background( RoundedRectangle(cornerRadius: 20) .fill(.ultraThinMaterial) .overlay( RoundedRectangle(cornerRadius: 20) .fill(Color(hex: "12122a").opacity(0.7)) ) ) .shadow(color: .black.opacity(0.2), radius: 10, y: 5) .padding(.horizontal) .onAppear { withAnimation(.easeOut(duration: 1.2)) { animatedScore = CGFloat(readiness.score) } } .opacity(appeared ? 1 : 0) .offset(y: appeared ? 0 : 20) .onAppear { withAnimation(.easeOut(duration: 0.5).delay(0.1)) { appeared = true } } } } // MARK: - Factor Row struct FactorRow: View { let name: String let icon: String let score: Int let value: String let color: Color var body: some View { HStack(spacing: 10) { Image(systemName: icon) .font(.caption) .foregroundColor(color) .frame(width: 20) Text(name) .font(.caption.weight(.medium)) .foregroundColor(Color(hex: "8888aa")) .frame(width: 75, alignment: .leading) GeometryReader { geo in ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 3) .fill(Color.white.opacity(0.08)) RoundedRectangle(cornerRadius: 3) .fill( LinearGradient( colors: [color.opacity(0.7), color], startPoint: .leading, endPoint: .trailing ) ) .frame(width: geo.size.width * CGFloat(score) / 100) } } .frame(height: 6) Text(value) .font(.caption) .foregroundColor(.white.opacity(0.7)) .frame(width: 55, alignment: .trailing) } } }