import SwiftUI import Bedrock struct TodayView: View { @Bindable var store: RitualStore @Bindable var categoryStore: CategoryStore @Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.scenePhase) private var scenePhase /// Rituals to show now based on current time of day. /// Depends on `store.currentTimeOfDay` which is observable. private var todayRituals: [Ritual] { // Access currentTimeOfDay to establish observation dependency _ = store.currentTimeOfDay return store.ritualsForToday() } /// Whether there are active rituals but none for the current time private var hasRitualsButNotNow: Bool { todayRituals.isEmpty && !store.currentRituals.isEmpty } /// Whether to show the renewal sheet private var showRenewalSheet: Bool { store.ritualNeedingRenewal != nil } /// Whether to use wide layout on iPad/landscape private var useWideLayout: Bool { horizontalSizeClass == .regular } /// Grid columns for ritual sections - 2 columns on regular width when multiple rituals private var ritualColumns: [GridItem] { AdaptiveColumns.columns( compactCount: 1, regularCount: todayRituals.count > 1 ? 2 : 1, spacing: Design.Spacing.large, horizontalSizeClass: horizontalSizeClass ) } var body: some View { ScrollView(.vertical, showsIndicators: false) { VStack(alignment: .leading, spacing: Design.Spacing.large) { if todayRituals.isEmpty { if hasRitualsButNotNow { // Has active rituals but none for current time of day TodayNoRitualsForTimeView(store: store) } else { // No active rituals at all TodayEmptyStateView(store: store, categoryStore: categoryStore) } } else { // Use 2-column grid on iPad/landscape when multiple rituals LazyVGrid(columns: ritualColumns, alignment: .leading, spacing: Design.Spacing.large) { ForEach(todayRituals) { ritual in TodayRitualSectionView( focusTitle: ritual.title, focusTheme: ritual.theme, dayLabel: store.ritualDayLabel(for: ritual), completionSummary: store.completionSummary(for: ritual), progress: store.ritualProgress(for: ritual), habitRows: habitRows(for: ritual), iconName: ritual.iconName, timeOfDay: ritual.timeOfDay ) .frame(maxHeight: .infinity, alignment: .top) } } } } .padding(Design.Spacing.large) .adaptiveContentWidth() } .background(LinearGradient( colors: [AppSurface.primary, AppSurface.secondary], startPoint: .topLeading, endPoint: .bottomTrailing )) .refreshable { store.refresh() } .navigationTitle(String(localized: "Today")) .navigationBarTitleDisplayMode(horizontalSizeClass == .regular ? .inline : .large) .sheet(isPresented: .init( get: { showRenewalSheet }, set: { if !$0 { store.dismissRenewalPrompt() } } )) { if let ritual = store.ritualNeedingRenewal { ArcRenewalSheet(store: store, categoryStore: categoryStore, ritual: ritual) } } .onAppear { store.updateCurrentTimeOfDay() store.refreshIfNeeded() } .onChange(of: scenePhase) { _, newPhase in // Check for time-of-day changes when app becomes active if newPhase == .active { if store.updateCurrentTimeOfDay() { store.refresh() } } } } private func habitRows(for ritual: Ritual) -> [HabitRowModel] { store.habits(for: ritual).map { habit in HabitRowModel( id: habit.id, title: habit.title, symbolName: habit.symbolName, isCompleted: store.isHabitCompletedToday(habit), action: { store.toggleHabitCompletion(habit) } ) } } } #Preview { TodayView(store: RitualStore.preview, categoryStore: CategoryStore.preview) }