FRE-4665: Implement Phase 3 AI training plans and premium features
- Models: TrainingPlan, Race, FamilyPlan, BeginnerMode, CommunityEvent - Services: 5 service layers with protocol-based architecture - ViewModels: 5 view models with @MainActor ObservableObject pattern - Views: 10 SwiftUI views for all Phase 3 features - Updated README with full Phase 3 documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
125
Lendair/Views/FamilyMemberView.swift
Normal file
125
Lendair/Views/FamilyMemberView.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
import SwiftUI
|
||||
|
||||
struct FamilyMemberView: View {
|
||||
let member: FamilyMember
|
||||
@State private var weeklyData: [(day: String, distance: Double)] = []
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
memberHeader
|
||||
statsSection
|
||||
weeklyActivitySection
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
.navigationTitle(member.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
private var memberHeader: some View {
|
||||
VStack(spacing: 16) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(member.isPrimary ? Color.blue.opacity(0.2) : Color.green.opacity(0.2))
|
||||
.frame(width: 80, height: 80)
|
||||
Image(systemName: member.role.icon)
|
||||
.font(.system(size: 36))
|
||||
.foregroundColor(member.isPrimary ? .blue : .green)
|
||||
}
|
||||
|
||||
VStack(spacing: 4) {
|
||||
Text(member.name)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
Text(member.role.displayName)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.secondary.opacity(0.1))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
|
||||
private var statsSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Statistics")
|
||||
.font(.headline)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
statCard(value: "\(Int(member.totalDistanceKm))", label: "Total km", icon: "figure.run")
|
||||
statCard(value: "\(member.totalWorkouts)", label: "Workouts", icon: "checkmark.circle")
|
||||
statCard(value: "\(Int(member.weeklyDistanceKm))", label: "This Week", icon: "chart.bar.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func statCard(value: String, label: String, icon: String) -> some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(.blue)
|
||||
Text(value)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.secondary.opacity(0.08))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
private var weeklyActivitySection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Weekly Activity")
|
||||
.font(.headline)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
ForEach(DayOfWeek.allCases, id: \.self) { day in
|
||||
HStack {
|
||||
Text(day.displayName)
|
||||
.font(.subheadline)
|
||||
.frame(width: 36)
|
||||
GeometryReader { geo in
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.blue.opacity(0.6))
|
||||
.frame(width: min(geo.size.width, 200), height: 24)
|
||||
}
|
||||
.frame(height: 24)
|
||||
Text("\(Int(member.weeklyDistanceKm / 7)) km")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
FamilyMemberView(member: sampleMember)
|
||||
}
|
||||
}
|
||||
|
||||
private var sampleMember: FamilyMember {
|
||||
FamilyMember(
|
||||
id: "1",
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
role: .owner,
|
||||
joinedAt: Date().addingTimeInterval(-30 * 24 * 3600),
|
||||
avatarUrl: nil,
|
||||
isPrimary: true,
|
||||
totalDistanceKm: 245.5,
|
||||
totalWorkouts: 42,
|
||||
weeklyDistanceKm: 32.0,
|
||||
weeklyWorkouts: 5
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user