mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge pull request #401 from Ebullioscopic/integration/fantastical
Changes in Icons and stuff, along with readme updates
This commit is contained in:
commit
668fba2d3a
11 changed files with 346 additions and 59 deletions
|
|
@ -2042,7 +2042,6 @@
|
||||||
},
|
},
|
||||||
"±10 Seconds" : {
|
"±10 Seconds" : {
|
||||||
"comment" : "Display name for the \"±10 Seconds\" music skip behavior option.",
|
"comment" : "Display name for the \"±10 Seconds\" music skip behavior option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -2665,7 +2664,6 @@
|
||||||
},
|
},
|
||||||
"Accent" : {
|
"Accent" : {
|
||||||
"comment" : "Display name for the caps lock indicator tint mode.",
|
"comment" : "Display name for the caps lock indicator tint mode.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -3143,7 +3141,6 @@
|
||||||
},
|
},
|
||||||
"Adaptive gradient" : {
|
"Adaptive gradient" : {
|
||||||
"comment" : "Display name for the timer icon color mode: adaptive gradient.",
|
"comment" : "Display name for the timer icon color mode: adaptive gradient.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -5194,7 +5191,6 @@
|
||||||
},
|
},
|
||||||
"Apple Music" : {
|
"Apple Music" : {
|
||||||
"comment" : "Media controller type for Apple Music",
|
"comment" : "Media controller type for Apple Music",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -5917,7 +5913,6 @@
|
||||||
},
|
},
|
||||||
"Bar" : {
|
"Bar" : {
|
||||||
"comment" : "Name of the timer progress style that shows a horizontal bar.",
|
"comment" : "Name of the timer progress style that shows a horizontal bar.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -7236,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.",
|
||||||
|
|
@ -11787,6 +11785,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Default View" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Default: dynamic.m4a" : {
|
"Default: dynamic.m4a" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -12212,7 +12213,6 @@
|
||||||
},
|
},
|
||||||
"Digital" : {
|
"Digital" : {
|
||||||
"comment" : "The display name for the \"Digital\" reminder presentation style.",
|
"comment" : "The display name for the \"Digital\" reminder presentation style.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -14928,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.",
|
||||||
|
|
@ -17300,7 +17303,6 @@
|
||||||
},
|
},
|
||||||
"Green" : {
|
"Green" : {
|
||||||
"comment" : "Display name for the green caps lock indicator tint mode.",
|
"comment" : "Display name for the green caps lock indicator tint mode.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -25124,7 +25126,6 @@
|
||||||
},
|
},
|
||||||
"Now Playing" : {
|
"Now Playing" : {
|
||||||
"comment" : "Name of the media controller type for the \"Now Playing\" option.",
|
"comment" : "Name of the media controller type for the \"Now Playing\" option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -25809,7 +25810,6 @@
|
||||||
},
|
},
|
||||||
"Panel" : {
|
"Panel" : {
|
||||||
"comment" : "Display name for the clipboard display mode \"Panel\".",
|
"comment" : "Display name for the clipboard display mode \"Panel\".",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -28661,7 +28661,6 @@
|
||||||
},
|
},
|
||||||
"Ring" : {
|
"Ring" : {
|
||||||
"comment" : "The name of the timer progress style that shows a ring shape.",
|
"comment" : "The name of the timer progress style that shows a ring shape.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -29919,7 +29918,6 @@
|
||||||
},
|
},
|
||||||
"Separate Tab" : {
|
"Separate Tab" : {
|
||||||
"comment" : "Display name for the clipboard display mode option to open the clipboard in a separate tab.",
|
"comment" : "Display name for the clipboard display mode option to open the clipboard in a separate tab.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -31687,7 +31685,6 @@
|
||||||
},
|
},
|
||||||
"Shows screen assistant as a dropdown attached to the AI button" : {
|
"Shows screen assistant as a dropdown attached to the AI button" : {
|
||||||
"comment" : "Description of the screen assistant display mode option that shows the screen assistant as a dropdown attached to the AI button.",
|
"comment" : "Description of the screen assistant display mode option that shows the screen assistant as a dropdown attached to the AI button.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -31700,7 +31697,6 @@
|
||||||
},
|
},
|
||||||
"Shows screen assistant in a floating panel near the notch" : {
|
"Shows screen assistant in a floating panel near the notch" : {
|
||||||
"comment" : "Description of the screen assistant display mode option that shows the screen assistant in a floating panel near the notch.",
|
"comment" : "Description of the screen assistant display mode option that shows the screen assistant in a floating panel near the notch.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -31854,7 +31850,6 @@
|
||||||
},
|
},
|
||||||
"Skip forward or backward by ten seconds" : {
|
"Skip forward or backward by ten seconds" : {
|
||||||
"comment" : "Description of a music skip behavior option.",
|
"comment" : "Description of a music skip behavior option.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -32517,7 +32512,6 @@
|
||||||
},
|
},
|
||||||
"Spotify" : {
|
"Spotify" : {
|
||||||
"comment" : "Spotify media controller type.",
|
"comment" : "Spotify media controller type.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -32706,7 +32700,6 @@
|
||||||
},
|
},
|
||||||
"Standard previous/next track controls" : {
|
"Standard previous/next track controls" : {
|
||||||
"comment" : "Description of the music skip behavior option to use standard previous/next track controls.",
|
"comment" : "Description of the music skip behavior option to use standard previous/next track controls.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -34131,6 +34124,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Third-party Calendar Integration" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"This duration powers the \"Custom\" option inside the timer popover for quick access." : {
|
"This duration powers the \"Custom\" option inside the timer popover for quick access." : {
|
||||||
"comment" : "A footer description for the custom timer settings, explaining their purpose.",
|
"comment" : "A footer description for the custom timer settings, explaining their purpose.",
|
||||||
|
|
@ -34995,7 +34991,6 @@
|
||||||
},
|
},
|
||||||
"Track Skip" : {
|
"Track Skip" : {
|
||||||
"comment" : "Display name for the \"Track Skip\" music skip behavior.",
|
"comment" : "Display name for the \"Track Skip\" music skip behavior.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
@ -37194,6 +37189,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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." : {
|
||||||
"comment" : "A help text for the setting that allows the focus indicator to collapse instead of staying visible.",
|
"comment" : "A help text for the setting that allows the focus indicator to collapse instead of staying visible.",
|
||||||
|
|
@ -37763,7 +37761,6 @@
|
||||||
},
|
},
|
||||||
"Youtube Music" : {
|
"Youtube Music" : {
|
||||||
"comment" : "Spotify music service.",
|
"comment" : "Spotify music service.",
|
||||||
"extractionState" : "stale",
|
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ru" : {
|
"ru" : {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||||
|
* Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||||
*
|
*
|
||||||
* Objective-C bridge to expose C++ AudioProcessor to Swift.
|
* Objective-C bridge to expose C++ AudioProcessor to Swift.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||||
|
* Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||||
*
|
*
|
||||||
* Objective-C++ implementation bridging AudioProcessor to Swift.
|
* Objective-C++ implementation bridging AudioProcessor to Swift.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||||
*
|
*
|
||||||
* Real-time audio processor using Accelerate framework for efficient FFT
|
* Real-time audio processor using Accelerate framework for efficient FFT
|
||||||
* and band filtering. Adapted from rtaudio project.
|
* and band filtering. Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||||
*
|
*
|
||||||
* CoreAudio tap for capturing real-time audio from music applications.
|
* CoreAudio tap for capturing real-time audio from music applications.
|
||||||
* Uses macOS 14.2+ Process Tap API for efficient audio capture.
|
* Uses macOS 14.2+ Process Tap API for efficient audio capture.
|
||||||
* Adapted from rtaudio project.
|
* Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||||
*
|
*
|
||||||
* Real-time audio spectrum visualization using CoreAudio tap data.
|
* Real-time audio spectrum visualization using CoreAudio tap data.
|
||||||
* Adapted from rtaudio project.
|
* Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2074,7 +2074,15 @@ private struct ExternalDisplayIntegrationsSection: View {
|
||||||
if enableThirdPartyDDCIntegration {
|
if enableThirdPartyDDCIntegration {
|
||||||
Picker("Provider", selection: $thirdPartyDDCProvider) {
|
Picker("Provider", selection: $thirdPartyDDCProvider) {
|
||||||
ForEach(ThirdPartyDDCProvider.allCases) { provider in
|
ForEach(ThirdPartyDDCProvider.allCases) { provider in
|
||||||
Text(provider.displayName).tag(provider)
|
HStack {
|
||||||
|
AppIconImage(
|
||||||
|
bundleIdentifiers: provider.bundleIdentifiers,
|
||||||
|
symbolFallback: "display",
|
||||||
|
symbolColor: .secondary
|
||||||
|
)
|
||||||
|
Text(provider.displayName)
|
||||||
|
}
|
||||||
|
.tag(provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.settingsHighlight(id: highlightID("Third-party DDC provider"))
|
.settingsHighlight(id: highlightID("Third-party DDC provider"))
|
||||||
|
|
@ -2752,6 +2760,9 @@ 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(.enableThirdPartyCalendarApp) private var enableThirdPartyCalendarApp
|
||||||
|
@Default(.selectedCalendarApp) private var selectedCalendarApp
|
||||||
|
@Default(.fantasticalDefaultView) private var fantasticalDefaultView
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
SettingsTab.calendar.highlightID(for: title)
|
SettingsTab.calendar.highlightID(for: title)
|
||||||
|
|
@ -3045,6 +3056,49 @@ struct CalendarSettings: View {
|
||||||
.disabled(!lockScreenShowCalendarEvent)
|
.disabled(!lockScreenShowCalendarEvent)
|
||||||
.settingsHighlight(id: highlightID("Show start time after event begins"))
|
.settingsHighlight(id: highlightID("Show start time after event begins"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Third-party Calendar Integration
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "ellipsis.calendar")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
Text("Enable third-party calendar app launch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.settingsHighlight(id: highlightID("Enable third-party calendar app launch"))
|
||||||
|
|
||||||
|
if enableThirdPartyCalendarApp {
|
||||||
|
Picker("Calendar App", selection: $selectedCalendarApp) {
|
||||||
|
ForEach(ThirdPartyCalendarApp.allCases) { app in
|
||||||
|
HStack {
|
||||||
|
AppIconImage(
|
||||||
|
bundleIdentifiers: app.bundleIdentifiers,
|
||||||
|
symbolFallback: app.fallbackIconName,
|
||||||
|
symbolColor: app.fallbackIconColor
|
||||||
|
)
|
||||||
|
Text(app.displayName)
|
||||||
|
}
|
||||||
|
.tag(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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: {
|
||||||
|
Text("Third-party Calendar Integration")
|
||||||
|
} footer: {
|
||||||
|
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")) {
|
||||||
let grouped = Dictionary(grouping: calendarManager.allCalendars, by: \.accountName)
|
let grouped = Dictionary(grouping: calendarManager.allCalendars, by: \.accountName)
|
||||||
|
|
@ -3462,19 +3516,7 @@ struct Shelf: View {
|
||||||
Picker("Quick Share Service", selection: $quickShareProvider) {
|
Picker("Quick Share Service", selection: $quickShareProvider) {
|
||||||
ForEach(quickShareService.availableProviders, id: \.id) { provider in
|
ForEach(quickShareService.availableProviders, id: \.id) { provider in
|
||||||
HStack {
|
HStack {
|
||||||
Group {
|
QuickShareProviderIconImage(provider: provider, size: 16)
|
||||||
if let imgData = provider.imageData, let nsImg = NSImage(data: imgData) {
|
|
||||||
Image(nsImage: nsImg)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
|
||||||
} else {
|
|
||||||
Image(systemName: "square.and.arrow.up")
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
Text(provider.id)
|
Text(provider.id)
|
||||||
}
|
}
|
||||||
.tag(provider.id)
|
.tag(provider.id)
|
||||||
|
|
@ -3485,19 +3527,7 @@ struct Shelf: View {
|
||||||
|
|
||||||
if let selectedProvider {
|
if let selectedProvider {
|
||||||
HStack {
|
HStack {
|
||||||
Group {
|
QuickShareProviderIconImage(provider: selectedProvider, size: 16)
|
||||||
if let imgData = selectedProvider.imageData, let nsImg = NSImage(data: imgData) {
|
|
||||||
Image(nsImage: nsImg)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
|
||||||
} else {
|
|
||||||
Image(systemName: "square.and.arrow.up")
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Currently selected: \(selectedProvider.id)")
|
Text("Currently selected: \(selectedProvider.id)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
@ -7933,3 +7963,109 @@ struct TerminalSettings: View {
|
||||||
.navigationTitle("Terminal")
|
.navigationTitle("Terminal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Reusable App Icon View
|
||||||
|
|
||||||
|
/// Fetches the real app icon from the system using bundle identifiers,
|
||||||
|
/// falling back to an asset catalog image or an SF Symbol.
|
||||||
|
struct AppIconImage: View {
|
||||||
|
let bundleIdentifiers: [String]
|
||||||
|
var assetFallback: String? = nil
|
||||||
|
var symbolFallback: String = "app.fill"
|
||||||
|
var symbolColor: Color = .accentColor
|
||||||
|
var size: CGFloat = 16
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if let nsImage = resolvedIcon() {
|
||||||
|
Image(nsImage: nsImage)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||||
|
} else if let assetFallback, let nsImage = NSImage(named: NSImage.Name(assetFallback)) {
|
||||||
|
Image(nsImage: nsImage)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||||
|
} else {
|
||||||
|
Image(systemName: symbolFallback)
|
||||||
|
.foregroundColor(symbolColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolvedIcon() -> NSImage? {
|
||||||
|
for bundleID in bundleIdentifiers {
|
||||||
|
if let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) {
|
||||||
|
let icon = NSWorkspace.shared.icon(forFile: appURL.path)
|
||||||
|
// NSWorkspace returns a valid icon even for generic apps;
|
||||||
|
// resize to keep memory low.
|
||||||
|
let thumb = NSImage(size: NSSize(width: 32, height: 32))
|
||||||
|
thumb.lockFocus()
|
||||||
|
icon.draw(in: NSRect(origin: .zero, size: NSSize(width: 32, height: 32)),
|
||||||
|
from: NSRect(origin: .zero, size: icon.size),
|
||||||
|
operation: .copy, fraction: 1.0)
|
||||||
|
thumb.unlockFocus()
|
||||||
|
return thumb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct QuickShareProviderIconImage: View {
|
||||||
|
let provider: QuickShareProvider
|
||||||
|
var size: CGFloat = 16
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if let imgData = provider.imageData, let nsImg = NSImage(data: imgData) {
|
||||||
|
Image(nsImage: nsImg)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||||
|
} else {
|
||||||
|
AppIconImage(
|
||||||
|
bundleIdentifiers: provider.bundleIdentifiersFallback,
|
||||||
|
assetFallback: provider.assetFallbackName,
|
||||||
|
symbolFallback: provider.symbolFallbackName,
|
||||||
|
symbolColor: .accentColor,
|
||||||
|
size: size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension QuickShareProvider {
|
||||||
|
var bundleIdentifiersFallback: [String] {
|
||||||
|
switch id {
|
||||||
|
case "LocalSend":
|
||||||
|
return ["org.localsend.localsend_app", "org.localsend.localsend"]
|
||||||
|
case "AirDrop":
|
||||||
|
return ["com.apple.finder"]
|
||||||
|
case "Mail":
|
||||||
|
return ["com.apple.mail"]
|
||||||
|
case "Messages":
|
||||||
|
return ["com.apple.MobileSMS", "com.apple.iChat"]
|
||||||
|
case "Notes":
|
||||||
|
return ["com.apple.Notes"]
|
||||||
|
case "Reminders":
|
||||||
|
return ["com.apple.reminders"]
|
||||||
|
case "Add to Safari Reading List":
|
||||||
|
return ["com.apple.Safari"]
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetFallbackName: String? {
|
||||||
|
id == "LocalSend" ? "LocalSend" : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbolFallbackName: String {
|
||||||
|
id == "System Share Menu" ? "square.and.arrow.up.on.square" : "square.and.arrow.up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Atoll (DynamicIsland)
|
* Atoll (DynamicIsland)
|
||||||
* Copyright (C) 2024-2026 Atoll Contributors
|
* Copyright (C) 2024-2026 Atoll Contributors
|
||||||
|
* Adapted from SwiftTerm (https://github.com/migueldeicaza/SwiftTerm)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,54 @@ enum CalendarSelectionState: Codable, Defaults.Serializable {
|
||||||
case selected(Set<String>)
|
case selected(Set<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FantasticalViewStyle: String, CaseIterable, Codable, Defaults.Serializable {
|
||||||
|
case mini = "mini"
|
||||||
|
case calendar = "calendar"
|
||||||
|
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .mini: return "Mini View"
|
||||||
|
case .calendar: return "Full Calendar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bundle identifiers to try when looking up the app icon (first match wins).
|
||||||
|
var bundleIdentifiers: [String] {
|
||||||
|
switch self {
|
||||||
|
case .fantastical: return ["com.flexibits.fantastical2.mac", "com.flexibits.fantastical"]
|
||||||
|
case .notionCalendar: return ["com.cron.electron"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackIconName: String {
|
||||||
|
switch self {
|
||||||
|
case .fantastical: return "calendar.badge.clock"
|
||||||
|
case .notionCalendar: return "calendar.badge.plus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackIconColor: 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
|
||||||
|
|
@ -330,6 +378,14 @@ enum ThirdPartyDDCProvider: String, CaseIterable, Codable, Defaults.Serializable
|
||||||
return "Lunar"
|
return "Lunar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bundle identifiers to try when looking up the app icon.
|
||||||
|
var bundleIdentifiers: [String] {
|
||||||
|
switch self {
|
||||||
|
case .betterDisplay: return ["pro.betterdisplay.BetterDisplay"]
|
||||||
|
case .lunar: return ["fyi.lunar.Lunar"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HideNotchOption: String, Defaults.Serializable {
|
enum HideNotchOption: String, Defaults.Serializable {
|
||||||
|
|
@ -854,6 +910,11 @@ extension Defaults.Keys {
|
||||||
static let lockScreenShowCalendarStartTimeAfterBegins = Key<Bool>("lockScreenShowCalendarStartTimeAfterBegins", default: true)
|
static let lockScreenShowCalendarStartTimeAfterBegins = Key<Bool>("lockScreenShowCalendarStartTimeAfterBegins", default: true)
|
||||||
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
|
||||||
|
static let enableThirdPartyCalendarApp = Key<Bool>("enableThirdPartyCalendarApp", default: false)
|
||||||
|
static let selectedCalendarApp = Key<ThirdPartyCalendarApp>("selectedCalendarApp", default: .fantastical)
|
||||||
|
static let fantasticalDefaultView = Key<FantasticalViewStyle>("fantasticalDefaultView", default: .mini)
|
||||||
|
|
||||||
// MARK: Battery
|
// MARK: Battery
|
||||||
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
||||||
static let showBatteryIndicator = Key<Bool>("showBatteryIndicator", default: BatteryActivityManager.shared.hasBattery())
|
static let showBatteryIndicator = Key<Bool>("showBatteryIndicator", default: BatteryActivityManager.shared.hasBattery())
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Defaults
|
||||||
|
import AppKit
|
||||||
|
|
||||||
struct EventModel: Equatable, Identifiable {
|
struct EventModel: Equatable, Identifiable {
|
||||||
let id: String
|
let id: String
|
||||||
|
|
@ -97,7 +99,20 @@ extension EventModel {
|
||||||
var isMeeting: Bool { !participants.isEmpty }
|
var isMeeting: Bool { !participants.isEmpty }
|
||||||
|
|
||||||
func calendarAppURL() -> URL? {
|
func calendarAppURL() -> URL? {
|
||||||
|
// Check if a third-party calendar app is enabled
|
||||||
|
if Defaults[.enableThirdPartyCalendarApp] {
|
||||||
|
switch Defaults[.selectedCalendarApp] {
|
||||||
|
case .fantastical:
|
||||||
|
return fantasticalURL()
|
||||||
|
case .notionCalendar:
|
||||||
|
return notionCalendarURL()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return appleCalendarURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns URL to open event in Apple Calendar
|
||||||
|
private func appleCalendarURL() -> URL? {
|
||||||
guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
|
guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +138,76 @@ extension EventModel {
|
||||||
}
|
}
|
||||||
return URL(string: "ical://ekevent\(date)/\(id)?method=show&options=more")
|
return URL(string: "ical://ekevent\(date)/\(id)?method=show&options=more")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns URL to open date in Fantastical
|
||||||
|
private func fantasticalURL() -> URL? {
|
||||||
|
// Reminders still use Apple's Reminders app
|
||||||
|
guard !type.isReminder else {
|
||||||
|
return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewStyle = Defaults[.fantasticalDefaultView]
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd"
|
||||||
|
let dateString = dateFormatter.string(from: start)
|
||||||
|
|
||||||
|
// x-fantastical3://show/mini/yyyy-MM-dd or x-fantastical3://show/calendar/yyyy-MM-dd
|
||||||
|
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)/\(dateString)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns URL to open Fantastical at current date (for general calendar access)
|
||||||
|
static func fantasticalShowURL(for date: Date? = nil) -> URL? {
|
||||||
|
let viewStyle = Defaults[.fantasticalDefaultView]
|
||||||
|
if let date = date {
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd"
|
||||||
|
let dateString = dateFormatter.string(from: date)
|
||||||
|
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)/\(dateString)")
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,12 @@ Atoll builds upon the work of several open-source projects and draws inspiration
|
||||||
|
|
||||||
- [**SkyLightWindow**](https://github.com/Lakr233/SkyLightWindow) - window rendering for Lock Screen Widgets
|
- [**SkyLightWindow**](https://github.com/Lakr233/SkyLightWindow) - window rendering for Lock Screen Widgets
|
||||||
|
|
||||||
|
- [**rtaudio**](https://github.com/ZephyrCodesStuff/rtaudio) - Live music visualizer using C++ was adapted from this project
|
||||||
|
|
||||||
|
- [**SwiftTerm**](https://github.com/migueldeicaza/SwiftTerm) - Terminal tab implementation in the standard mode was adapted from this project
|
||||||
|
|
||||||
- Wick - Thanks Nate for allowing us to replicate the iOS like Timer design for the Lock Screen Widget
|
- Wick - Thanks Nate for allowing us to replicate the iOS like Timer design for the Lock Screen Widget
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
<a href="https://github.com/Ebullioscopic/Atoll/graphs/contributors">
|
<a href="https://github.com/Ebullioscopic/Atoll/graphs/contributors">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue