import SwiftUI import SwiftData // Module-level so GoalsTabView (same module) can reuse this formatter. extension TimeInterval { var hhmmss: String { let s = Int(self) return String(format: "%02d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60) } } struct MainTabView: View { @Query(sort: \TaskItem.sortOrder) private var tasks: [TaskItem] @State private var engine = TrackingEngine() private var activeTasks: [TaskItem] { tasks.filter { !$0.isArchived } } var body: some View { NavigationStack { ScrollView { VStack(spacing: 20) { SummaryCardsSection() if !activeTasks.isEmpty { TaskListSection(tasks: activeTasks) } LayoutEditEntryBanner() } .padding() } .background(Color.tfBackground) .navigationTitle(String(localized: "tab.main")) } .environment(engine) } } // MARK: – Summary cards private struct SummaryCardsSection: View { var body: some View { VStack(spacing: 12) { SummaryCard(period: String(localized: "main.period.today"), icon: "sun.max.fill") SummaryCard(period: String(localized: "main.period.week"), icon: "calendar.badge.clock") SummaryCard(period: String(localized: "main.period.month"), icon: "calendar") } } } private struct SummaryCard: View { let period: String let icon: String var body: some View { HStack(spacing: 12) { Image(systemName: icon) .font(.title3) .foregroundStyle(Color.tfPrimary) .frame(width: 28) Text(period) .font(.headline) .foregroundStyle(Color.tfOnBackground) Spacer() Text("—") .font(.subheadline) .foregroundStyle(Color.tfMuted) } .padding() .background(Color.tfSurface) .clipShape(RoundedRectangle(cornerRadius: 12)) .shadow(color: Color.tfSeparator, radius: 2, x: 0, y: 1) } } // MARK: – Task list private struct TaskListSection: View { let tasks: [TaskItem] var body: some View { VStack(alignment: .leading, spacing: 10) { Text("작업") .font(.footnote.weight(.semibold)) .foregroundStyle(Color.tfMuted) .textCase(.uppercase) ForEach(tasks) { task in TaskActionCard(task: task) } } } } private struct TaskActionCard: View { @Environment(\.modelContext) private var context @Environment(TrackingEngine.self) private var engine let task: TaskItem @State private var counterPulse = false private var primaryColor: Color { task.tags.first.map { Color(hex: $0.colorHex) } ?? Color.tfPrimary } var body: some View { HStack(spacing: 12) { Image(systemName: task.icon) .font(.title3) .foregroundStyle(primaryColor) .frame(width: 28) VStack(alignment: .leading, spacing: 2) { Text(task.name) .font(.subheadline.weight(.semibold)) .foregroundStyle(Color.tfOnBackground) Text(statusLabel) .font(.caption.monospacedDigit()) .foregroundStyle(engine.isRunning(task) ? primaryColor : Color.tfMuted) .contentTransition(.numericText()) .animation(.linear(duration: 0.2), value: statusLabel) } Spacer() actionButton } .padding() .background(Color.tfSurface) .clipShape(RoundedRectangle(cornerRadius: 12)) .shadow(color: Color.tfSeparator, radius: 2, x: 0, y: 1) } private var statusLabel: String { switch task.taskType { case .timer: return engine.isRunning(task) ? engine.elapsed(for: task).hhmmss : String(localized: "task.type.timer") case .counter: return String(localized: "task.type.counter") } } @ViewBuilder private var actionButton: some View { switch task.taskType { case .timer: let running = engine.isRunning(task) Button { toggleTimer() } label: { Image(systemName: running ? "stop.fill" : "play.fill") .font(.title3) .foregroundStyle(.white) .frame(width: 44, height: 44) .background(running ? Color.red : primaryColor) .clipShape(Circle()) } .buttonStyle(.plain) case .counter: Button { logCount() } label: { Image(systemName: "plus") .font(.title3) .foregroundStyle(.white) .frame(width: 44, height: 44) .background(primaryColor) .clipShape(Circle()) .scaleEffect(counterPulse ? 1.3 : 1.0) } .buttonStyle(.plain) } } private func toggleTimer() { if engine.isRunning(task) { engine.stopTimer(for: task, context: context) } else { engine.startTimer(for: task, context: context) } } private func logCount() { engine.incrementCounter(for: task, context: context) withAnimation(.spring(duration: 0.15, bounce: 0.6)) { counterPulse = true } Task { try? await Task.sleep(for: .milliseconds(350)) withAnimation(.spring(duration: 0.2)) { counterPulse = false } } } } // MARK: – Layout-edit mode entry private struct LayoutEditEntryBanner: View { var body: some View { HStack(spacing: 10) { Image(systemName: "square.grid.2x2") .foregroundStyle(Color.tfAccent) Text(String(localized: "main.layout_edit.placeholder")) .font(.footnote) .foregroundStyle(Color.tfMuted) Spacer() Image(systemName: "chevron.right") .font(.footnote.weight(.semibold)) .foregroundStyle(Color.tfMuted) } .padding() .background(Color.tfSurface) .clipShape(RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.tfSeparator, lineWidth: 1) ) } } #Preview { MainTabView() .modelContainer(for: [TaskItem.self, TagEntity.self, TrackingRecord.self], inMemory: true) .environment(AppPreferences()) }