mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update 3rd party calendar icons
This commit is contained in:
parent
563afde92f
commit
78463086ba
2 changed files with 150 additions and 33 deletions
|
|
@ -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"))
|
||||||
|
|
@ -3053,9 +3061,12 @@ struct CalendarSettings: View {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
|
Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: selectedCalendarApp.iconName)
|
AppIconImage(
|
||||||
.foregroundColor(selectedCalendarApp.iconColor)
|
bundleIdentifiers: selectedCalendarApp.bundleIdentifiers,
|
||||||
.frame(width: 20, height: 20)
|
symbolFallback: selectedCalendarApp.fallbackIconName,
|
||||||
|
symbolColor: selectedCalendarApp.fallbackIconColor,
|
||||||
|
size: 20
|
||||||
|
)
|
||||||
Text("Enable third-party calendar app launch")
|
Text("Enable third-party calendar app launch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3064,7 +3075,15 @@ struct CalendarSettings: View {
|
||||||
if enableThirdPartyCalendarApp {
|
if enableThirdPartyCalendarApp {
|
||||||
Picker("Calendar App", selection: $selectedCalendarApp) {
|
Picker("Calendar App", selection: $selectedCalendarApp) {
|
||||||
ForEach(ThirdPartyCalendarApp.allCases) { app in
|
ForEach(ThirdPartyCalendarApp.allCases) { app in
|
||||||
Text(app.displayName).tag(app)
|
HStack {
|
||||||
|
AppIconImage(
|
||||||
|
bundleIdentifiers: app.bundleIdentifiers,
|
||||||
|
symbolFallback: app.fallbackIconName,
|
||||||
|
symbolColor: app.fallbackIconColor
|
||||||
|
)
|
||||||
|
Text(app.displayName)
|
||||||
|
}
|
||||||
|
.tag(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.settingsHighlight(id: highlightID("Calendar App"))
|
.settingsHighlight(id: highlightID("Calendar App"))
|
||||||
|
|
@ -3500,19 +3519,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)
|
||||||
|
|
@ -3523,19 +3530,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)
|
||||||
|
|
@ -7971,3 +7966,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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,14 +281,22 @@ enum ThirdPartyCalendarApp: String, CaseIterable, Codable, Defaults.Serializable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconName: String {
|
/// 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 {
|
switch self {
|
||||||
case .fantastical: return "calendar.badge.clock"
|
case .fantastical: return "calendar.badge.clock"
|
||||||
case .notionCalendar: return "calendar.badge.plus"
|
case .notionCalendar: return "calendar.badge.plus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconColor: Color {
|
var fallbackIconColor: Color {
|
||||||
switch self {
|
switch self {
|
||||||
case .fantastical: return .red
|
case .fantastical: return .red
|
||||||
case .notionCalendar: return .blue
|
case .notionCalendar: return .blue
|
||||||
|
|
@ -370,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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue