// // 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 calendarAuthorizationStatus: EKAuthorizationStatus = .notDetermined @Published var reminderAuthorizationStatus: EKAuthorizationStatus = .notDetermined private var selectedCalendars: [CalendarModel] = [] private let calendarService = CalendarService() private var eventStoreChangedObserver: NSObjectProtocol? var hasCalendarAccess: Bool { isAuthorized(calendarAuthorizationStatus) } var hasReminderAccess: Bool { isAuthorized(reminderAuthorizationStatus) } private init() { self.currentWeekStartDate = CalendarManager.startOfDay(Date()) setupEventStoreChangedObserver() Task { await self.bootstrapAuthorizations() } } 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 { await self?.reloadCalendarAndReminderLists() } } } @MainActor func reloadCalendarAndReminderLists() async { let all = await calendarService.calendars() self.eventCalendars = all.filter { !$0.isReminder } self.reminderLists = all.filter { $0.isReminder } self.allCalendars = all // for legacy compatibility, can be removed if not needed updateSelectedCalendars() } private func bootstrapAuthorizations() async { await checkCalendarAuthorization(forceReload: true) await checkReminderAuthorization(forceReload: true) } private func refreshAuthorizationStatuses() { calendarAuthorizationStatus = EKEventStore.authorizationStatus(for: .event) reminderAuthorizationStatus = EKEventStore.authorizationStatus(for: .reminder) } private func isAuthorized(_ status: EKAuthorizationStatus) -> Bool { switch status { case .fullAccess, .authorized: return true default: return false } } func checkCalendarAuthorization(forceReload: Bool = false) async { refreshAuthorizationStatuses() let status = calendarAuthorizationStatus print("📅 Current calendar authorization status: \(status.rawValue)") switch status { case .notDetermined: let granted = await calendarService.requestAccess(to: .event) refreshAuthorizationStatuses() if granted || hasCalendarAccess { await reloadCalendarAndReminderLists() await updateEvents() } case .restricted, .denied: NSLog("Calendar access denied or restricted") case .writeOnly: NSLog("Calendar write-only access") case .fullAccess: guard forceReload else { return } await reloadCalendarAndReminderLists() await updateEvents() @unknown default: if isAuthorized(status) { guard forceReload else { return } await reloadCalendarAndReminderLists() await updateEvents() } else { print("Unknown authorization status: \(status.rawValue)") } } } func checkReminderAuthorization(forceReload: Bool = false) async { refreshAuthorizationStatuses() let status = reminderAuthorizationStatus print("📅 Current reminder authorization status: \(status.rawValue)") switch status { case .notDetermined: let granted = await calendarService.requestAccess(to: .reminder) refreshAuthorizationStatuses() if granted || hasReminderAccess { await reloadCalendarAndReminderLists() } case .restricted, .denied: NSLog("Reminder access denied or restricted") case .writeOnly: NSLog("Reminder write-only access") case .fullAccess: guard forceReload else { return } await reloadCalendarAndReminderLists() @unknown default: if isAuthorized(status) { guard forceReload else { return } await reloadCalendarAndReminderLists() } else { print("Unknown reminder authorization status: \(status.rawValue)") } } } func updateSelectedCalendars() { selectedCalendars = allCalendars.filter { getCalendarSelected($0) } } func getCalendarSelected(_ calendar: CalendarModel) -> Bool { switch Defaults[.calendarSelectionState] { case .all: return true case .selected(let identifiers): return identifiers.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) } selectionState = identifiers.isEmpty ? .all : identifiers.count == allCalendars.count ? .all : .selected(identifiers) // if empty, select all } Defaults[.calendarSelectionState] = selectionState updateSelectedCalendars() await updateEvents() } static func startOfDay(_ date: Date) -> Date { return 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 eventsResult = await calendarService.events( from: currentWeekStartDate, to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!, calendars: calendarIDs ) self.events = eventsResult } }