HotFix: DND persistance

This commit is contained in:
Hariharan Mudaliar 2026-03-08 18:34:19 +05:30
commit c7da3f025c
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
2 changed files with 41 additions and 4 deletions

View file

@ -75,7 +75,6 @@ struct ContentView: View {
@Default(.capsLockIndicatorTintMode) var capsLockTintMode
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
@Default(.showDoNotDisturbIndicator) var showDoNotDisturbIndicator
@Default(.focusIndicatorNonPersistent) var focusIndicatorNonPersistent
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
@Default(.enableCapsLockIndicator) var enableCapsLockIndicator
@Default(.enableExtensionLiveActivities) var enableExtensionLiveActivities
@ -713,7 +712,7 @@ struct ContentView: View {
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .download) && vm.notchState == .closed && downloadManager.isDownloading && Defaults[.enableDownloadListener] && !vm.hideOnClosed {
DownloadLiveActivity()
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .doNotDisturb) && vm.notchState == .closed && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && (doNotDisturbManager.isDoNotDisturbActive || Defaults[.focusIndicatorNonPersistent]) && !vm.hideOnClosed && !lockScreenManager.isLocked {
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .doNotDisturb) && vm.notchState == .closed && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && (doNotDisturbManager.isDoNotDisturbActive || doNotDisturbManager.isFocusToastDismissing) && !vm.hideOnClosed && !lockScreenManager.isLocked {
DoNotDisturbLiveActivity()
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .lockScreen) && vm.notchState == .closed && (lockScreenManager.isLocked || !lockScreenManager.isLockIdle) && Defaults[.enableLockScreenLiveActivity] && !vm.hideOnClosed {
LockScreenLiveActivity()
@ -1044,7 +1043,7 @@ struct ContentView: View {
return .recording
}
if enableDoNotDisturbDetection && showDoNotDisturbIndicator && (doNotDisturbManager.isDoNotDisturbActive || focusIndicatorNonPersistent) {
if enableDoNotDisturbDetection && showDoNotDisturbIndicator && doNotDisturbManager.isDoNotDisturbActive {
let mode = FocusModeType.resolve(identifier: doNotDisturbManager.currentFocusModeIdentifier, name: doNotDisturbManager.currentFocusModeName)
return .focus(mode)
}

View file

@ -33,6 +33,11 @@ final class DoNotDisturbManager: ObservableObject {
// Used by the brief-toast UI to show an ON toast when the active mode switches while Focus stays enabled.
@Published var focusToastTrigger: UUID = UUID()
/// Briefly `true` after focus turns OFF while toast mode is enabled,
/// keeping the standalone DoNotDisturbLiveActivity mounted long enough
/// for the "Off" dismissal animation to play out.
@Published private(set) var isFocusToastDismissing: Bool = false
private let notificationCenter = DistributedNotificationCenter.default()
private let metadataExtractionQueue = DispatchQueue(label: "com.dynamicisland.focus.metadata", qos: .userInitiated)
private let pollingQueue = DispatchQueue(label: "com.dynamicisland.focus.polling", qos: .utility)
@ -56,6 +61,9 @@ final class DoNotDisturbManager: ObservableObject {
/// Delayed task that clears retained metadata after the OFF animation completes.
private var metadataClearTask: Task<Void, Never>?
/// Task that resets `isFocusToastDismissing` after the OFF toast animation.
private var toastDismissTask: Task<Void, Never>?
private init() {
focusLogStream.onMetadataUpdate = { [weak self] identifier, name in
self?.handleLogMetadataUpdate(identifier: identifier, name: name)
@ -112,6 +120,7 @@ final class DoNotDisturbManager: ObservableObject {
stateVerificationTask = nil
metadataClearTask?.cancel()
metadataClearTask = nil
cancelFocusToastDismiss()
isMonitoring = false
DispatchQueue.main.async {
@ -243,10 +252,12 @@ final class DoNotDisturbManager: ObservableObject {
self.currentFocusModeIdentifier = previousIdentifier
self.currentFocusModeName = previousName
self.scheduleMetadataClear()
self.beginFocusToastDismissIfNeeded()
} else {
// Focus turned ON — cancel any pending metadata clear.
// Focus turned ON — cancel any pending metadata clear and toast dismiss.
self.metadataClearTask?.cancel()
self.metadataClearTask = nil
self.cancelFocusToastDismiss()
}
// Start or stop periodic verification based on new state.
@ -279,6 +290,7 @@ final class DoNotDisturbManager: ObservableObject {
self.isDoNotDisturbActive = false
}
self.scheduleMetadataClear()
self.beginFocusToastDismissIfNeeded()
debugPrint("[DoNotDisturbManager] State verification: focus no longer active, resetting.")
break
}
@ -321,6 +333,32 @@ final class DoNotDisturbManager: ObservableObject {
}
}
/// Sets `isFocusToastDismissing` to `true` for ~2.5 seconds when focus turns
/// OFF while toast mode is enabled. This keeps the standalone DND live activity
/// mounted long enough for the "Off" toast animation, without permanently
/// occupying the live-activity slot.
private func beginFocusToastDismissIfNeeded() {
guard Defaults[.focusIndicatorNonPersistent] else { return }
toastDismissTask?.cancel()
isFocusToastDismissing = true
toastDismissTask = Task { @MainActor [weak self] in
try? await Task.sleep(for: .milliseconds(2500))
guard !Task.isCancelled, let self else { return }
withAnimation(.smooth(duration: 0.25)) {
self.isFocusToastDismissing = false
}
}
}
/// Cancels any in-progress toast dismiss countdown (e.g. when focus turns back ON).
private func cancelFocusToastDismiss() {
toastDismissTask?.cancel()
toastDismissTask = nil
isFocusToastDismissing = false
}
private func handleLogMetadataUpdate(identifier: String?, name: String?) {
metadataExtractionQueue.async { [weak self] in
guard let self = self else { return }