import SwiftUI import SwiftData struct DashboardView: View { @Environment(\.modelContext) private var modelContext @State private var viewModel = DashboardViewModel() var body: some View { ZStack { Theme.background.ignoresSafeArea() ScrollView { VStack(spacing: 20) { progressRing if !viewModel.topGoalProgress.isEmpty { topGoals } remainingTasksSection } .padding() } } .navigationTitle(String(localized: "tab.dashboard")) .onAppear { viewModel.setup(context: modelContext) } } // MARK: - Progress Ring private var progressRing: some View { VStack(spacing: 12) { ZStack { Circle() .stroke(Theme.surface, lineWidth: 16) Circle() .trim(from: 0, to: viewModel.overallProgress) .stroke(Theme.green, style: StrokeStyle(lineWidth: 16, lineCap: .round)) .rotationEffect(.degrees(-90)) .animation(.easeInOut(duration: 0.4), value: viewModel.overallProgress) VStack(spacing: 4) { Text("\(Int(viewModel.overallProgress * 100))%") .font(.system(size: 34, weight: .bold, design: .rounded)) .foregroundStyle(Theme.primaryText) Text(String(localized: "dashboard.progress.title")) .font(.caption) .foregroundStyle(Theme.primaryText.opacity(0.6)) } } .frame(width: 180, height: 180) .padding(.top, 8) } } // MARK: - Top Goals private var topGoals: some View { VStack(alignment: .leading, spacing: 10) { Text(String(localized: "dashboard.goals.title")) .font(.headline) .foregroundStyle(Theme.primaryText) VStack(spacing: 10) { ForEach(viewModel.topGoalProgress) { progress in TopGoalCard(progress: progress) } } } .frame(maxWidth: .infinity, alignment: .leading) } // MARK: - Remaining Tasks private var remainingTasksSection: some View { VStack(alignment: .leading, spacing: 10) { Text(String(localized: "dashboard.tasks.title")) .font(.headline) .foregroundStyle(Theme.primaryText) if viewModel.remainingTasks.isEmpty { Text(String(localized: "dashboard.tasks.empty")) .font(.subheadline) .foregroundStyle(Theme.primaryText.opacity(0.5)) .frame(maxWidth: .infinity, alignment: .center) .padding(.vertical, 24) } else { VStack(spacing: 8) { ForEach(viewModel.remainingTasks) { task in RemainingTaskRow(task: task) { viewModel.addCount(to: task) } } } } } .frame(maxWidth: .infinity, alignment: .leading) } } // MARK: - Top Goal Card private struct TopGoalCard: View { let progress: GoalProgress var body: some View { HStack(spacing: 12) { VStack(alignment: .leading, spacing: 2) { Text(progress.displayName) .font(.body) .fontWeight(.semibold) .foregroundStyle(Theme.primaryText) ProgressView(value: progress.ratio) .tint(progress.isCompleted ? Theme.green : Theme.yellow) } Text(progress.percentText) .font(.caption) .fontWeight(.bold) .foregroundStyle(progress.isCompleted ? Theme.green : Theme.yellow) } .padding() .background(Theme.surface, in: RoundedRectangle(cornerRadius: 12)) } } // MARK: - Remaining Task Row private struct RemainingTaskRow: View { let task: TaskItem let onAddCount: () -> Void var body: some View { HStack(spacing: 12) { Text(task.icon) .font(.title3) .frame(width: 30) Text(task.name) .font(.body) .foregroundStyle(Theme.primaryText) Spacer() if task.type == .count { Button(action: onAddCount) { Image(systemName: "plus.circle.fill") .foregroundStyle(Theme.green) } .buttonStyle(.plain) } else { Image(systemName: "clock") .foregroundStyle(Theme.primaryText.opacity(0.4)) } } .padding(12) .background(Theme.surface, in: RoundedRectangle(cornerRadius: 10)) } } #Preview { NavigationStack { DashboardView() } }