import AppTypes import ComposableArchitecture import Dao import Dependencies import Foundation import SQLiteData @Reducer public struct AlertLoaderFeature: Sendable { @Dependency(\.continuousClock) var clock @Dependency(\.date) var date @Dependency(\.calendar) var calendar static let refreshInterval = Duration.seconds(5) @ObservableState public struct State: Equatable { public var lateTrackeeNames: [String] = [] public var dayOfWeek: String = "" @FetchAll(ReminderTime.none) var allReminders: [ReminderTime] @FetchAll(Trackee.none) var allTrackees: [Trackee] public init() { self._allReminders = FetchAll(ReminderTime.all) self._allTrackees = FetchAll(Trackee.all) } } public enum Action: Equatable { case startMonitoring case tick } enum CancelID { case alertLoop } public init() {} public var body: some Reducer { Reduce { state, action in switch action { case .startMonitoring: return .run { send in await send(.tick) for await _ in self.clock.timer(interval: Self.refreshInterval) { await send(.tick) } } .cancellable(id: CancelID.alertLoop, cancelInFlight: true) case .tick: let now = date.now let cal = calendar let lateTrackeeIds = Set( state.allReminders .filter { $0.isLate(date: now, calendar: cal) } .map { $0.trackeeId } ) // A soft-disabled trackee keeps its reminder rows but never nags. state.lateTrackeeNames = state.allTrackees .filter { $0.remindersEnabled && lateTrackeeIds.contains($0.id) } .map { $0.name } state.dayOfWeek = cal.weekdaySymbols[cal.component(.weekday, from: now) - 1] return .none } } } }