Update Notion Calendar

This commit is contained in:
Hariharan Mudaliar 2026-04-06 13:41:55 +05:30
commit 563afde92f
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
4 changed files with 110 additions and 20 deletions

View file

@ -7231,6 +7231,9 @@
} }
} }
} }
},
"Calendar App" : {
}, },
"Calendar or Reminder access is denied. Please enable it in System Settings." : { "Calendar or Reminder access is denied. Please enable it in System Settings." : {
"comment" : "A message displayed when a user has denied access to the calendar or reminders on their device. It instructs the user to open System Settings to grant access.", "comment" : "A message displayed when a user has denied access to the calendar or reminders on their device. It instructs the user to open System Settings to grant access.",
@ -14925,6 +14928,9 @@
} }
} }
} }
},
"Enable third-party calendar app launch" : {
}, },
"Enable third-party DDC app integration" : { "Enable third-party DDC app integration" : {
"comment" : "A toggle switch that enables or disables integration with a third-party Dynamic Display Control (DDC) app.", "comment" : "A toggle switch that enables or disables integration with a third-party Dynamic Display Control (DDC) app.",
@ -35981,9 +35987,6 @@
} }
} }
} }
},
"Use Fantastical" : {
}, },
"Use MacBook icon when on battery" : { "Use MacBook icon when on battery" : {
"localizations" : { "localizations" : {
@ -37187,7 +37190,7 @@
} }
} }
}, },
"When enabled, clicking on calendar events will open Fantastical instead of Apple Calendar. More integrations coming soon." : { "When enabled, clicking on calendar events will open the selected third-party calendar app instead of Apple Calendar." : {
}, },
"When enabled, Focus appears briefly (on/off) and then collapses instead of staying visible." : { "When enabled, Focus appears briefly (on/off) and then collapses instead of staying visible." : {

View file

@ -2752,7 +2752,8 @@ struct CalendarSettings: View {
@Default(.lockScreenCalendarSelectionMode) private var lockScreenCalendarSelectionMode @Default(.lockScreenCalendarSelectionMode) private var lockScreenCalendarSelectionMode
@Default(.lockScreenSelectedCalendarIDs) private var lockScreenSelectedCalendarIDs @Default(.lockScreenSelectedCalendarIDs) private var lockScreenSelectedCalendarIDs
@Default(.lockScreenShowCalendarEventAfterStartEnabled) private var lockScreenShowCalendarEventAfterStartEnabled @Default(.lockScreenShowCalendarEventAfterStartEnabled) private var lockScreenShowCalendarEventAfterStartEnabled
@Default(.useFantasticalCalendar) private var useFantasticalCalendar @Default(.enableThirdPartyCalendarApp) private var enableThirdPartyCalendarApp
@Default(.selectedCalendarApp) private var selectedCalendarApp
@Default(.fantasticalDefaultView) private var fantasticalDefaultView @Default(.fantasticalDefaultView) private var fantasticalDefaultView
private func highlightID(_ title: String) -> String { private func highlightID(_ title: String) -> String {
@ -3050,28 +3051,37 @@ struct CalendarSettings: View {
// MARK: - Third-party Calendar Integration // MARK: - Third-party Calendar Integration
Section { Section {
Defaults.Toggle(key: .useFantasticalCalendar) { Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
HStack { HStack {
Image(systemName: "calendar.badge.clock") Image(systemName: selectedCalendarApp.iconName)
.foregroundColor(.red) .foregroundColor(selectedCalendarApp.iconColor)
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("Use Fantastical") Text("Enable third-party calendar app launch")
} }
} }
.settingsHighlight(id: highlightID("Use Fantastical")) .settingsHighlight(id: highlightID("Enable third-party calendar app launch"))
if useFantasticalCalendar { if enableThirdPartyCalendarApp {
Picker("Default View", selection: $fantasticalDefaultView) { Picker("Calendar App", selection: $selectedCalendarApp) {
ForEach(FantasticalViewStyle.allCases, id: \.self) { style in ForEach(ThirdPartyCalendarApp.allCases) { app in
Text(style.displayName).tag(style) Text(app.displayName).tag(app)
} }
} }
.settingsHighlight(id: highlightID("Fantastical Default View")) .settingsHighlight(id: highlightID("Calendar App"))
if selectedCalendarApp == .fantastical {
Picker("Default View", selection: $fantasticalDefaultView) {
ForEach(FantasticalViewStyle.allCases, id: \.self) { style in
Text(style.displayName).tag(style)
}
}
.settingsHighlight(id: highlightID("Fantastical Default View"))
}
} }
} header: { } header: {
Text("Third-party Calendar Integration") Text("Third-party Calendar Integration")
} footer: { } footer: {
Text("When enabled, clicking on calendar events will open Fantastical instead of Apple Calendar. More integrations coming soon.") Text("When enabled, clicking on calendar events will open the selected third-party calendar app instead of Apple Calendar.")
} }
Section(header: Text("Select Calendars")) { Section(header: Text("Select Calendars")) {

View file

@ -268,6 +268,34 @@ enum FantasticalViewStyle: String, CaseIterable, Codable, Defaults.Serializable
} }
} }
enum ThirdPartyCalendarApp: String, CaseIterable, Codable, Defaults.Serializable, Identifiable {
case fantastical = "fantastical"
case notionCalendar = "notionCalendar"
var id: String { rawValue }
var displayName: String {
switch self {
case .fantastical: return "Fantastical"
case .notionCalendar: return "Notion Calendar"
}
}
var iconName: String {
switch self {
case .fantastical: return "calendar.badge.clock"
case .notionCalendar: return "calendar.badge.plus"
}
}
var iconColor: Color {
switch self {
case .fantastical: return .red
case .notionCalendar: return .blue
}
}
}
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
case popover = "popover" // Traditional popover attached to button case popover = "popover" // Traditional popover attached to button
case panel = "panel" // Floating panel near notch case panel = "panel" // Floating panel near notch
@ -867,7 +895,8 @@ extension Defaults.Keys {
static let lockScreenWeatherWidgetRowOrder = Key<String>("lockScreenWeatherWidgetRowOrder", default: "weather_calendar_focus") static let lockScreenWeatherWidgetRowOrder = Key<String>("lockScreenWeatherWidgetRowOrder", default: "weather_calendar_focus")
// MARK: Third-party Calendar Integration // MARK: Third-party Calendar Integration
static let useFantasticalCalendar = Key<Bool>("useFantasticalCalendar", default: false) static let enableThirdPartyCalendarApp = Key<Bool>("enableThirdPartyCalendarApp", default: false)
static let selectedCalendarApp = Key<ThirdPartyCalendarApp>("selectedCalendarApp", default: .fantastical)
static let fantasticalDefaultView = Key<FantasticalViewStyle>("fantasticalDefaultView", default: .mini) static let fantasticalDefaultView = Key<FantasticalViewStyle>("fantasticalDefaultView", default: .mini)
// MARK: Battery // MARK: Battery

View file

@ -22,6 +22,7 @@
import Foundation import Foundation
import Defaults import Defaults
import AppKit
struct EventModel: Equatable, Identifiable { struct EventModel: Equatable, Identifiable {
let id: String let id: String
@ -98,9 +99,14 @@ extension EventModel {
var isMeeting: Bool { !participants.isEmpty } var isMeeting: Bool { !participants.isEmpty }
func calendarAppURL() -> URL? { func calendarAppURL() -> URL? {
// Check if Fantastical is enabled // Check if a third-party calendar app is enabled
if Defaults[.useFantasticalCalendar] { if Defaults[.enableThirdPartyCalendarApp] {
return fantasticalURL() switch Defaults[.selectedCalendarApp] {
case .fantastical:
return fantasticalURL()
case .notionCalendar:
return notionCalendarURL()
}
} }
return appleCalendarURL() return appleCalendarURL()
} }
@ -160,6 +166,48 @@ extension EventModel {
} }
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)") return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)")
} }
/// Returns URL to open event in Notion Calendar (formerly Cron)
private func notionCalendarURL() -> URL? {
// Reminders still use Apple's Reminders app
guard !type.isReminder else {
return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
}
let formatter = ISO8601DateFormatter()
var components = URLComponents()
components.scheme = "cron"
components.host = "showEvent"
components.queryItems = [
URLQueryItem(name: "accountEmail", value: calendar.accountName),
URLQueryItem(name: "iCalUID", value: id),
URLQueryItem(name: "startDate", value: formatter.string(from: start)),
URLQueryItem(name: "endDate", value: formatter.string(from: end)),
URLQueryItem(name: "title", value: title)
]
if let url = components.url {
return url
}
// Fallback: just launch Notion Calendar app
Self.launchNotionCalendar()
return nil
}
/// Launches Notion Calendar app directly (fallback or general access)
static func launchNotionCalendar() {
// Launch via bundle identifier (Notion Calendar's bundle ID is "com.cron.electron")
if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.cron.electron") {
let config = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: config) { _, error in
if let error = error {
print("Error launching Notion Calendar: \(error.localizedDescription)")
}
}
}
}
} }
struct Participant: Hashable { struct Participant: Hashable {