mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge pull request #241 from Ebullioscopic/ui/fixes
BugFix: Display Lock and Unlock Lag
This commit is contained in:
commit
76b6c6da1a
8 changed files with 132 additions and 18 deletions
|
|
@ -0,0 +1,83 @@
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
struct LockScreenDisplayContext {
|
||||||
|
let screen: NSScreen
|
||||||
|
let frame: NSRect
|
||||||
|
let identifier: String
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class LockScreenDisplayContextProvider {
|
||||||
|
static let shared = LockScreenDisplayContextProvider()
|
||||||
|
|
||||||
|
private(set) var context: LockScreenDisplayContext?
|
||||||
|
private var screenChangeObserver: NSObjectProtocol?
|
||||||
|
private var workspaceObservers: [NSObjectProtocol] = []
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
refresh(reason: "init")
|
||||||
|
registerObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let screenChangeObserver {
|
||||||
|
NotificationCenter.default.removeObserver(screenChangeObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspaceCenter = NSWorkspace.shared.notificationCenter
|
||||||
|
workspaceObservers.forEach { workspaceCenter.removeObserver($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func refresh(reason: String) -> LockScreenDisplayContext? {
|
||||||
|
guard let screen = NSScreen.main ?? NSScreen.screens.first else {
|
||||||
|
context = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapshot = LockScreenDisplayContext(
|
||||||
|
screen: screen,
|
||||||
|
frame: screen.frame,
|
||||||
|
identifier: screen.localizedName
|
||||||
|
)
|
||||||
|
|
||||||
|
context = snapshot
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSnapshot() -> LockScreenDisplayContext? {
|
||||||
|
if let context {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
return refresh(reason: "snapshot-miss")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func registerObservers() {
|
||||||
|
screenChangeObserver = NotificationCenter.default.addObserver(
|
||||||
|
forName: NSApplication.didChangeScreenParametersNotification,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.refresh(reason: "screen-parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspaceCenter = NSWorkspace.shared.notificationCenter
|
||||||
|
let wakeObserver = workspaceCenter.addObserver(
|
||||||
|
forName: NSWorkspace.screensDidWakeNotification,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.refresh(reason: "screens-did-wake")
|
||||||
|
}
|
||||||
|
|
||||||
|
let spaceObserver = workspaceCenter.addObserver(
|
||||||
|
forName: NSWorkspace.activeSpaceDidChangeNotification,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.refresh(reason: "space-changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceObservers = [wakeObserver, spaceObserver]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -83,7 +83,7 @@ class LockScreenLiveActivityWindowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func lockContext() -> (notchSize: CGSize, screen: NSScreen)? {
|
private func lockContext() -> (notchSize: CGSize, screen: NSScreen)? {
|
||||||
guard let screen = NSScreen.main else {
|
guard let screen = LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main else {
|
||||||
print("[\(timestamp())] LockScreenLiveActivityWindowManager: no main screen available")
|
print("[\(timestamp())] LockScreenLiveActivityWindowManager: no main screen available")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ class LockScreenManager: ObservableObject {
|
||||||
print("[\(timestamp())] LockScreenManager: 🔒 Screen LOCKED event received")
|
print("[\(timestamp())] LockScreenManager: 🔒 Screen LOCKED event received")
|
||||||
Logger.log("LockScreenManager: Screen locked", category: .lifecycle)
|
Logger.log("LockScreenManager: Screen locked", category: .lifecycle)
|
||||||
LockSoundPlayer.shared.playLockChime()
|
LockSoundPlayer.shared.playLockChime()
|
||||||
|
LockScreenDisplayContextProvider.shared.refresh(reason: "screen-locked")
|
||||||
|
|
||||||
// Update state SYNCHRONOUSLY without Task/await to avoid any delay
|
// Update state SYNCHRONOUSLY without Task/await to avoid any delay
|
||||||
lastUpdated = Date()
|
lastUpdated = Date()
|
||||||
|
|
@ -134,6 +135,7 @@ class LockScreenManager: ObservableObject {
|
||||||
print("[\(timestamp())] LockScreenManager: 🔓 Screen UNLOCKED event received")
|
print("[\(timestamp())] LockScreenManager: 🔓 Screen UNLOCKED event received")
|
||||||
Logger.log("LockScreenManager: Screen unlocked", category: .lifecycle)
|
Logger.log("LockScreenManager: Screen unlocked", category: .lifecycle)
|
||||||
LockSoundPlayer.shared.playUnlockChime()
|
LockSoundPlayer.shared.playUnlockChime()
|
||||||
|
LockScreenDisplayContextProvider.shared.refresh(reason: "screen-unlocked")
|
||||||
lastUpdated = Date()
|
lastUpdated = Date()
|
||||||
updateIdleState(locked: false)
|
updateIdleState(locked: false)
|
||||||
isLocked = false
|
isLocked = false
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class LockScreenPanelManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let screen = NSScreen.main else {
|
guard let screen = currentScreen() else {
|
||||||
print("[\(timestamp())] LockScreenPanelManager: no main screen available")
|
print("[\(timestamp())] LockScreenPanelManager: no main screen available")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ class LockScreenPanelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyOffsetAdjustment(animated: Bool = true) {
|
func applyOffsetAdjustment(animated: Bool = true) {
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
let screenFrame = screen.frame
|
let screenFrame = screen.frame
|
||||||
let newCollapsed = collapsedFrame(for: screenFrame)
|
let newCollapsed = collapsedFrame(for: screenFrame)
|
||||||
collapsedFrame = newCollapsed
|
collapsedFrame = newCollapsed
|
||||||
|
|
@ -225,7 +225,7 @@ class LockScreenPanelManager {
|
||||||
private func handleScreenGeometryChange(reason: String) {
|
private func handleScreenGeometryChange(reason: String) {
|
||||||
guard let window = panelWindow else { return }
|
guard let window = panelWindow else { return }
|
||||||
guard window.isVisible || panelAnimator.isPresented else { return }
|
guard window.isVisible || panelAnimator.isPresented else { return }
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
|
|
||||||
let screenFrame = screen.frame
|
let screenFrame = screen.frame
|
||||||
collapsedFrame = collapsedFrame(for: screenFrame)
|
collapsedFrame = collapsedFrame(for: screenFrame)
|
||||||
|
|
@ -245,4 +245,8 @@ class LockScreenPanelManager {
|
||||||
let originY = baseOriginY + defaultLowering + clampedOffset
|
let originY = baseOriginY + defaultLowering + clampedOffset
|
||||||
return NSRect(x: originX, y: originY, width: collapsedSize.width, height: collapsedSize.height)
|
return NSRect(x: originX, y: originY, width: collapsedSize.width, height: collapsedSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func currentScreen() -> NSScreen? {
|
||||||
|
LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ final class LockScreenReminderWidgetPanelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshPosition(animated: Bool) {
|
func refreshPosition(animated: Bool) {
|
||||||
guard let window, window.isVisible, let screen = NSScreen.main else { return }
|
guard let window, window.isVisible, let screen = currentScreen() else { return }
|
||||||
let target = frame(for: window.frame.size, on: screen)
|
let target = frame(for: window.frame.size, on: screen)
|
||||||
if animated {
|
if animated {
|
||||||
NSAnimationContext.runAnimationGroup { context in
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
|
|
@ -49,7 +49,7 @@ final class LockScreenReminderWidgetPanelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) {
|
private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) {
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
if !makeVisible, window == nil {
|
if !makeVisible, window == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -161,5 +161,9 @@ final class LockScreenReminderWidgetPanelManager {
|
||||||
refreshPosition(animated: false)
|
refreshPosition(animated: false)
|
||||||
print("LockScreenReminderWidgetPanelManager: realigned window due to \(reason)")
|
print("LockScreenReminderWidgetPanelManager: realigned window due to \(reason)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func currentScreen() -> NSScreen? {
|
||||||
|
LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func showWidget() {
|
func showWidget() {
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
let window = ensureWindow()
|
let window = ensureWindow()
|
||||||
let frame = targetFrame(on: screen)
|
let frame = targetFrame(on: screen)
|
||||||
window.setFrame(frame, display: true)
|
window.setFrame(frame, display: true)
|
||||||
|
|
@ -150,7 +150,7 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
|
|
||||||
|
|
||||||
func refreshPosition(animated: Bool) {
|
func refreshPosition(animated: Bool) {
|
||||||
guard let window, window.isVisible, let screen = NSScreen.main else { return }
|
guard let window, window.isVisible, let screen = currentScreen() else { return }
|
||||||
let frame = targetFrame(on: screen)
|
let frame = targetFrame(on: screen)
|
||||||
if animated {
|
if animated {
|
||||||
NSAnimationContext.runAnimationGroup { context in
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
|
|
@ -279,4 +279,8 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
refreshPosition(animated: false)
|
refreshPosition(animated: false)
|
||||||
print("LockScreenTimerWidgetPanelManager: realigned window due to \(reason)")
|
print("LockScreenTimerWidgetPanelManager: realigned window due to \(reason)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func currentScreen() -> NSScreen? {
|
||||||
|
LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,19 +35,32 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
locationProvider.prepareAuthorization()
|
locationProvider.prepareAuthorization()
|
||||||
|
let existingSnapshot = snapshot
|
||||||
|
let hadSnapshot = existingSnapshot != nil
|
||||||
|
var cachedSnapshot = existingSnapshot
|
||||||
|
|
||||||
|
if cachedSnapshot == nil, let payload = latestWeatherPayload {
|
||||||
|
let derivedSnapshot = makeSnapshot(from: payload)
|
||||||
|
snapshot = derivedSnapshot
|
||||||
|
cachedSnapshot = derivedSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
if let cachedSnapshot {
|
||||||
|
deliver(cachedSnapshot, forceShow: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
let needsForceShowOnRefresh = cachedSnapshot == nil
|
||||||
|
let shouldForceRefresh = !hadSnapshot
|
||||||
|
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
let snapshot = await self.refresh(force: self.snapshot == nil)
|
let refreshedSnapshot = await self.refresh(force: shouldForceRefresh)
|
||||||
|
|
||||||
guard LockScreenManager.shared.currentLockStatus else {
|
guard LockScreenManager.shared.currentLockStatus else { return }
|
||||||
LockScreenWeatherPanelManager.shared.hide()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let snapshot {
|
if let refreshedSnapshot {
|
||||||
self.deliver(snapshot, forceShow: true)
|
self.deliver(refreshedSnapshot, forceShow: needsForceShowOnRefresh)
|
||||||
} else {
|
} else if needsForceShowOnRefresh {
|
||||||
LockScreenWeatherPanelManager.shared.hide()
|
LockScreenWeatherPanelManager.shared.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ final class LockScreenWeatherPanelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func render(snapshot: LockScreenWeatherSnapshot, makeVisible: Bool) {
|
private func render(snapshot: LockScreenWeatherSnapshot, makeVisible: Bool) {
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
if !makeVisible, window == nil {
|
if !makeVisible, window == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ final class LockScreenWeatherPanelManager {
|
||||||
|
|
||||||
func refreshPositionForOffsets(animated: Bool = true) {
|
func refreshPositionForOffsets(animated: Bool = true) {
|
||||||
guard let window, let snapshot = lastSnapshot else { return }
|
guard let window, let snapshot = lastSnapshot else { return }
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = currentScreen() else { return }
|
||||||
let size = lastContentSize ?? window.frame.size
|
let size = lastContentSize ?? window.frame.size
|
||||||
let targetFrame = frame(for: size, snapshot: snapshot, on: screen)
|
let targetFrame = frame(for: size, snapshot: snapshot, on: screen)
|
||||||
latestFrame = targetFrame
|
latestFrame = targetFrame
|
||||||
|
|
@ -153,4 +153,8 @@ final class LockScreenWeatherPanelManager {
|
||||||
refreshPositionForOffsets(animated: false)
|
refreshPositionForOffsets(animated: false)
|
||||||
print("LockScreenWeatherPanelManager: realigned window due to \(reason)")
|
print("LockScreenWeatherPanelManager: realigned window due to \(reason)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func currentScreen() -> NSScreen? {
|
||||||
|
LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue