From 78463086bae9d3b62dee0e56d7742f387355c6d3 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Mon, 6 Apr 2026 14:06:34 +0530 Subject: [PATCH] Update 3rd party calendar icons --- .../components/Settings/SettingsView.swift | 163 ++++++++++++++---- DynamicIsland/models/Constants.swift | 20 ++- 2 files changed, 150 insertions(+), 33 deletions(-) diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index 9d39231..3c783db 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -2074,7 +2074,15 @@ private struct ExternalDisplayIntegrationsSection: View { if enableThirdPartyDDCIntegration { Picker("Provider", selection: $thirdPartyDDCProvider) { 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")) @@ -3053,9 +3061,12 @@ struct CalendarSettings: View { Section { Defaults.Toggle(key: .enableThirdPartyCalendarApp) { HStack { - Image(systemName: selectedCalendarApp.iconName) - .foregroundColor(selectedCalendarApp.iconColor) - .frame(width: 20, height: 20) + AppIconImage( + bundleIdentifiers: selectedCalendarApp.bundleIdentifiers, + symbolFallback: selectedCalendarApp.fallbackIconName, + symbolColor: selectedCalendarApp.fallbackIconColor, + size: 20 + ) Text("Enable third-party calendar app launch") } } @@ -3064,7 +3075,15 @@ struct CalendarSettings: View { if enableThirdPartyCalendarApp { Picker("Calendar App", selection: $selectedCalendarApp) { 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")) @@ -3500,19 +3519,7 @@ struct Shelf: View { Picker("Quick Share Service", selection: $quickShareProvider) { ForEach(quickShareService.availableProviders, id: \.id) { provider in HStack { - Group { - 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) + QuickShareProviderIconImage(provider: provider, size: 16) Text(provider.id) } .tag(provider.id) @@ -3523,19 +3530,7 @@ struct Shelf: View { if let selectedProvider { HStack { - Group { - 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) + QuickShareProviderIconImage(provider: selectedProvider, size: 16) VStack(alignment: .leading, spacing: 2) { Text("Currently selected: \(selectedProvider.id)") .font(.caption) @@ -7971,3 +7966,109 @@ struct TerminalSettings: View { .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" + } +} diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index a8ad397..7d9ebc9 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -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 { case .fantastical: return "calendar.badge.clock" case .notionCalendar: return "calendar.badge.plus" } } - var iconColor: Color { + var fallbackIconColor: Color { switch self { case .fantastical: return .red case .notionCalendar: return .blue @@ -370,6 +378,14 @@ enum ThirdPartyDDCProvider: String, CaseIterable, Codable, Defaults.Serializable 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 {