From 070f88df6fa1f0495cce4bace4196b9fe33fc206 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Fri, 17 Oct 2025 20:43:29 +0530 Subject: [PATCH] Fully working stuff --- DynamicIsland/Localizable.xcstrings | 6 ++ .../LockScreen/LockScreenMusicPanel.swift | 82 ++++++++++++------- .../Notch/MinimalisticMusicPlayerView.swift | 69 ++++++++++++---- .../components/Settings/SettingsView.swift | 18 ++++ DynamicIsland/enums/generic.swift | 7 ++ .../managers/LockScreenPanelManager.swift | 9 +- DynamicIsland/models/Constants.swift | 1 + 7 files changed, 143 insertions(+), 49 deletions(-) diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index b5d8596..c3a14aa 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -9189,6 +9189,9 @@ }, "LinkedIn" : { + }, + "Liquid Glass requires macOS 26 or later." : { + }, "Live Monitoring" : { @@ -9198,6 +9201,9 @@ }, "Lock Screen" : { + }, + "Lock screen material" : { + }, "Loop Mode" : { diff --git a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift index 75ace95..16713f5 100644 --- a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift +++ b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift @@ -9,15 +9,24 @@ import SwiftUI import Defaults struct LockScreenMusicPanel: View { + static let defaultSize = CGSize(width: 420, height: 180) + @ObservedObject var musicManager = MusicManager.shared @State private var sliderValue: Double = 0 @State private var dragging: Bool = false @State private var lastDragged: Date = .distantPast @State private var isActive = true + @Default(.lockScreenGlassStyle) var lockScreenGlassStyle - private let panelWidth: CGFloat = 460 - private let panelHeight: CGFloat = 180 private let cornerRadius: CGFloat = 28 + private var panelSize: CGSize { Self.defaultSize } + + private var usesLiquidGlass: Bool { + if #available(macOS 26.0, *) { + return lockScreenGlassStyle == .liquid + } + return false + } var body: some View { if isActive { @@ -27,7 +36,7 @@ struct LockScreenMusicPanel: View { } } else { Color.clear - .frame(width: panelWidth, height: panelHeight) + .frame(width: panelSize.width, height: panelSize.height) } } @@ -87,26 +96,8 @@ struct LockScreenMusicPanel: View { } .padding(.horizontal, 20) .padding(.vertical, 16) - .frame(width: panelWidth, height: panelHeight) - .background { - if #available(macOS 26.0, *) { - RoundedRectangle(cornerRadius: cornerRadius) - .glassEffect(in: .rect(cornerRadius: cornerRadius)) - .onAppear { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss.SSS" - print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✨ Using macOS 26.0+ Liquid Glass effect") - } - } else { - RoundedRectangle(cornerRadius: cornerRadius) - .fill(.ultraThinMaterial) - .onAppear { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss.SSS" - print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: 🪟 Using ultraThinMaterial fallback (macOS < 26.0)") - } - } - } + .frame(width: panelSize.width, height: panelSize.height) + .background(panelBackground) .overlay { RoundedRectangle(cornerRadius: cornerRadius) .stroke(Color.white.opacity(0.35), lineWidth: 1.4) @@ -115,9 +106,7 @@ struct LockScreenMusicPanel: View { .onAppear { sliderValue = musicManager.elapsedTime isActive = true - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss.SSS" - print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✅ View appeared") + logPanelAppearance() } } @@ -241,9 +230,8 @@ struct LockScreenMusicPanel: View { Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill") .font(.system(size: 24, weight: .semibold)) .foregroundColor(.white) - .frame(width: 54, height: 54) - .background(Color.white.opacity(0.15)) - .clipShape(Circle()) + .frame(width: 48, height: 48) + .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) } @@ -253,6 +241,8 @@ struct LockScreenMusicPanel: View { Image(systemName: icon) .font(.system(size: size, weight: .medium)) .foregroundColor(isActive ? .red : .white.opacity(0.8)) + .frame(width: 32, height: 32) + .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) } @@ -264,4 +254,38 @@ struct LockScreenMusicPanel: View { case .one: return "repeat.1" } } + + @ViewBuilder + private var panelBackground: some View { + if usesLiquidGlass { + liquidPanelBackground + } else { + frostedPanelBackground + } + } + + @ViewBuilder + private var liquidPanelBackground: some View { + if #available(macOS 26.0, *) { + RoundedRectangle(cornerRadius: cornerRadius) + .glassEffect( + .regular + .tint(Color.white.opacity(0.12)) + .interactive(), + in: .rect(cornerRadius: cornerRadius) + ) + } + } + + private var frostedPanelBackground: some View { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(.ultraThinMaterial) + } + + private func logPanelAppearance(event: String = "✅ View appeared") { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss.SSS" + let styleDescriptor = usesLiquidGlass ? "Liquid Glass" : "Frosted" + print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: \(event) – \(styleDescriptor)") + } } diff --git a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift index fd7eae7..febb879 100644 --- a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift +++ b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift @@ -192,24 +192,29 @@ struct MinimalisticMusicPlayerView: View { } private var playPauseButton: some View { - Button(action: { - Task { await musicManager.togglePlay() } - }) { - Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.white) - .frame(width: 50, height: 50) - } - .buttonStyle(PlainButtonStyle()) + MinimalisticSquircircleButton( + icon: musicManager.isPlaying ? "pause.fill" : "play.fill", + fontSize: 24, + fontWeight: .semibold, + frameSize: CGSize(width: 54, height: 54), + cornerRadius: 20, + foregroundColor: .white, + action: { + Task { await musicManager.togglePlay() } + } + ) } private func controlButton(icon: String, size: CGFloat = 18, isActive: Bool = false, action: @escaping () -> Void) -> some View { - Button(action: action) { - Image(systemName: icon) - .font(.system(size: size, weight: .medium)) - .foregroundColor(isActive ? .red : .white.opacity(0.8)) - } - .buttonStyle(PlainButtonStyle()) + MinimalisticSquircircleButton( + icon: icon, + fontSize: size, + fontWeight: .medium, + frameSize: CGSize(width: 40, height: 40), + cornerRadius: 16, + foregroundColor: isActive ? .red : .white.opacity(0.85), + action: action + ) } private var repeatIcon: String { @@ -274,3 +279,37 @@ struct MinimalisticAlbumArtView: View { .scaleEffect(musicManager.isPlaying ? 1 : 0.85) } } + +// MARK: - Hover-highlighted control button + +private struct MinimalisticSquircircleButton: View { + let icon: String + let fontSize: CGFloat + let fontWeight: Font.Weight + let frameSize: CGSize + let cornerRadius: CGFloat + let foregroundColor: Color + let action: () -> Void + + @State private var isHovering = false + + var body: some View { + Button(action: action) { + Image(systemName: icon) + .font(.system(size: fontSize, weight: fontWeight)) + .foregroundColor(foregroundColor) + .frame(width: frameSize.width, height: frameSize.height) + .background( + RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + .fill(isHovering ? Color.white.opacity(0.18) : .clear) + ) + .contentShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) + } + .buttonStyle(PlainButtonStyle()) + .onHover { hovering in + withAnimation(.easeOut(duration: 0.18)) { + isHovering = hovering + } + } + } +} diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index 8e6bc5d..dfc2e05 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -174,6 +174,7 @@ struct GeneralSettings: View { @Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection @Default(.showRecordingIndicator) var showRecordingIndicator @Default(.enableMinimalisticUI) var enableMinimalisticUI + @Default(.lockScreenGlassStyle) var lockScreenGlassStyle var body: some View { Form { @@ -298,6 +299,23 @@ struct GeneralSettings: View { Section { Defaults.Toggle("Enable Lock Screen Live Activity", key: .enableLockScreenLiveActivity) + if #available(macOS 26.0, *) { + Picker("Lock screen material", selection: $lockScreenGlassStyle) { + ForEach(LockScreenGlassStyle.allCases) { style in + Text(style.rawValue).tag(style) + } + } + } else { + Picker("Lock screen material", selection: $lockScreenGlassStyle) { + ForEach(LockScreenGlassStyle.allCases) { style in + Text(style.rawValue).tag(style) + } + } + .disabled(true) + Text("Liquid Glass requires macOS 26 or later.") + .font(.caption) + .foregroundStyle(.secondary) + } Button("Copy Latest Crash Report") { copyLatestCrashReport() diff --git a/DynamicIsland/enums/generic.swift b/DynamicIsland/enums/generic.swift index e1ae0c6..58736df 100644 --- a/DynamicIsland/enums/generic.swift +++ b/DynamicIsland/enums/generic.swift @@ -70,3 +70,10 @@ enum SliderColorEnum: String, CaseIterable, Defaults.Serializable { case albumArt = "Match album art" case accent = "Accent color" } + +enum LockScreenGlassStyle: String, CaseIterable, Defaults.Serializable, Identifiable { + case liquid = "Liquid Glass" + case frosted = "Frosted Glass" + + var id: String { rawValue } +} diff --git a/DynamicIsland/managers/LockScreenPanelManager.swift b/DynamicIsland/managers/LockScreenPanelManager.swift index 06fff41..16f5fab 100644 --- a/DynamicIsland/managers/LockScreenPanelManager.swift +++ b/DynamicIsland/managers/LockScreenPanelManager.swift @@ -41,12 +41,11 @@ class LockScreenPanelManager { return } - let panelWidth: CGFloat = 460 - let panelHeight: CGFloat = 180 + let panelSize = LockScreenMusicPanel.defaultSize let screenFrame = screen.frame - let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelWidth / 2) - let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelHeight - 32 - let targetFrame = NSRect(x: originX, y: originY, width: panelWidth, height: panelHeight) + let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelSize.width / 2) + let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelSize.height - 32 + let targetFrame = NSRect(x: originX, y: originY, width: panelSize.width, height: panelSize.height) let window: NSWindow diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index fa40afe..1b7b625 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -336,6 +336,7 @@ extension Defaults.Keys { static let showShuffleAndRepeat = Key("showShuffleAndRepeat", default: false) // Enable lock screen media widget (shows the standalone panel when screen is locked) static let enableLockScreenMediaWidget = Key("enableLockScreenMediaWidget", default: true) + static let lockScreenGlassStyle = Key("lockScreenGlassStyle", default: .liquid) // MARK: Battery static let showPowerStatusNotifications = Key("showPowerStatusNotifications", default: true)