// ── Main Content View with Tab Navigation ───────────────────── // iPhone: TabView, iPad: NavigationSplitView with sidebar import SwiftUI struct ContentView: View { @EnvironmentObject var timerStore: TimerStore @Environment(\.horizontalSizeClass) private var sizeClass @State private var selectedTab: Tab = .timeline enum Tab: String, CaseIterable, Identifiable { case timeline = "Timeline" case focus = "Focus" case history = "History" case settings = "Settings" var id: String { rawValue } var icon: String { switch self { case .timeline: return "clock.fill" case .focus: return "target" case .history: return "chart.bar.fill" case .settings: return "gearshape.fill" } } } var body: some View { Group { if sizeClass == .regular { iPadLayout } else { iPhoneLayout } } .tint(CMColors.accent) } // MARK: - iPhone Layout (TabView) private var iPhoneLayout: some View { TabView(selection: $selectedTab) { TimelineView() .tabItem { Label(Tab.timeline.rawValue, systemImage: Tab.timeline.icon) } .tag(Tab.timeline) if FeatureFlagService.shared.isEnabled("focus_mode_enabled") { PomodoroView() .tabItem { Label(Tab.focus.rawValue, systemImage: Tab.focus.icon) } .tag(Tab.focus) } HistoryView() .tabItem { Label(Tab.history.rawValue, systemImage: Tab.history.icon) } .tag(Tab.history) SettingsView() .tabItem { Label(Tab.settings.rawValue, systemImage: Tab.settings.icon) } .tag(Tab.settings) } } // MARK: - iPad Layout (NavigationSplitView) private var iPadLayout: some View { NavigationSplitView { // Sidebar VStack(spacing: 0) { ForEach(Tab.allCases.filter { tab in if tab == .focus { return FeatureFlagService.shared.isEnabled("focus_mode_enabled") } return true }) { tab in Button { selectedTab = tab } label: { Label(tab.rawValue, systemImage: tab.icon) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.vertical, 10) .background(selectedTab == tab ? CMColors.accent.opacity(0.15) : Color.clear) .cornerRadius(8) } .buttonStyle(.plain) } } .padding(.horizontal, 8) .navigationTitle("ChronoMind") .background(CMColors.bg) } detail: { // Detail pane switch selectedTab { case .timeline: iPadTimelineDetail case .focus: PomodoroView() case .history: HistoryView() case .settings: SettingsView() } } .navigationSplitViewStyle(.balanced) } // MARK: - iPad Timeline with Side Detail @State private var selectedTimerId: String? private var iPadTimelineDetail: some View { HStack(spacing: 0) { // Left: Timeline TimelineView() .frame(maxWidth: .infinity) // Right: Selected timer detail (if any) if let timerId = selectedTimerId, let timer = timerStore.getTimer(timerId) { Divider() .background(CMColors.border) iPadTimerDetail(timer: timer) .frame(width: 340) .transition(.move(edge: .trailing)) } } .animation(.easeInOut(duration: 0.25), value: selectedTimerId) } private func iPadTimerDetail(timer: CMTimer) -> some View { ScrollView { VStack(alignment: .leading, spacing: CMSpacing.lg) { // Header HStack { Text("Timer Detail") .font(CMFonts.body(size: 11, weight: .bold)) .foregroundStyle(CMColors.textMuted) .tracking(1.5) Spacer() Button { selectedTimerId = nil } label: { Image(systemName: "xmark.circle.fill") .foregroundStyle(CMColors.textMuted) } } // Timer info VStack(alignment: .leading, spacing: CMSpacing.sm) { HStack { Text(timer.label) .font(CMFonts.display(size: 22)) .foregroundStyle(CMColors.text) Spacer() UrgencyBadge(urgency: timer.urgency) } Text(timer.type.label) .font(CMFonts.body(size: 13)) .foregroundStyle(CMColors.textSecondary) } // Countdown CountdownRing( progress: { guard let duration = timer.duration, duration > 0 else { return 0 } let remaining = max(0, timer.targetTime.timeIntervalSince(timerStore.now)) return 1.0 - (remaining / duration) }(), urgency: timer.urgency, remainingSeconds: max(0, timer.targetTime.timeIntervalSince(timerStore.now)), totalSeconds: timer.duration ?? 0, size: 180 ) .frame(maxWidth: .infinity) // Cascade progress if !timer.warnings.isEmpty { VStack(alignment: .leading, spacing: CMSpacing.sm) { Text("CASCADE WARNINGS") .font(CMFonts.body(size: 11, weight: .bold)) .foregroundStyle(CMColors.textMuted) .tracking(1.5) CascadeProgressBar( fired: timer.warnings.filter(\.fired).count, total: timer.warnings.count, urgency: timer.urgency ) ForEach(timer.warnings) { warning in HStack { Image(systemName: warning.fired ? "checkmark.circle.fill" : "circle") .font(.caption) .foregroundStyle(warning.fired ? CMColors.success : CMColors.textMuted) Text("\(warning.minutesBefore)m before") .font(CMFonts.body(size: 13)) .foregroundStyle(warning.fired ? CMColors.text : CMColors.textSecondary) Spacer() Text(formatTime(warning.scheduledTime)) .font(CMFonts.mono(size: 12)) .foregroundStyle(CMColors.textMuted) } } } } // Pomodoro info if let pomState = timer.pomodoroState, let pomConfig = timer.pomodoroConfig { VStack(alignment: .leading, spacing: CMSpacing.sm) { Text("POMODORO") .font(CMFonts.body(size: 11, weight: .bold)) .foregroundStyle(CMColors.textMuted) .tracking(1.5) HStack { Text("Round \(pomState.currentRound)/\(pomConfig.rounds)") .font(CMFonts.body(size: 14, weight: .medium)) .foregroundStyle(CMColors.text) Spacer() Text(pomState.isBreak ? "Break" : "Work") .font(CMFonts.body(size: 13, weight: .semibold)) .foregroundStyle(pomState.isBreak ? CMColors.success : CMColors.accent) .padding(.horizontal, CMSpacing.md) .padding(.vertical, CMSpacing.xxs) .background((pomState.isBreak ? CMColors.success : CMColors.accent).opacity(0.15)) .clipShape(Capsule()) } } } // Actions HStack(spacing: CMSpacing.md) { if timer.state == .active || timer.state == .warning { Button { HapticEngine.tap() timerStore.pause(timer.id) } label: { Label("Pause", systemImage: "pause.fill") .font(CMFonts.body(size: 14, weight: .semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.sm) .background(CMColors.important) .clipShape(RoundedRectangle(cornerRadius: CMRadius.sm)) } } if timer.state == .paused { Button { HapticEngine.tap() timerStore.resume(timer.id) } label: { Label("Resume", systemImage: "play.fill") .font(CMFonts.body(size: 14, weight: .semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.sm) .background(CMColors.accent) .clipShape(RoundedRectangle(cornerRadius: CMRadius.sm)) } } if timer.state == .firing { Button { HapticEngine.tap() timerStore.dismiss(timer.id) selectedTimerId = nil } label: { Label("Dismiss", systemImage: "xmark") .font(CMFonts.body(size: 14, weight: .semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.sm) .background(CMColors.error) .clipShape(RoundedRectangle(cornerRadius: CMRadius.sm)) } } Button { HapticEngine.tap() timerStore.removeTimer(timer.id) selectedTimerId = nil } label: { Image(systemName: "trash") .font(.body.weight(.semibold)) .foregroundStyle(CMColors.error) .padding(CMSpacing.sm) .background(CMColors.error.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: CMRadius.sm)) } } // Metadata VStack(alignment: .leading, spacing: CMSpacing.xs) { Text("DETAILS") .font(CMFonts.body(size: 11, weight: .bold)) .foregroundStyle(CMColors.textMuted) .tracking(1.5) metadataRow("Created", value: formatDateTime(timer.createdAt)) metadataRow("Target", value: formatDateTime(timer.targetTime)) metadataRow("State", value: timer.state.rawValue) metadataRow("Snoozes", value: "\(timer.snoozeCount)") if let category = timer.category { metadataRow("Category", value: category) } } } .padding(CMSpacing.lg) } .background(CMColors.bg) } private func metadataRow(_ label: String, value: String) -> some View { HStack { Text(label) .font(CMFonts.body(size: 12)) .foregroundStyle(CMColors.textMuted) Spacer() Text(value) .font(CMFonts.mono(size: 12)) .foregroundStyle(CMColors.textSecondary) } } }