mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Transient DND Mode
This commit is contained in:
parent
4f36b69abf
commit
2ce6a3ae96
5 changed files with 83 additions and 16 deletions
|
|
@ -471,7 +471,7 @@ struct ContentView: View {
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .download) && vm.notchState == .closed && downloadManager.isDownloading && Defaults[.enableDownloadListener] && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .download) && vm.notchState == .closed && downloadManager.isDownloading && Defaults[.enableDownloadListener] && !vm.hideOnClosed {
|
||||||
DownloadLiveActivity()
|
DownloadLiveActivity()
|
||||||
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
|
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .doNotDisturb) && vm.notchState == .closed && doNotDisturbManager.isDoNotDisturbActive && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && !vm.hideOnClosed && !lockScreenManager.isLocked {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .doNotDisturb) && vm.notchState == .closed && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && (doNotDisturbManager.isDoNotDisturbActive || Defaults[.focusIndicatorNonPersistent]) && !vm.hideOnClosed && !lockScreenManager.isLocked {
|
||||||
DoNotDisturbLiveActivity()
|
DoNotDisturbLiveActivity()
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .lockScreen) && vm.notchState == .closed && (lockScreenManager.isLocked || !lockScreenManager.isLockIdle) && Defaults[.enableLockScreenLiveActivity] && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .lockScreen) && vm.notchState == .closed && (lockScreenManager.isLocked || !lockScreenManager.isLockIdle) && Defaults[.enableLockScreenLiveActivity] && !vm.hideOnClosed {
|
||||||
LockScreenLiveActivity()
|
LockScreenLiveActivity()
|
||||||
|
|
|
||||||
|
|
@ -10731,6 +10731,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Labels are forced to compact on/off text while brief toast mode is enabled." : {
|
||||||
|
"comment" : "A help text explaining why the \"Show Focus Label\" toggle is disabled when the \"Show Focus as brief toast\" toggle is enabled.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Last Updated" : {
|
"Last Updated" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -17451,6 +17455,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Show the active Focus name inside the indicator." : {
|
||||||
|
"comment" : "A description of why the \"Show Focus Label\" toggle is disabled when the \"Show Focus as brief toast\" toggle is enabled.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Show timer name" : {
|
"Show timer name" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
@ -20378,6 +20386,10 @@
|
||||||
},
|
},
|
||||||
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"When enabled, Focus appears briefly (on/off) and then collapses instead of staying visible." : {
|
||||||
|
"comment" : "A help text for the setting that allows the focus indicator to collapse instead of staying visible.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"When enabled, stats monitoring stops a few seconds after the notch closes." : {
|
"When enabled, stats monitoring stops a few seconds after the notch closes." : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
@ObservedObject var manager = DoNotDisturbManager.shared
|
@ObservedObject var manager = DoNotDisturbManager.shared
|
||||||
@Default(.showDoNotDisturbLabel) private var showLabelSetting
|
@Default(.showDoNotDisturbLabel) private var showLabelSetting
|
||||||
|
@Default(.focusIndicatorNonPersistent) private var focusToastMode
|
||||||
|
|
||||||
@State private var isExpanded = false
|
@State private var isExpanded = false
|
||||||
@State private var showInactiveIcon = false
|
@State private var showInactiveIcon = false
|
||||||
|
|
@ -53,10 +54,15 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var labelWingWidth: CGFloat {
|
private var labelWingWidth: CGFloat {
|
||||||
if shouldShowLabel {
|
guard shouldShowLabel else {
|
||||||
return max(desiredLabelWidth, minimalWingWidth)
|
return focusToastMode ? 0 : ((isExpanded || showInactiveIcon) ? minimalWingWidth : 0)
|
||||||
}
|
}
|
||||||
return (isExpanded || showInactiveIcon) ? minimalWingWidth : 0
|
|
||||||
|
if focusToastMode {
|
||||||
|
return max(labelIntrinsicWidth + 26, minimalWingWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(desiredLabelWidth, minimalWingWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var minimalWingWidth: CGFloat {
|
private var minimalWingWidth: CGFloat {
|
||||||
|
|
@ -76,7 +82,7 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var shouldShowLabel: Bool {
|
private var shouldShowLabel: Bool {
|
||||||
showLabelSetting && isExpanded && !labelText.isEmpty
|
focusToastMode ? (isExpanded && !labelText.isEmpty) : (showLabelSetting && isExpanded && !labelText.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Focus metadata
|
// MARK: - Focus metadata
|
||||||
|
|
@ -93,16 +99,23 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var labelText: String {
|
private var labelText: String {
|
||||||
let trimmed = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
if focusToastMode {
|
||||||
if !trimmed.isEmpty {
|
return manager.isDoNotDisturbActive ? "On" : "Off"
|
||||||
return trimmed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallback = focusMode.displayName
|
let trimmed = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if focusMode == .doNotDisturb {
|
if showLabelSetting {
|
||||||
return "Do Not Disturb"
|
if !trimmed.isEmpty {
|
||||||
|
return trimmed
|
||||||
|
} else if focusMode == .doNotDisturb {
|
||||||
|
return "Do Not Disturb"
|
||||||
|
} else {
|
||||||
|
let fallback = focusMode.displayName
|
||||||
|
return fallback.isEmpty ? "Focus" : fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fallback.isEmpty ? "Focus" : fallback
|
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var accessibilityDescription: String {
|
private var accessibilityDescription: String {
|
||||||
|
|
@ -158,6 +171,7 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
||||||
.foregroundColor(activeAccentColor)
|
.foregroundColor(activeAccentColor)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.8)
|
||||||
.contentTransition(.opacity)
|
.contentTransition(.opacity)
|
||||||
.background(
|
.background(
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
|
|
@ -181,12 +195,15 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
|
withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
|
||||||
isExpanded = true
|
isExpanded = true
|
||||||
}
|
}
|
||||||
|
if focusToastMode {
|
||||||
|
scheduleTransientCollapse()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleFocusStateChange(_ oldValue: Bool, _ isActive: Bool) {
|
private func handleFocusStateChange(_ oldValue: Bool, _ isActive: Bool) {
|
||||||
|
cancelPendingTasks()
|
||||||
if isActive {
|
if isActive {
|
||||||
cancelPendingTasks()
|
|
||||||
withAnimation(.smooth(duration: 0.2)) {
|
withAnimation(.smooth(duration: 0.2)) {
|
||||||
showInactiveIcon = false
|
showInactiveIcon = false
|
||||||
}
|
}
|
||||||
|
|
@ -194,12 +211,18 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
iconScale = 1.0
|
iconScale = 1.0
|
||||||
isExpanded = true
|
isExpanded = true
|
||||||
}
|
}
|
||||||
|
if focusToastMode {
|
||||||
|
scheduleTransientCollapse()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
triggerInactiveAnimation()
|
triggerInactiveAnimation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func triggerInactiveAnimation() {
|
private func triggerInactiveAnimation() {
|
||||||
|
withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
|
||||||
|
isExpanded = true
|
||||||
|
}
|
||||||
withAnimation(.smooth(duration: 0.2)) {
|
withAnimation(.smooth(duration: 0.2)) {
|
||||||
showInactiveIcon = true
|
showInactiveIcon = true
|
||||||
}
|
}
|
||||||
|
|
@ -208,9 +231,16 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
iconScale = 1.2
|
iconScale = 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toastMode = focusToastMode
|
||||||
scaleResetTask?.cancel()
|
scaleResetTask?.cancel()
|
||||||
scaleResetTask = Task { @MainActor in
|
scaleResetTask = Task { @MainActor in
|
||||||
try? await Task.sleep(for: .milliseconds(650))
|
try? await Task.sleep(for: .milliseconds(toastMode ? 650 : 450))
|
||||||
|
withAnimation(.interpolatingSpring(stiffness: 180, damping: 18)) {
|
||||||
|
iconScale = 1.0
|
||||||
|
}
|
||||||
|
if toastMode {
|
||||||
|
return
|
||||||
|
}
|
||||||
withAnimation(.smooth(duration: 0.2)) {
|
withAnimation(.smooth(duration: 0.2)) {
|
||||||
showInactiveIcon = false
|
showInactiveIcon = false
|
||||||
}
|
}
|
||||||
|
|
@ -218,13 +248,17 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
|
|
||||||
collapseTask?.cancel()
|
collapseTask?.cancel()
|
||||||
collapseTask = Task { @MainActor in
|
collapseTask = Task { @MainActor in
|
||||||
try? await Task.sleep(for: .milliseconds(320))
|
try? await Task.sleep(for: .milliseconds(focusToastMode ? 900 : 320))
|
||||||
withAnimation(.smooth(duration: 0.32)) {
|
withAnimation(.smooth(duration: 0.32)) {
|
||||||
isExpanded = false
|
isExpanded = false
|
||||||
|
if focusToastMode {
|
||||||
|
showInactiveIcon = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupTask?.cancel()
|
cleanupTask?.cancel()
|
||||||
|
guard !focusToastMode else { return }
|
||||||
cleanupTask = Task { @MainActor in
|
cleanupTask = Task { @MainActor in
|
||||||
try? await Task.sleep(for: .milliseconds(650))
|
try? await Task.sleep(for: .milliseconds(650))
|
||||||
withAnimation(.smooth(duration: 0.2)) {
|
withAnimation(.smooth(duration: 0.2)) {
|
||||||
|
|
@ -233,6 +267,19 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func scheduleTransientCollapse() {
|
||||||
|
collapseTask?.cancel()
|
||||||
|
cleanupTask?.cancel()
|
||||||
|
|
||||||
|
collapseTask = Task { @MainActor in
|
||||||
|
try? await Task.sleep(for: .milliseconds(1150))
|
||||||
|
withAnimation(.smooth(duration: 0.32)) {
|
||||||
|
isExpanded = false
|
||||||
|
showInactiveIcon = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func cancelPendingTasks() {
|
private func cancelPendingTasks() {
|
||||||
scaleResetTask?.cancel()
|
scaleResetTask?.cancel()
|
||||||
collapseTask?.cancel()
|
collapseTask?.cancel()
|
||||||
|
|
|
||||||
|
|
@ -2056,6 +2056,7 @@ struct LiveActivitiesSettings: View {
|
||||||
|
|
||||||
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
||||||
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
|
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
|
||||||
|
@Default(.focusIndicatorNonPersistent) var focusIndicatorNonPersistent
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
SettingsTab.liveActivities.highlightID(for: title)
|
SettingsTab.liveActivities.highlightID(for: title)
|
||||||
|
|
@ -2104,9 +2105,15 @@ struct LiveActivitiesSettings: View {
|
||||||
.settingsHighlight(id: highlightID("Show Focus Indicator"))
|
.settingsHighlight(id: highlightID("Show Focus Indicator"))
|
||||||
|
|
||||||
Defaults.Toggle("Show Focus Label", key: .showDoNotDisturbLabel)
|
Defaults.Toggle("Show Focus Label", key: .showDoNotDisturbLabel)
|
||||||
.disabled(!enableDoNotDisturbDetection)
|
.disabled(!enableDoNotDisturbDetection || focusIndicatorNonPersistent)
|
||||||
|
.help(focusIndicatorNonPersistent ? "Labels are forced to compact on/off text while brief toast mode is enabled." : "Show the active Focus name inside the indicator.")
|
||||||
.settingsHighlight(id: highlightID("Show Focus Label"))
|
.settingsHighlight(id: highlightID("Show Focus Label"))
|
||||||
|
|
||||||
|
Defaults.Toggle("Show Focus as brief toast", key: .focusIndicatorNonPersistent)
|
||||||
|
.disabled(!enableDoNotDisturbDetection)
|
||||||
|
.settingsHighlight(id: highlightID("Show Focus as brief toast"))
|
||||||
|
.help("When enabled, Focus appears briefly (on/off) and then collapses instead of staying visible.")
|
||||||
|
|
||||||
if doNotDisturbManager.isMonitoring {
|
if doNotDisturbManager.isMonitoring {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Focus Status")
|
Text("Focus Status")
|
||||||
|
|
|
||||||
|
|
@ -616,6 +616,7 @@ extension Defaults.Keys {
|
||||||
static let enableDoNotDisturbDetection = Key<Bool>("enableDoNotDisturbDetection", default: true)
|
static let enableDoNotDisturbDetection = Key<Bool>("enableDoNotDisturbDetection", default: true)
|
||||||
static let showDoNotDisturbIndicator = Key<Bool>("showDoNotDisturbIndicator", default: true)
|
static let showDoNotDisturbIndicator = Key<Bool>("showDoNotDisturbIndicator", default: true)
|
||||||
static let showDoNotDisturbLabel = Key<Bool>("showDoNotDisturbLabel", default: true)
|
static let showDoNotDisturbLabel = Key<Bool>("showDoNotDisturbLabel", default: true)
|
||||||
|
static let focusIndicatorNonPersistent = Key<Bool>("focusIndicatorNonPersistent", default: false)
|
||||||
|
|
||||||
// MARK: Privacy Indicators (Camera & Microphone Detection)
|
// MARK: Privacy Indicators (Camera & Microphone Detection)
|
||||||
static let enableCameraDetection = Key<Bool>("enableCameraDetection", default: true)
|
static let enableCameraDetection = Key<Bool>("enableCameraDetection", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue