From 854eb3e4bdea859198e8714c04b50e82a220a090 Mon Sep 17 00:00:00 2001 From: Jis G Jacob Date: Mon, 18 May 2026 09:51:42 +0530 Subject: [PATCH 1/2] Atoll: tighten minimalist music player layout - Push content below notch cutout, tighten album art halo - Resize play/pause and side buttons to fit inside notch bounds - Scope progress bar clipping so album art glow renders fully Signed-off-by: Jis G Jacob --- .../Notch/MinimalisticMusicPlayerView.swift | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift index 6e5b273..91255d5 100644 --- a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift +++ b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift @@ -113,6 +113,7 @@ struct MinimalisticMusicPlayerView: View { // Compact progress bar progressBar .padding(.top, 6) + .clipped() // Compact playback controls if shouldShowControlHUDRow { @@ -133,7 +134,7 @@ struct MinimalisticMusicPlayerView: View { reminderList } .padding(.horizontal, 12) - .padding(.top, -15) + .padding(.top, 12) .padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding) .frame(maxWidth: .infinity) .frame(height: calculateDynamicHeight(), alignment: .top) @@ -254,7 +255,7 @@ struct MinimalisticMusicPlayerView: View { height += 6 + 4 // progress bar + top padding // Add playback controls height - height += 40 + 2 // controls + top padding + height += 54 + 2 // controls + top padding // Add lyrics height if enabled in settings (reserve space even while loading) if enableLyrics { @@ -706,7 +707,7 @@ private struct MinimalisticReminderDetailsView: View { // MARK: - Playback Controls (Larger) private var playbackControls: some View { - HStack(spacing: 16) { + HStack(spacing: 10) { ForEach(Array(displayedSlots.enumerated()), id: \.offset) { _, slot in slotView(for: slot) } @@ -834,10 +835,10 @@ private struct MinimalisticReminderDetailsView: View { private var playPauseButton: some View { MinimalisticSquircircleButton( icon: musicManager.isPlaying ? (musicManager.isLiveStream ? "stop.fill" : "pause.fill") : "play.fill", - fontSize: 28, + fontSize: 26, fontWeight: .semibold, - frameSize: CGSize(width: 60, height: 60), - cornerRadius: 24, + frameSize: CGSize(width: 54, height: 54), + cornerRadius: 22, foregroundColor: .white, pressEffect: .none, symbolEffectStyle: .replace, @@ -867,8 +868,8 @@ private struct MinimalisticReminderDetailsView: View { icon: icon, fontSize: size, fontWeight: .medium, - frameSize: CGSize(width: 40, height: 40), - cornerRadius: 16, + frameSize: CGSize(width: 36, height: 36), + cornerRadius: 14, foregroundColor: isActive ? resolvedActiveColor : .white.opacity(0.85), pressEffect: pressEffect, symbolEffectStyle: symbolEffect, @@ -991,8 +992,8 @@ private struct MinimalisticReminderDetailsView: View { icon: routeManager.activeDevice?.iconName ?? "speaker.wave.2", fontSize: 18, fontWeight: .medium, - frameSize: CGSize(width: 40, height: 40), - cornerRadius: 16, + frameSize: CGSize(width: 36, height: 36), + cornerRadius: 14, foregroundColor: .white.opacity(0.85), symbolEffectStyle: .replace ) { @@ -1051,8 +1052,8 @@ private struct MinimalisticReminderDetailsView: View { icon: "airplayaudio", fontSize: 18, fontWeight: .medium, - frameSize: CGSize(width: 40, height: 40), - cornerRadius: 16, + frameSize: CGSize(width: 36, height: 36), + cornerRadius: 14, foregroundColor: .white.opacity(0.85), symbolEffectStyle: .replace ) { @@ -1144,17 +1145,17 @@ struct MinimalisticAlbumArtView: View { ) .clipped() .clipShape(RoundedRectangle(cornerRadius: albumArtCornerRadius)) - .scaleEffect(x: 1.3, y: 1.4) + .scaleEffect(x: 1.08, y: 1.1) .rotationEffect(.degrees(92)) - .blur(radius: 35) + .blur(radius: 16) .opacity( usesLiveCanvasArtwork - ? (musicManager.isPlaying ? 0.68 : 0.22) - : min(0.6, 1 - max(musicManager.albumArt.getBrightness(), 0.3)) + ? (musicManager.isPlaying ? 0.55 : 0.18) + : min(0.45, 1 - max(musicManager.albumArt.getBrightness(), 0.3)) ) .shadow( - color: Color(nsColor: musicManager.avgColor).opacity(usesLiveCanvasArtwork ? 0.28 : 0.16), - radius: usesLiveCanvasArtwork ? 18 : 12, + color: Color(nsColor: musicManager.avgColor).opacity(usesLiveCanvasArtwork ? 0.22 : 0.12), + radius: usesLiveCanvasArtwork ? 10 : 7, x: 0, y: 0 ) From 3829627dbd1d9a9adc1229dcca801cee50bd1974 Mon Sep 17 00:00:00 2001 From: Jis G Jacob Date: Mon, 18 May 2026 09:51:50 +0530 Subject: [PATCH 2/2] Atoll: clear lock screen widgets faster on unlock - Add session-active and CGSession poll fallbacks for unlock detection - Force weather panel alpha to 0 before orderOut for instant hide Signed-off-by: Jis G Jacob --- .../managers/LockScreenManager.swift | 63 +++++++++++++++++-- .../LockScreenWeatherPanelManager.swift | 2 + 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/DynamicIsland/managers/LockScreenManager.swift b/DynamicIsland/managers/LockScreenManager.swift index 6807f02..95da4fc 100644 --- a/DynamicIsland/managers/LockScreenManager.swift +++ b/DynamicIsland/managers/LockScreenManager.swift @@ -48,6 +48,7 @@ class LockScreenManager: ObservableObject { private var debounceIdleTask: Task? private var collapseTask: Task? private var postUnlockMusicHUDTask: Task? + private var lockStatePollTask: Task? // MARK: - Helpers @@ -65,9 +66,11 @@ class LockScreenManager: ObservableObject { deinit { DistributedNotificationCenter.default().removeObserver(self) + NSWorkspace.shared.notificationCenter.removeObserver(self) debounceIdleTask?.cancel() collapseTask?.cancel() postUnlockMusicHUDTask?.cancel() + lockStatePollTask?.cancel() } // MARK: - Setup @@ -80,7 +83,7 @@ class LockScreenManager: ObservableObject { name: .init("com.apple.screenIsLocked"), object: nil ) - + // Observe screen unlocked event DistributedNotificationCenter.default().addObserver( self, @@ -88,7 +91,18 @@ class LockScreenManager: ObservableObject { name: .init("com.apple.screenIsUnlocked"), object: nil ) - + + // Fallback: macOS sometimes delays `com.apple.screenIsUnlocked`, leaving + // lock-screen widgets visible after the user-perceived unlock. + // The workspace session-active notification typically fires earlier; the + // guard at the top of `screenUnlocked` makes the call idempotent. + NSWorkspace.shared.notificationCenter.addObserver( + self, + selector: #selector(screenUnlocked), + name: NSWorkspace.sessionDidBecomeActiveNotification, + object: nil + ) + print("LockScreenManager: ✅ Observers registered for lock/unlock events") } @@ -141,9 +155,11 @@ class LockScreenManager: ObservableObject { print("[\(timestamp())] LockScreenManager: ⏭️ Lock icon disabled in settings") } + startLockStatePolling() + print("[\(timestamp())] LockScreenManager: ✅ Lock screen activated") } - + @objc private func screenUnlocked() { guard isLocked else { print("[\(timestamp())] LockScreenManager: 🔁 Unlock event ignored (already unlocked)") @@ -156,6 +172,7 @@ class LockScreenManager: ObservableObject { lastUpdated = Date() updateIdleState(locked: false) isLocked = false + stopLockStatePolling() postUnlockMusicHUDTask?.cancel() shouldDelayPostUnlockMusicHUD = Defaults[.enableLockScreenLiveActivity] @@ -207,8 +224,46 @@ class LockScreenManager: ObservableObject { print("[\(self.timestamp())] LockScreenManager: ✅ Lock screen deactivated") } + // MARK: - Lock State Polling + + // Defensive fallback against late/missed `com.apple.screenIsUnlocked` and + // `NSWorkspace.sessionDidBecomeActiveNotification` notifications, which + // macOS sometimes delivers well after the user-perceived unlock — leaving + // lock-screen widgets visible for an extra moment. While we believe we are + // locked, poll the canonical session-lock state and fire `screenUnlocked()` + // the moment the OS flips. The handler's duplicate-event guard makes this + // safe to call alongside any later-arriving notification. + private static func isSessionScreenLocked() -> Bool { + guard let session = CGSessionCopyCurrentDictionary() as? [String: Any] else { + return false + } + return session["CGSSessionScreenIsLocked"] as? Bool ?? false + } + + private func startLockStatePolling() { + lockStatePollTask?.cancel() + lockStatePollTask = Task { [weak self] in + while !Task.isCancelled { + try? await Task.sleep(for: .milliseconds(500)) + if Task.isCancelled { return } + await MainActor.run { + guard let self, self.isLocked else { return } + if !Self.isSessionScreenLocked() { + print("[\(self.timestamp())] LockScreenManager: 🔓 Polling detected unlock ahead of notification") + self.screenUnlocked() + } + } + } + } + } + + private func stopLockStatePolling() { + lockStatePollTask?.cancel() + lockStatePollTask = nil + } + // MARK: - Idle State Management - + /// Copy EXACT logic from ScreenRecordingManager private func updateIdleState(locked: Bool) { if locked { diff --git a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift index 9b6b5b1..493b5b9 100644 --- a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift +++ b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift @@ -49,8 +49,10 @@ final class LockScreenWeatherPanelManager { func hide() { guard let window else { return } + window.alphaValue = 0 window.orderOut(nil) window.contentView = nil + window.alphaValue = 1 latestFrame = nil lastSnapshot = nil lastContentSize = nil