Lock Screen indicator for focus mode

This commit is contained in:
Hariharan Mudaliar 2025-12-13 23:29:35 +05:30
commit 3fd190f24a
No known key found for this signature in database
GPG key ID: D04F1C2B9194DDFC
4 changed files with 103 additions and 9 deletions

View file

@ -7084,6 +7084,9 @@
}, },
"Displays play/pause and skip buttons beside the notch while music is active. Disabled by default." : { "Displays play/pause and skip buttons beside the notch while music is active. Disabled by default." : {
},
"Displays the current Focus state above the weather capsule whenever Focus detection is enabled." : {
}, },
"Do Not Disturb" : { "Do Not Disturb" : {
@ -8847,9 +8850,16 @@
}, },
"Fit to Notch" : { "Fit to Notch" : {
},
"Focus active: %@" : {
"comment" : "An accessibility label for the focus widget, describing the currently active focus mode.",
"isCommentAutoGenerated" : true
}, },
"Focus Status" : { "Focus Status" : {
},
"Focus Widget" : {
}, },
"Free · %@" : { "Free · %@" : {
"comment" : "A label displaying the amount of free storage on a disk device. The argument is the number of free bytes on the disk.", "comment" : "A label displaying the amount of free storage on a disk device. The argument is the number of free bytes on the disk.",

View file

@ -1,8 +1,13 @@
import SwiftUI import SwiftUI
import Defaults
struct LockScreenWeatherWidget: View { struct LockScreenWeatherWidget: View {
let snapshot: LockScreenWeatherSnapshot let snapshot: LockScreenWeatherSnapshot
@ObservedObject private var focusManager = DoNotDisturbManager.shared
@Default(.enableDoNotDisturbDetection) private var focusDetectionEnabled
@Default(.showDoNotDisturbIndicator) private var focusIndicatorEnabled
@Default(.enableLockScreenFocusWidget) private var lockScreenFocusWidgetEnabled
private let inlinePrimaryFont = Font.system(size: 22, weight: .semibold, design: .rounded) private let inlinePrimaryFont = Font.system(size: 22, weight: .semibold, design: .rounded)
private let inlineSecondaryFont = Font.system(size: 13, weight: .medium, design: .rounded) private let inlineSecondaryFont = Font.system(size: 13, weight: .medium, design: .rounded)
@ -20,6 +25,24 @@ struct LockScreenWeatherWidget: View {
} }
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: focusWidgetSpacing) {
if shouldShowFocusWidget {
focusWidget
}
mainWidgetRow
}
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.white.opacity(0.65))
.padding(.horizontal, 10)
.padding(.top, topPadding)
.padding(.bottom, bottomPadding)
.background(Color.clear)
.shadow(color: .black.opacity(0.35), radius: 8, x: 0, y: 3)
.accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityLabel)
}
private var mainWidgetRow: some View {
HStack(alignment: stackAlignment, spacing: stackSpacing) { HStack(alignment: stackAlignment, spacing: stackSpacing) {
if let charging = snapshot.charging { if let charging = snapshot.charging {
chargingSegment(for: charging) chargingSegment(for: charging)
@ -43,15 +66,6 @@ struct LockScreenWeatherWidget: View {
locationSegment locationSegment
} }
} }
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.white.opacity(0.65))
.padding(.horizontal, 10)
.padding(.top, topPadding)
.padding(.bottom, bottomPadding)
.background(Color.clear)
.shadow(color: .black.opacity(0.35), radius: 8, x: 0, y: 3)
.accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityLabel)
} }
@ViewBuilder @ViewBuilder
@ -336,6 +350,61 @@ struct LockScreenWeatherWidget: View {
snapshot.showsLocation && (snapshot.locationName?.isEmpty == false) snapshot.showsLocation && (snapshot.locationName?.isEmpty == false)
} }
private var focusWidgetSpacing: CGFloat {
guard shouldShowFocusWidget else { return 0 }
return isInline ? 14 : 20
}
private var shouldShowFocusWidget: Bool {
lockScreenFocusWidgetEnabled &&
focusDetectionEnabled &&
focusIndicatorEnabled &&
focusManager.isDoNotDisturbActive &&
!focusDisplayName.isEmpty
}
private var focusDisplayName: String {
let trimmed = focusManager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty {
return trimmed
}
if focusMode == .doNotDisturb {
return "Do Not Disturb"
}
let fallback = focusMode.displayName
return fallback.isEmpty ? "Focus" : fallback
}
private var focusMode: FocusModeType {
FocusModeType.resolve(
identifier: focusManager.currentFocusModeIdentifier,
name: focusManager.currentFocusModeName
)
}
private var focusIcon: Image {
focusMode
.resolvedActiveIcon(usePrivateSymbol: true)
.renderingMode(.template)
}
private var focusWidget: some View {
HStack(alignment: .center, spacing: 8) {
focusIcon
.font(.system(size: 20, weight: .semibold))
.frame(width: 26, height: 26)
Text(focusDisplayName)
.font(inlinePrimaryFont)
.lineLimit(1)
.minimumScaleFactor(0.85)
}
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
.padding(.horizontal, 2)
.accessibilityLabel("Focus active: \(focusDisplayName)")
}
private func chargingIconName(for info: LockScreenWeatherSnapshot.ChargingInfo) -> String? { private func chargingIconName(for info: LockScreenWeatherSnapshot.ChargingInfo) -> String? {
let icon = info.iconName let icon = info.iconName
return icon.isEmpty ? nil : icon return icon.isEmpty ? nil : icon
@ -549,6 +618,10 @@ struct LockScreenWeatherWidget: View {
components.append(accessibilityBatteryText(for: battery)) components.append(accessibilityBatteryText(for: battery))
} }
if shouldShowFocusWidget {
components.append("Focus active: \(focusDisplayName)")
}
return components.joined(separator: ". ") return components.joined(separator: ". ")
} }

View file

@ -2583,6 +2583,7 @@ struct LockScreenSettings: View {
@Default(.enableLockScreenMediaWidget) private var enableLockScreenMediaWidget @Default(.enableLockScreenMediaWidget) private var enableLockScreenMediaWidget
@Default(.enableLockScreenTimerWidget) private var enableLockScreenTimerWidget @Default(.enableLockScreenTimerWidget) private var enableLockScreenTimerWidget
@Default(.enableLockScreenWeatherWidget) private var enableLockScreenWeatherWidget @Default(.enableLockScreenWeatherWidget) private var enableLockScreenWeatherWidget
@Default(.enableLockScreenFocusWidget) private var enableLockScreenFocusWidget
@Default(.lockScreenWeatherWidgetStyle) private var lockScreenWeatherWidgetStyle @Default(.lockScreenWeatherWidgetStyle) private var lockScreenWeatherWidgetStyle
@Default(.lockScreenWeatherProviderSource) private var lockScreenWeatherProviderSource @Default(.lockScreenWeatherProviderSource) private var lockScreenWeatherProviderSource
@Default(.lockScreenWeatherTemperatureUnit) private var lockScreenWeatherTemperatureUnit @Default(.lockScreenWeatherTemperatureUnit) private var lockScreenWeatherTemperatureUnit
@ -2738,6 +2739,15 @@ struct LockScreenSettings: View {
Text("Enable the weather capsule and configure its layout, provider, units, and optional battery/AQI indicators.") Text("Enable the weather capsule and configure its layout, provider, units, and optional battery/AQI indicators.")
} }
Section {
Defaults.Toggle("Show focus widget", key: .enableLockScreenFocusWidget)
.settingsHighlight(id: highlightID("Show focus widget"))
} header: {
Text("Focus Widget")
} footer: {
Text("Displays the current Focus state above the weather capsule whenever Focus detection is enabled.")
}
LockScreenPositioningControls() LockScreenPositioningControls()
Section { Section {

View file

@ -452,6 +452,7 @@ extension Defaults.Keys {
// Enable lock screen media widget (shows the standalone panel when screen is locked) // Enable lock screen media widget (shows the standalone panel when screen is locked)
static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true) static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true)
static let enableLockScreenWeatherWidget = Key<Bool>("enableLockScreenWeatherWidget", default: true) static let enableLockScreenWeatherWidget = Key<Bool>("enableLockScreenWeatherWidget", default: true)
static let enableLockScreenFocusWidget = Key<Bool>("enableLockScreenFocusWidget", default: true)
static let enableLockScreenReminderWidget = Key<Bool>("enableLockScreenReminderWidget", default: true) static let enableLockScreenReminderWidget = Key<Bool>("enableLockScreenReminderWidget", default: true)
static let enableLockScreenTimerWidget = Key<Bool>("enableLockScreenTimerWidget", default: true) static let enableLockScreenTimerWidget = Key<Bool>("enableLockScreenTimerWidget", default: true)
static let lockScreenWeatherRefreshInterval = Key<TimeInterval>("lockScreenWeatherRefreshInterval", default: 30 * 60) static let lockScreenWeatherRefreshInterval = Key<TimeInterval>("lockScreenWeatherRefreshInterval", default: 30 * 60)