Fixed the width and color

This commit is contained in:
Hariharan Mudaliar 2025-11-10 02:19:31 +05:30
commit 39c135cfd7
6 changed files with 217 additions and 73 deletions

View file

@ -8497,10 +8497,6 @@
}, },
"Fit to Notch" : { "Fit to Notch" : {
},
"Focus mode active" : {
"comment" : "An accessibility label for the focus indicator.",
"isCommentAutoGenerated" : true
}, },
"Focus Status" : { "Focus Status" : {

View file

@ -2,112 +2,211 @@
// DoNotDisturbLiveActivity.swift // DoNotDisturbLiveActivity.swift
// DynamicIsland // DynamicIsland
// //
// Renders the closed-notch Focus indicator with a purple moon badge. // Renders the closed-notch Focus indicator with per-mode colours and
// an icon-first layout that collapses gracefully when Focus ends.
// //
import Defaults
import SwiftUI import SwiftUI
struct DoNotDisturbLiveActivity: View { struct DoNotDisturbLiveActivity: View {
@EnvironmentObject var vm: DynamicIslandViewModel @EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var manager = DoNotDisturbManager.shared @ObservedObject var manager = DoNotDisturbManager.shared
@State private var isExpanded = false @Default(.showDoNotDisturbLabel) private var showLabelSetting
private let focusColor = Color.purple @State private var isExpanded = false
@State private var showInactiveIcon = false
@State private var iconScale: CGFloat = 1.0
@State private var scaleResetTask: Task<Void, Never>?
@State private var collapseTask: Task<Void, Never>?
@State private var cleanupTask: Task<Void, Never>?
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {
leadingWing iconWing
.frame(width: leadingWingWidth, height: wingHeight) .frame(width: iconWingWidth, height: wingHeight)
Rectangle() Rectangle()
.fill(Color.black) .fill(Color.black)
.frame(width: vm.closedNotchSize.width) .frame(width: vm.closedNotchSize.width)
trailingWing labelWing
.frame(width: trailingWingWidth, height: wingHeight) .frame(width: labelWingWidth, height: wingHeight)
} }
.frame(height: vm.effectiveClosedNotchHeight) .frame(height: vm.effectiveClosedNotchHeight)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityDescription) .accessibilityLabel(accessibilityDescription)
.onAppear { .onAppear(perform: handleInitialState)
withAnimation(.smooth(duration: 0.35)) { .onChange(of: manager.isDoNotDisturbActive, handleFocusStateChange)
isExpanded = true .onDisappear(perform: cancelPendingTasks)
}
}
.onChange(of: manager.isDoNotDisturbActive) { _, isActive in
withAnimation(.smooth(duration: 0.35)) {
isExpanded = isActive
}
}
} }
// MARK: - Layout helpers
private var wingHeight: CGFloat { private var wingHeight: CGFloat {
max(vm.effectiveClosedNotchHeight - 10, 20) max(vm.effectiveClosedNotchHeight - 10, 20)
} }
private var leadingWingWidth: CGFloat { private var iconWingWidth: CGFloat {
isExpanded ? max(vm.effectiveClosedNotchHeight - 10, 20) : 0 (isExpanded || showInactiveIcon) ? max(vm.effectiveClosedNotchHeight - 12, 24) : 0
} }
private var trailingWingWidth: CGFloat { private var labelWingWidth: CGFloat {
guard isExpanded else { return 0 } shouldShowLabel ? max(vm.closedNotchSize.width * 0.45, 110) : 0
return max(vm.closedNotchSize.width * 0.65, 120)
} }
private var focusSymbol: String { private var shouldShowLabel: Bool {
if let symbol = FocusModeType(rawValue: manager.currentFocusModeIdentifier)?.sfSymbol { showLabelSetting && isExpanded && !labelText.isEmpty
return symbol
}
return FocusModeType.doNotDisturb.sfSymbol
} }
private var focusLabel: String { // MARK: - Focus metadata
if !manager.currentFocusModeName.isEmpty {
return manager.currentFocusModeName private var focusMode: FocusModeType {
FocusModeType(rawValue: manager.currentFocusModeIdentifier) ?? .doNotDisturb
} }
return FocusModeType(rawValue: manager.currentFocusModeIdentifier)?.displayName ?? "Do Not Disturb"
private var activeAccentColor: Color {
focusMode.accentColor
}
private var labelText: String {
let trimmed = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? "Focus" : trimmed
} }
private var accessibilityDescription: String { private var accessibilityDescription: String {
"Focus mode active: \(focusLabel)" if manager.isDoNotDisturbActive {
return "Focus active: \(labelText)"
} else {
return "Focus inactive"
}
} }
private var leadingWing: some View { private var currentIconName: String {
if manager.isDoNotDisturbActive {
return focusMode.sfSymbol
} else if showInactiveIcon {
return focusMode.inactiveSymbol
} else {
return focusMode.sfSymbol
}
}
private var currentIconColor: Color {
if manager.isDoNotDisturbActive {
return activeAccentColor
} else if showInactiveIcon {
return .white
} else {
return activeAccentColor
}
}
private var iconBackground: Color {
if manager.isDoNotDisturbActive {
return activeAccentColor.opacity(0.18)
} else if showInactiveIcon {
return Color.white.opacity(0.10)
} else {
return Color.black.opacity(0.001)
}
}
// MARK: - Subviews
private var iconWing: some View {
Color.clear Color.clear
.background { .overlay(alignment: .center) {
if isExpanded { if iconWingWidth > 0 {
ZStack {
RoundedRectangle(cornerRadius: 6, style: .continuous) RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(focusColor.opacity(0.18)) .fill(iconBackground)
.overlay {
Image(systemName: focusSymbol) Image(systemName: currentIconName)
.font(.system(size: 12, weight: .semibold)) .font(.system(size: 13, weight: .semibold))
.foregroundColor(focusColor) .foregroundColor(currentIconColor)
} .scaleEffect(iconScale)
.frame(width: leadingWingWidth, height: wingHeight) .animation(.none, value: iconScale)
} }
} }
} }
.animation(.smooth(duration: 0.3), value: iconWingWidth)
}
private var trailingWing: some View { private var labelWing: some View {
Color.clear Color.clear
.background { .overlay(alignment: .trailing) {
if isExpanded { if shouldShowLabel {
HStack { Text(labelText)
Text(focusLabel)
.font(.system(size: 12, weight: .semibold, design: .rounded)) .font(.system(size: 12, weight: .semibold, design: .rounded))
.foregroundColor(focusColor) .foregroundColor(activeAccentColor)
.lineLimit(1) .lineLimit(1)
.padding(.horizontal, 12) .contentTransition(.opacity)
.padding(.vertical, 6) .padding(.horizontal, 4)
}
.frame(maxWidth: .infinity, alignment: .trailing)
.background(
Capsule(style: .continuous)
.fill(focusColor.opacity(0.18))
)
} }
} }
.animation(.smooth(duration: 0.3), value: shouldShowLabel)
}
// MARK: - State transitions
private func handleInitialState() {
if manager.isDoNotDisturbActive {
withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
isExpanded = true
}
}
}
private func handleFocusStateChange(_ oldValue: Bool, _ isActive: Bool) {
if isActive {
cancelPendingTasks()
showInactiveIcon = false
withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
iconScale = 1.0
isExpanded = true
}
} else {
triggerInactiveAnimation()
}
}
private func triggerInactiveAnimation() {
showInactiveIcon = true
withAnimation(.interpolatingSpring(stiffness: 220, damping: 12)) {
iconScale = 1.2
}
scaleResetTask?.cancel()
scaleResetTask = Task { @MainActor in
try? await Task.sleep(for: .milliseconds(180))
withAnimation(.spring(response: 0.28, dampingFraction: 0.75)) {
iconScale = 1.0
}
}
collapseTask?.cancel()
collapseTask = Task { @MainActor in
try? await Task.sleep(for: .milliseconds(320))
withAnimation(.smooth(duration: 0.32)) {
isExpanded = false
}
}
cleanupTask?.cancel()
cleanupTask = Task { @MainActor in
try? await Task.sleep(for: .milliseconds(650))
showInactiveIcon = false
}
}
private func cancelPendingTasks() {
scaleResetTask?.cancel()
collapseTask?.cancel()
cleanupTask?.cancel()
scaleResetTask = nil
collapseTask = nil
cleanupTask = nil
} }
} }

View file

@ -175,6 +175,7 @@ struct GeneralSettings: View {
@Default(.showRecordingIndicator) var showRecordingIndicator @Default(.showRecordingIndicator) var showRecordingIndicator
@Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection @Default(.enableDoNotDisturbDetection) var enableDoNotDisturbDetection
@Default(.showDoNotDisturbIndicator) var showDoNotDisturbIndicator @Default(.showDoNotDisturbIndicator) var showDoNotDisturbIndicator
@Default(.showDoNotDisturbLabel) var showDoNotDisturbLabel
@Default(.enableMinimalisticUI) var enableMinimalisticUI @Default(.enableMinimalisticUI) var enableMinimalisticUI
@Default(.lockScreenGlassStyle) var lockScreenGlassStyle @Default(.lockScreenGlassStyle) var lockScreenGlassStyle
@Default(.lockScreenShowAppIcon) var lockScreenShowAppIcon @Default(.lockScreenShowAppIcon) var lockScreenShowAppIcon
@ -274,6 +275,9 @@ struct GeneralSettings: View {
Defaults.Toggle("Show Focus Indicator", key: .showDoNotDisturbIndicator) Defaults.Toggle("Show Focus Indicator", key: .showDoNotDisturbIndicator)
.disabled(!enableDoNotDisturbDetection) .disabled(!enableDoNotDisturbDetection)
Defaults.Toggle("Show Focus Label", key: .showDoNotDisturbLabel)
.disabled(!enableDoNotDisturbDetection)
if doNotDisturbManager.isMonitoring { if doNotDisturbManager.isMonitoring {
HStack { HStack {
Text("Focus Status") Text("Focus Status")

View file

@ -10,31 +10,39 @@ import SwiftUI
struct FocusIndicator: View { struct FocusIndicator: View {
@ObservedObject var manager = DoNotDisturbManager.shared @ObservedObject var manager = DoNotDisturbManager.shared
private let focusColor = Color.purple
var body: some View { var body: some View {
Capsule() Capsule()
.fill(Color.black) .fill(Color.black)
.overlay { .overlay {
ZStack { ZStack {
Circle() Circle()
.fill(focusColor.opacity(0.2)) .fill(accentColor.opacity(0.2))
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Image(systemName: focusSymbol) Image(systemName: focusSymbol)
.font(.system(size: 12, weight: .semibold)) .font(.system(size: 12, weight: .semibold))
.foregroundStyle(focusColor) .foregroundStyle(accentColor)
} }
} }
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel("Focus mode active") .accessibilityLabel(accessibilityLabel)
}
private var focusMode: FocusModeType {
FocusModeType(rawValue: manager.currentFocusModeIdentifier) ?? .doNotDisturb
} }
private var focusSymbol: String { private var focusSymbol: String {
if let symbol = FocusModeType(rawValue: manager.currentFocusModeIdentifier)?.sfSymbol { focusMode.sfSymbol
return symbol
} }
return FocusModeType.doNotDisturb.sfSymbol
private var accentColor: Color {
focusMode.accentColor
}
private var accessibilityLabel: String {
let name = manager.currentFocusModeName.isEmpty ? focusMode.displayName : manager.currentFocusModeName
return "Focus active: \(name)"
} }
} }

View file

@ -187,4 +187,40 @@ enum FocusModeType: String {
case .unknown: return "moon.zzz.fill" case .unknown: return "moon.zzz.fill"
} }
} }
var accentColor: Color {
switch self {
case .doNotDisturb:
return Color(red: 0.370, green: 0.360, blue: 0.902)
case .work:
return Color(red: 0.133, green: 0.475, blue: 0.992)
case .personal:
return Color(red: 0.937, green: 0.282, blue: 0.624)
case .sleep:
return Color(red: 0.341, green: 0.384, blue: 0.980)
case .driving:
return Color(red: 0.988, green: 0.561, blue: 0.153)
case .fitness:
return Color(red: 0.176, green: 0.804, blue: 0.459)
case .gaming:
return Color(red: 0.639, green: 0.329, blue: 0.937)
case .mindfulness:
return Color(red: 0.239, green: 0.718, blue: 0.682)
case .reading:
return Color(red: 0.239, green: 0.596, blue: 0.965)
case .custom:
return Color(red: 0.513, green: 0.478, blue: 0.965)
case .unknown:
return Color(red: 0.370, green: 0.360, blue: 0.902)
}
}
var inactiveSymbol: String {
switch self {
case .doNotDisturb:
return "moon.circle"
default:
return sfSymbol
}
}
} }

View file

@ -487,6 +487,7 @@ extension Defaults.Keys {
// MARK: Focus / Do Not Disturb Detection // MARK: Focus / Do Not Disturb Detection
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)
// 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)