Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion boringNotch/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ struct ContentView: View {

@State private var haptics: Bool = false

@State private var albumArtOpacity: Double = 1.0
@State private var albumArtFadeTask: Task<Void, Never>?

@Namespace var albumArtNamespace

@Default(.showNotHumanFace) var showNotHumanFace
@Default(.albumArtDisplayMode) var albumArtDisplayMode

// Use standardized animations from StandardAnimations enum
private let animationSpring = StandardAnimations.interactive
Expand Down Expand Up @@ -451,7 +455,7 @@ struct ContentView: View {
return base
}()

Image(nsImage: musicManager.albumArt)
currentAlbumArtImage
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(
Expand All @@ -463,6 +467,7 @@ struct ContentView: View {
width: scaledArtSize,
height: scaledArtSize
)
.opacity(albumArtDisplayMode == .fade ? albumArtOpacity : 1.0)

Rectangle()
.fill(.black)
Expand Down Expand Up @@ -536,6 +541,68 @@ struct ContentView: View {
height: displayClosedNotchHeight,
alignment: .center
)
.onAppear {
refreshAlbumArtFade()
}
.onChange(of: musicManager.songTitle) { _, _ in
refreshAlbumArtFade()
}
.onChange(of: vm.notchState) { _, _ in
refreshAlbumArtFade()
}
.onChange(of: isHovering) { _, _ in
refreshAlbumArtFade()
}
.onChange(of: albumArtDisplayMode) { _, _ in
refreshAlbumArtFade()
}
}

private var currentAlbumArtImage: Image {
if albumArtDisplayMode == .appIcon,
let bundleID = musicManager.bundleIdentifier,
!bundleID.isEmpty {
return AppIcon(for: bundleID)
}
return Image(nsImage: musicManager.albumArt)
}

private func refreshAlbumArtFade() {
guard albumArtDisplayMode == .fade else {
albumArtFadeTask?.cancel()
albumArtFadeTask = nil
if albumArtOpacity != 1.0 {
withAnimation(.easeInOut(duration: 0.3)) {
albumArtOpacity = 1.0
}
}
return
}

if vm.notchState != .closed || isHovering {
albumArtFadeTask?.cancel()
albumArtFadeTask = nil
withAnimation(.easeInOut(duration: 0.3)) {
albumArtOpacity = 1.0
}
return
}

scheduleAlbumArtFade()
}

private func scheduleAlbumArtFade() {
albumArtFadeTask?.cancel()
withAnimation(.easeInOut(duration: 0.3)) {
albumArtOpacity = 1.0
}
albumArtFadeTask = Task { @MainActor in
try? await Task.sleep(for: .seconds(3))
if Task.isCancelled { return }
withAnimation(.easeInOut(duration: 1.0)) {
albumArtOpacity = 0.0
}
}
}

@ViewBuilder
Expand Down
92 changes: 68 additions & 24 deletions boringNotch/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,49 @@
}
}
},
"Album art display" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Album art display"
}
}
}
},
"album_art_always" : {
"comment" : "Album art display: Always show",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Always show"
}
}
}
},
"album_art_app_icon" : {
"comment" : "Album art display: Show app icon",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Show app icon"
}
}
}
},
"album_art_fade" : {
"comment" : "Album art display: Fade after 3 seconds",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fade after 3 seconds"
}
}
}
},
"All-day" : {
"localizations" : {
"cs" : {
Expand Down Expand Up @@ -2449,16 +2492,6 @@
}
}
},
"Changing the app language requires restarting Boring Notch." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Changing the app language requires restarting Boring Notch."
}
}
}
},
"Change media with horizontal gestures" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -2505,6 +2538,16 @@
}
}
},
"Changing the app language requires restarting Boring Notch." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Changing the app language requires restarting Boring Notch."
}
}
}
},
"Charging" : {
"localizations" : {
"cs" : {
Expand Down Expand Up @@ -6893,6 +6936,16 @@
}
}
},
"Later" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Later"
}
}
}
},
"Launch at login" : {
"localizations" : {
"fr" : {
Expand All @@ -6915,16 +6968,6 @@
}
}
},
"Later" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Later"
}
}
}
},
"Layout Preview" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -9559,6 +9602,7 @@
}
},
"OSD" : {
"extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
Expand Down Expand Up @@ -10055,9 +10099,6 @@
},
"Real-time audio waveform" : {

},
"Requires macOS 14.2 or later. Update macOS to enable real-time audio waveform." : {

},
"Release name" : {
"localizations" : {
Expand Down Expand Up @@ -10441,6 +10482,9 @@
}
}
}
},
"Requires macOS 14.2 or later. Update macOS to enable real-time audio waveform." : {

},
"Reset to Defaults" : {
"localizations" : {
Expand Down Expand Up @@ -14153,4 +14197,4 @@
}
},
"version" : "1.1"
}
}
6 changes: 6 additions & 0 deletions boringNotch/components/Settings/Views/MediaSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct Media: View {
@Default(.hideNotchOption) var hideNotchOption
@Default(.enableSneakPeek) private var enableSneakPeek
@Default(.sneakPeekStyles) var sneakPeekStyles
@Default(.albumArtDisplayMode) var albumArtDisplayMode

@Default(.enableLyrics) var enableLyrics

Expand Down Expand Up @@ -77,6 +78,11 @@ struct Media: View {
}
}
}
Picker("Album art display", selection: $albumArtDisplayMode) {
ForEach(AlbumArtDisplayMode.allCases) { mode in
Text(mode.localizedString).tag(mode)
}
}
Picker(
selection: $hideNotchOption,
label:
Expand Down
26 changes: 25 additions & 1 deletion boringNotch/models/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,26 @@ enum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable {
}
}

// Album art display options for the closed-notch music live activity
enum AlbumArtDisplayMode: String, CaseIterable, Identifiable, Defaults.Serializable {
case always
case fade
case appIcon

var id: String { self.rawValue }

var localizedString: String {
switch self {
case .always:
return NSLocalizedString("album_art_always", comment: "Album art display: Always show")
case .fade:
return NSLocalizedString("album_art_fade", comment: "Album art display: Fade after 3 seconds")
case .appIcon:
return NSLocalizedString("album_art_app_icon", comment: "Album art display: Show app icon")
}
}
}

// Action to perform when Option (⌥) is held while pressing media keys
enum OptionKeyAction: String, CaseIterable, Identifiable, Defaults.Serializable {
case openSettings
Expand Down Expand Up @@ -276,7 +296,11 @@ extension Defaults.Keys {
"musicControlSlotLimit",
default: MusicControlButton.defaultLayout.count
)

static let albumArtDisplayMode = Key<AlbumArtDisplayMode>(
"albumArtDisplayMode",
default: .always
)

// MARK: Battery
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
static let showBatteryIndicator = Key<Bool>("showBatteryIndicator", default: true)
Expand Down
Loading