Enable app icon selection on macOS

This commit is contained in:
Arkadiusz Fal
2026-04-20 21:21:18 +02:00
parent b0f9bb2229
commit d2b6a158db
5 changed files with 53 additions and 10 deletions

View File

@@ -78,6 +78,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
LoggingService.shared.logCloudKit("Requesting remote notification registration...")
NSApplication.shared.registerForRemoteNotifications()
if let rawValue = UserDefaults.standard.string(forKey: "appIcon"),
let icon = AppIcon(rawValue: rawValue) {
SettingsManager.applyMacAppIcon(icon)
}
}
func application(_ application: NSApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

View File

@@ -8,6 +8,8 @@
import Foundation
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
#endif
extension SettingsManager {
@@ -35,9 +37,8 @@ extension SettingsManager {
}
}
// MARK: - App Icon Settings (iOS only)
// MARK: - App Icon Settings
#if os(iOS)
var appIcon: AppIcon {
get {
if let cached = _appIcon { return cached }
@@ -53,14 +54,53 @@ extension SettingsManager {
// Apply the icon change
Task { @MainActor in
#if os(iOS)
do {
try await UIApplication.shared.setAlternateIconName(newValue.alternateIconName)
} catch {
LoggingService.shared.error("Failed to set alternate icon: \(error)", category: .general)
}
#elseif os(macOS)
SettingsManager.applyMacAppIcon(newValue)
#endif
}
}
}
#if os(macOS)
static func applyMacAppIcon(_ icon: AppIcon) {
if icon == .default {
NSApp.applicationIconImage = nil
} else if let source = NSImage(named: icon.previewImageName) {
NSApp.applicationIconImage = makeMacIconImage(from: source)
}
}
/// Paints the source image onto a 1024×1024 canvas with the standard macOS
/// squircle mask and transparent padding so the Dock renders it like a
/// native app icon.
private static func makeMacIconImage(from source: NSImage) -> NSImage {
let canvasSize: CGFloat = 1024
// macOS icon grid: artwork occupies ~824×824 centered in a 1024 canvas,
// with a ~185pt corner radius on the masked rect.
let artworkSize: CGFloat = 824
let cornerRadius: CGFloat = 185
let inset = (canvasSize - artworkSize) / 2
let artworkRect = NSRect(x: inset, y: inset, width: artworkSize, height: artworkSize)
let image = NSImage(size: NSSize(width: canvasSize, height: canvasSize))
image.lockFocus()
let path = NSBezierPath(roundedRect: artworkRect, xRadius: cornerRadius, yRadius: cornerRadius)
path.addClip()
source.draw(in: artworkRect,
from: .zero,
operation: .sourceOver,
fraction: 1.0,
respectFlipped: true,
hints: [.interpolation: NSImageInterpolation.high.rawValue])
image.unlockFocus()
return image
}
#endif
/// Whether to show a checkmark badge on fully watched video thumbnails.

View File

@@ -52,7 +52,6 @@ enum AccentColor: String, CaseIterable, Codable {
}
}
#if os(iOS)
enum AppIcon: String, CaseIterable, Codable {
case `default`
case classic
@@ -89,7 +88,6 @@ enum AppIcon: String, CaseIterable, Codable {
}
}
}
#endif
// MARK: - Video Quality

View File

@@ -203,9 +203,7 @@ final class SettingsManager {
// Appearance settings
var _listStyle: VideoListStyle?
#if os(iOS)
var _appIcon: AppIcon?
#endif
// Video Swipe Actions
#if !os(tvOS)

View File

@@ -18,8 +18,8 @@ struct AppearanceSettingsView: View {
ThemeSection(settings: settings)
#endif
// App icon section (iOS only)
#if os(iOS)
// App icon section
#if !os(tvOS)
AppIconSection(settings: settings)
#endif
@@ -66,9 +66,9 @@ private struct ThemeSection: View {
}
}
// MARK: - App Icon Section (iOS only)
// MARK: - App Icon Section
#if os(iOS)
#if !os(tvOS)
private struct AppIconSection: View {
@Bindable var settings: SettingsManager
@@ -131,7 +131,9 @@ private struct AppIconPickerView: View {
}
}
.navigationTitle(String(localized: "settings.appearance.appIcon.header"))
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
}
}
#endif