diff --git a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift index 495a540..374a9df 100644 --- a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift +++ b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift @@ -9,6 +9,11 @@ import SwiftUI import Defaults struct LockScreenMusicPanel: View { + private struct GlassLogSnapshot: Equatable { + let style: LockScreenGlassStyle + let usesLiquidGlass: Bool + } + static let collapsedSize = CGSize(width: 420, height: 180) static let expandedSize = CGSize(width: 720, height: 340) @@ -23,6 +28,7 @@ struct LockScreenMusicPanel: View { @State private var isExpanded = false @State private var isVolumeSliderVisible = false @State private var collapseWorkItem: DispatchWorkItem? + @State private var lastLoggedGlassSnapshot: GlassLogSnapshot? @Default(.lockScreenGlassStyle) var lockScreenGlassStyle @Default(.lockScreenShowAppIcon) var showAppIcon @Default(.lockScreenPanelShowsBorder) var showPanelBorder @@ -78,45 +84,61 @@ struct LockScreenMusicPanel: View { } private var panelContent: some View { - panelCore - .frame(width: currentSize.width, height: currentSize.height) - .animation(.spring(response: 0.48, dampingFraction: 0.82, blendDuration: 0.18), value: isExpanded) - .animation(.spring(response: 0.45, dampingFraction: 0.85, blendDuration: 0.18), value: shouldShowVolumeSlider) - .onAppear { - sliderValue = musicManager.elapsedTime - isActive = true - logPanelAppearance() - updatePanelSize(animated: false) - routeManager.refreshDevices() + ZStack(alignment: .topLeading) { + panelBackgroundLayer + panelForeground + } + .frame(width: currentSize.width, height: currentSize.height, alignment: .topLeading) + .clipShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)) + .overlay { + if showPanelBorder && !usesLiquidGlass { + RoundedRectangle(cornerRadius: panelCornerRadius) + .stroke(Color.white.opacity(0.35), lineWidth: 1.4) } - .onDisappear { - isActive = false - cancelCollapseTimer() - isVolumeSliderVisible = false - } - .onChange(of: isExpanded) { _, expanded in - updatePanelSize() - } - .onChange(of: showMediaOutputControl) { _, enabled in - if !enabled { - withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) { - isVolumeSliderVisible = false - } - } - updatePanelSize() - } - .onChange(of: enableLyrics) { _, _ in + } + .shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 10) + .contentShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)) + .animation(.spring(response: 0.48, dampingFraction: 0.82, blendDuration: 0.18), value: isExpanded) + .animation(.spring(response: 0.45, dampingFraction: 0.85, blendDuration: 0.18), value: shouldShowVolumeSlider) + .onAppear { + sliderValue = musicManager.elapsedTime + isActive = true + logPanelAppearance() + updatePanelSize(animated: false) + routeManager.refreshDevices() + logGlassState(reason: "Panel appeared") + } + .onDisappear { + isActive = false + cancelCollapseTimer() + isVolumeSliderVisible = false + } + .onChange(of: isExpanded) { _, expanded in + updatePanelSize() + } + .onChange(of: showMediaOutputControl) { _, enabled in + if !enabled { withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) { - updatePanelSize() + isVolumeSliderVisible = false } } - .scaleEffect(animator.isPresented ? 1 : 0.9, anchor: .center) - .opacity(animator.isPresented ? 1 : 0) - .animation(.spring(response: 0.52, dampingFraction: 0.8), value: animator.isPresented) + updatePanelSize() + } + .onChange(of: enableLyrics) { _, _ in + withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) { + updatePanelSize() + } + } + .onChange(of: lockScreenGlassStyle) { _, _ in + logGlassState(reason: "Glass style updated") + } + .scaleEffect(animator.isPresented ? 1 : 0.9, anchor: .center) + .opacity(animator.isPresented ? 1 : 0) + .animation(.spring(response: 0.52, dampingFraction: 0.8), value: animator.isPresented) } @ViewBuilder - private var panelCore: some View { + private var panelForeground: some View { Group { if isExpanded { expandedLayout @@ -126,17 +148,7 @@ struct LockScreenMusicPanel: View { } .padding(.horizontal, isExpanded ? 24 : 20) .padding(.vertical, isExpanded ? 22 : 16) - .frame(width: currentSize.width, height: currentSize.height, alignment: .topLeading) - .background(panelBackground) - .clipShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)) - .overlay { - if showPanelBorder { - RoundedRectangle(cornerRadius: panelCornerRadius) - .stroke(Color.white.opacity(0.35), lineWidth: 1.4) - } - } - .shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 10) - .contentShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } private var collapsedLayout: some View { @@ -605,10 +617,17 @@ struct LockScreenMusicPanel: View { .padding(.horizontal, 12) .background( RoundedRectangle(cornerRadius: isExpanded ? 16 : 12, style: .continuous) - .fill(Color.white.opacity(0.08)) + .fill(sliderBackgroundFill) ) } + private var sliderBackgroundFill: Color { + if usesLiquidGlass { + return Color.white.opacity(0.05) + } + return Color.white.opacity(0.08) + } + private var shouldShowVolumeSlider: Bool { showMediaOutputControl && isVolumeSliderVisible } @@ -683,27 +702,40 @@ struct LockScreenMusicPanel: View { } @ViewBuilder - private var panelBackground: some View { + private var panelBackgroundLayer: some View { if usesLiquidGlass { - liquidPanelBackground + liquidPanelBackdrop } else if shouldUseFrostedBlur { frostedPanelBackground } else { - RoundedRectangle(cornerRadius: panelCornerRadius) - .fill(Color.black.opacity(0.45)) + opaquePanelBackground } } @ViewBuilder - private var liquidPanelBackground: some View { + private var liquidPanelBackdrop: some View { if #available(macOS 26.0, *) { clearLiquidGlassSurface(cornerRadius: panelCornerRadius) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .allowsHitTesting(false) + .accessibilityHidden(true) } } private var frostedPanelBackground: some View { RoundedRectangle(cornerRadius: panelCornerRadius) .fill(.ultraThinMaterial) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .allowsHitTesting(false) + .accessibilityHidden(true) + } + + private var opaquePanelBackground: some View { + RoundedRectangle(cornerRadius: panelCornerRadius) + .fill(Color.black.opacity(0.45)) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .allowsHitTesting(false) + .accessibilityHidden(true) } private func albumArtImage(size: CGFloat, cornerRadius: CGFloat) -> some View { @@ -749,19 +781,48 @@ struct LockScreenMusicPanel: View { @available(macOS 26.0, *) private func clearLiquidGlassSurface(cornerRadius: CGFloat) -> some View { -// RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) -// .fill(.clear) // Keep surface true to Apple's "Clear" glass guidance -// .glassEffect( -// .clear.interactive(), -// in: .rect(cornerRadius: cornerRadius) -// ) RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + .fill(.clear) .glassEffect( .clear.interactive(), in: .rect(cornerRadius: cornerRadius) ) } + private func logGlassState(reason: String) { + let snapshot = GlassLogSnapshot(style: lockScreenGlassStyle, usesLiquidGlass: usesLiquidGlass) + guard snapshot != lastLoggedGlassSnapshot else { return } + lastLoggedGlassSnapshot = snapshot + + struct ComponentState { + let name: String + let isLiquid: Bool + } + + let states = [ + ComponentState(name: "Panel Shell", isLiquid: usesLiquidGlass), + ComponentState(name: "Control Capsules", isLiquid: usesLiquidGlass), + ComponentState(name: "Volume Slider", isLiquid: usesLiquidGlass), + ComponentState(name: "Album Art Plate", isLiquid: usesLiquidGlass) + ] + + let componentSummary = states.map { entry -> String in + let mode = entry.isLiquid ? "Liquid" : "Frosted" + return "\(entry.name)=\(mode)" + }.joined(separator: ", ") + + print("[LockScreenMusicPanel] \(reason) – style=\(lockScreenGlassStyle.rawValue), usesLiquidGlass=\(usesLiquidGlass), components[\(componentSummary)], macOS \(currentOSVersionDescription())") + + if lockScreenGlassStyle == .liquid && !usesLiquidGlass { + print("[LockScreenMusicPanel] Liquid Glass requested but unavailable on this macOS build. Falling back to frosted visuals.") + } + } + + private func currentOSVersionDescription() -> String { + let version = ProcessInfo.processInfo.operatingSystemVersion + return "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + } + private func logPanelAppearance(event: String = "✅ View appeared") { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss.SSS" diff --git a/DynamicIsland/managers/LockScreenPanelManager.swift b/DynamicIsland/managers/LockScreenPanelManager.swift index 7875aaa..ff2572d 100644 --- a/DynamicIsland/managers/LockScreenPanelManager.swift +++ b/DynamicIsland/managers/LockScreenPanelManager.swift @@ -127,6 +127,7 @@ class LockScreenPanelManager { content.wantsLayer = true content.layer?.masksToBounds = true content.layer?.cornerRadius = collapsedPanelCornerRadius + content.layer?.backgroundColor = NSColor.clear.cgColor } if !hasDelegated {