Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

153 lines
4.8 KiB
Swift

import Combine
import Foundation
import MacToolsPluginKit
@MainActor
final class CalendarComponentViewModel: ObservableObject {
@Published private(set) var month: CalendarMonthModel
@Published private(set) var selectedDay: CalendarDayModel?
@Published private(set) var authorization: CalendarEventAuthorization
@Published private(set) var isLoadingEvents = false
private let eventService: CalendarEventServicing
private let holidayProvider: CalendarHolidayProvider
private let calendar: Calendar
private let localization: PluginLocalization
private var displayedMonthStart: Date
private var selectedDate: Date
private var eventsByDay: [Date: [CalendarEventSummary]] = [:]
private var loadTask: Task<Void, Never>?
init(
eventService: CalendarEventServicing = CalendarEventService(),
holidayProvider: CalendarHolidayProvider,
calendar: Calendar = CalendarComponentCalendars.gregorianFollowingSystem(),
localization: PluginLocalization = PluginLocalization(bundle: .main),
today: Date = Date()
) {
self.eventService = eventService
self.holidayProvider = holidayProvider
self.calendar = calendar
self.localization = localization
self.displayedMonthStart = CalendarComponentCalendars.monthStart(containing: today, calendar: calendar)
self.selectedDate = calendar.startOfDay(for: today)
self.authorization = eventService.authorization
self.month = CalendarMonthModelBuilder(
calendar: calendar,
holidayProvider: holidayProvider,
localization: localization
).makeMonth(containing: today, today: today)
self.selectedDay = month.days.first { calendar.isDate($0.date, inSameDayAs: selectedDate) }
}
func start() {
refresh()
}
func stop() {
loadTask?.cancel()
loadTask = nil
}
func refresh() {
rebuildMonth()
reloadEvents()
}
func moveMonth(by value: Int) {
guard let nextMonth = calendar.date(byAdding: .month, value: value, to: displayedMonthStart) else {
return
}
displayedMonthStart = CalendarComponentCalendars.monthStart(containing: nextMonth, calendar: calendar)
selectedDate = displayedMonthStart
eventsByDay = [:]
rebuildMonth()
reloadEvents()
}
func goToToday() {
let today = calendar.startOfDay(for: Date())
displayedMonthStart = CalendarComponentCalendars.monthStart(containing: today, calendar: calendar)
selectedDate = today
eventsByDay = [:]
rebuildMonth(today: today)
reloadEvents()
}
func select(_ day: CalendarDayModel) {
selectedDate = day.date
selectedDay = day
}
func open(_ day: CalendarDayModel) {
select(day)
eventService.openSystemCalendar(at: day.date)
}
private func reloadEvents() {
loadTask?.cancel()
loadTask = Task { [weak self] in
guard let self else {
return
}
authorization = eventService.authorization
guard authorization.isFullAccess else {
eventsByDay = [:]
isLoadingEvents = false
rebuildMonth()
return
}
guard let firstDate = month.days.first?.date,
let lastDate = month.days.last?.date,
let endDate = calendar.date(byAdding: .day, value: 1, to: lastDate) else {
return
}
isLoadingEvents = true
do {
let events = try await eventService.events(from: firstDate, to: endDate)
guard !Task.isCancelled else {
return
}
eventsByDay = CalendarEventGrouper.group(
events: events,
visibleDates: month.days.map(\.date),
calendar: calendar,
localization: localization
)
isLoadingEvents = false
rebuildMonth()
} catch {
guard !Task.isCancelled else {
return
}
eventsByDay = [:]
isLoadingEvents = false
rebuildMonth()
}
}
}
private func rebuildMonth(today: Date = Date()) {
month = CalendarMonthModelBuilder(
calendar: calendar,
holidayProvider: holidayProvider,
localization: localization
).makeMonth(
containing: displayedMonthStart,
today: today,
eventsByDay: eventsByDay
)
selectedDay = month.days.first { calendar.isDate($0.date, inSameDayAs: selectedDate) }
?? month.days.first { $0.isToday }
?? month.days.first
}
}