// // CalendarManager.swift // DynamicIsland // // Created by Harsh Vardhan Goswami on 08/09/24. // import Defaults import EventKit import SwiftUI // MARK: - CalendarManager @MainActor class CalendarManager: ObservableObject { static let shared = CalendarManager() @Published var currentWeekStartDate: Date @Published var events: [EventModel] = [] @Published var allCalendars: [CalendarModel] = [] @Published var eventCalendars: [CalendarModel] = [] @Published var reminderLists: [CalendarModel] = [] @Published var selectedCalendarIDs: Set = [] @Published var calendarAuthorizationStatus: EKAuthorizationStatus = .notDetermined @Published var reminderAuthorizationStatus: EKAuthorizationStatus = .notDetermined private var selectedCalendars: [CalendarModel] = [] private let calendarService = CalendarService() private var lastEventsFetchDate: Date? private let reloadRefreshInterval: TimeInterval = 15 private var eventStoreChangedObserver: NSObjectProtocol? var hasCalendarAccess: Bool { isAuthorized(calendarAuthorizationStatus) } var hasReminderAccess: Bool { isAuthorized(reminderAuthorizationStatus) } private init() { currentWeekStartDate = CalendarManager.startOfDay(Date()) setupEventStoreChangedObserver() Task { await reloadCalendarAndReminderLists() } } deinit { if let observer = eventStoreChangedObserver { NotificationCenter.default.removeObserver(observer) } } private func setupEventStoreChangedObserver() { eventStoreChangedObserver = NotificationCenter.default.addObserver( forName: .EKEventStoreChanged, object: nil, queue: .main ) { [weak self] _ in Task { guard let self else { return } await self.reloadCalendarAndReminderLists() await self.maybeRefreshEventsAfterReload() } } } @MainActor func reloadCalendarAndReminderLists() async { let allCalendars = await calendarService.calendars() eventCalendars = allCalendars.filter { !$0.isReminder } reminderLists = allCalendars.filter { $0.isReminder } self.allCalendars = allCalendars updateSelectedCalendars() } @MainActor private func maybeRefreshEventsAfterReload() async { guard hasCalendarAccess else { return } let now = Date() if let lastFetch = lastEventsFetchDate, now.timeIntervalSince(lastFetch) < reloadRefreshInterval { return } await updateEvents() } private func isAuthorized(_ status: EKAuthorizationStatus) -> Bool { switch status { case .authorized, .fullAccess: return true default: return false } } func checkCalendarAuthorization() async { let status = EKEventStore.authorizationStatus(for: .event) calendarAuthorizationStatus = status switch status { case .notDetermined: let granted = await calendarService.requestAccess(to: .event) calendarAuthorizationStatus = granted ? .fullAccess : .denied if granted { await reloadCalendarAndReminderLists() await updateEvents() } case .restricted, .denied: NSLog("Calendar access denied or restricted") case .authorized, .fullAccess: await reloadCalendarAndReminderLists() await updateEvents() case .writeOnly: NSLog("Calendar write only") @unknown default: NSLog("Unknown calendar authorization status") } } func checkReminderAuthorization() async { let status = EKEventStore.authorizationStatus(for: .reminder) reminderAuthorizationStatus = status switch status { case .notDetermined: let granted = await calendarService.requestAccess(to: .reminder) reminderAuthorizationStatus = granted ? .fullAccess : .denied if granted { await reloadCalendarAndReminderLists() } case .restricted, .denied: NSLog("Reminder access denied or restricted") case .authorized, .fullAccess: await reloadCalendarAndReminderLists() case .writeOnly: NSLog("Reminder write only") @unknown default: NSLog("Unknown reminder authorization status") } } func updateSelectedCalendars() { switch Defaults[.calendarSelectionState] { case .all: selectedCalendarIDs = Set(allCalendars.map { $0.id }) case .selected(let identifiers): selectedCalendarIDs = identifiers } selectedCalendars = allCalendars.filter { selectedCalendarIDs.contains($0.id) } } func getCalendarSelected(_ calendar: CalendarModel) -> Bool { selectedCalendarIDs.contains(calendar.id) } func setCalendarSelected(_ calendar: CalendarModel, isSelected: Bool) async { var selectionState = Defaults[.calendarSelectionState] switch selectionState { case .all: if !isSelected { let identifiers = Set(allCalendars.map { $0.id }).subtracting([calendar.id]) selectionState = .selected(identifiers) } case .selected(var identifiers): if isSelected { identifiers.insert(calendar.id) } else { identifiers.remove(calendar.id) } if identifiers.isEmpty || identifiers.count == allCalendars.count { selectionState = .all } else { selectionState = .selected(identifiers) } } Defaults[.calendarSelectionState] = selectionState updateSelectedCalendars() await updateEvents() } static func startOfDay(_ date: Date) -> Date { Calendar.current.startOfDay(for: date) } func updateCurrentDate(_ date: Date) async { currentWeekStartDate = Calendar.current.startOfDay(for: date) await updateEvents() } private func updateEvents() async { let calendarIDs = selectedCalendars.map { $0.id } let events = await calendarService.events( from: currentWeekStartDate, to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!, calendars: calendarIDs ) self.events = events lastEventsFetchDate = Date() } func setReminderCompleted(reminderID: String, completed: Bool) async { await calendarService.setReminderCompleted(reminderID: reminderID, completed: completed) await updateEvents() } }