Merge pull request #190 from Ebullioscopic/ui/fixes

UI/fixes
This commit is contained in:
Hariharan Mudaliar 2025-12-21 15:45:45 +05:30 • committed by GitHub
commit f612f15eb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 88 additions and 34 deletions

View file

@ -248,14 +248,14 @@ struct ContentView: View {
}
.onChange(of: vm.isBatteryPopoverActive) { _, newPopoverState in
runAfter(0.1) {
if !newPopoverState && !isHovering && vm.notchState == .open && !vm.isStatsPopoverActive && !vm.isMediaOutputPopoverActive && !vm.isReminderPopoverActive {
if !newPopoverState && !isHovering && vm.notchState == .open && !shouldPreventAutoClose() {
vm.close()
}
}
}
.onChange(of: vm.isStatsPopoverActive) { _, newPopoverState in
runAfter(0.1) {
if !newPopoverState && !isHovering && vm.notchState == .open && !vm.isBatteryPopoverActive && !vm.isClipboardPopoverActive && !vm.isColorPickerPopoverActive && !vm.isMediaOutputPopoverActive && !vm.isReminderPopoverActive {
if !newPopoverState && !isHovering && vm.notchState == .open && !shouldPreventAutoClose() {
vm.close()
}
}
@ -263,7 +263,14 @@ struct ContentView: View {
.onChange(of: vm.shouldRecheckHover) { _, _ in
// Recheck hover state when popovers are closed
runAfter(0.1) {
if vm.notchState == .open && !vm.isBatteryPopoverActive && !vm.isClipboardPopoverActive && !vm.isColorPickerPopoverActive && !vm.isStatsPopoverActive && !vm.isMediaOutputPopoverActive && !vm.isReminderPopoverActive && !isHovering {
if vm.notchState == .open && !shouldPreventAutoClose() && !isHovering {
vm.close()
}
}
}
.onReceive(NotificationCenter.default.publisher(for: .sharingDidFinish)) { _ in
runAfter(0.1) {
if vm.notchState == .open && !isHovering && !shouldPreventAutoClose() {
vm.close()
}
}
@ -737,7 +744,9 @@ struct ContentView: View {
}
vm.dropEvent = false
vm.close()
if !shouldPreventAutoClose() {
vm.close()
}
}
}
} else {
@ -800,7 +809,7 @@ struct ContentView: View {
self.isHovering = false
}
if self.vm.notchState == .open && !self.hasAnyActivePopovers() {
if self.vm.notchState == .open && !self.shouldPreventAutoClose() {
self.vm.close()
}
}
@ -818,6 +827,10 @@ struct ContentView: View {
vm.isMediaOutputPopoverActive ||
vm.isReminderPopoverActive
}
private func shouldPreventAutoClose() -> Bool {
hasAnyActivePopovers() || SharingStateManager.shared.preventNotchClose
}
// Helper to prevent rapid haptic feedback
private func triggerHapticIfAllowed() {

View file

@ -22,7 +22,6 @@ struct DoNotDisturbLiveActivity: View {
@State private var scaleResetTask: Task<Void, Never>?
@State private var collapseTask: Task<Void, Never>?
@State private var cleanupTask: Task<Void, Never>?
@State private var labelIntrinsicWidth: CGFloat = 0
private enum ToastTiming {
static let activeDisplay: UInt64 = 1800 // focus enabled toast linger
@ -119,14 +118,17 @@ struct DoNotDisturbLiveActivity: View {
}
private var desiredLabelWidth: CGFloat {
let measuredWidth = labelIntrinsicWidth + 8 // horizontal padding inside the label
let fallbackWidth = max(collapsedNotchWidth * 0.52, 136)
var width = max(measuredWidth, fallbackWidth)
var width = fallbackWidth
if focusMode == .doNotDisturb && shouldShowLabel {
width = max(width, 164)
}
if !shouldMarqueeLabel {
width = max(width, labelIntrinsicWidth + 8)
}
return width
}
@ -134,6 +136,31 @@ struct DoNotDisturbLiveActivity: View {
focusToastMode ? (isExpanded && !labelText.isEmpty) : (showLabelSetting && isExpanded && !labelText.isEmpty)
}
private var labelIntrinsicWidth: CGFloat {
guard !labelText.isEmpty else { return 0 }
return (labelText as NSString).size(withAttributes: [.font: focusLabelNSFont]).width
}
private var shouldMarqueeLabel: Bool {
shouldShowLabel && labelIntrinsicWidth > focusLabelBaselineWidth
}
private var marqueeFrameWidth: CGFloat {
max(48, labelWingWidth - 8)
}
private var focusLabelFont: Font {
.system(size: 12, weight: .semibold, design: .rounded)
}
private var focusLabelNSFont: NSFont {
NSFont.systemFont(ofSize: 12, weight: .semibold)
}
private var focusLabelBaselineWidth: CGFloat {
FocusLabelMetrics.baselineWidth
}
// MARK: - Focus metadata
private var focusMode: FocusModeType {
@ -247,25 +274,31 @@ struct DoNotDisturbLiveActivity: View {
Color.clear
.overlay(alignment: .trailing) {
if shouldShowLabel {
Text(labelText)
.font(.system(size: 12, weight: .semibold, design: .rounded))
.foregroundColor(labelColor)
.lineLimit(1)
.minimumScaleFactor(0.8)
.contentTransition(.opacity)
.background(
GeometryReader { proxy in
Color.clear
.preference(key: FocusLabelWidthPreferenceKey.self, value: proxy.size.width)
}
)
.padding(.horizontal, 4)
Group {
if shouldMarqueeLabel {
MarqueeText(
.constant(labelText),
font: focusLabelFont,
nsFont: .caption1,
textColor: labelColor,
minDuration: 0.4,
frameWidth: marqueeFrameWidth
)
.frame(width: marqueeFrameWidth, alignment: .trailing)
} else {
Text(labelText)
.font(focusLabelFont)
.foregroundColor(labelColor)
.lineLimit(1)
.minimumScaleFactor(0.8)
.contentTransition(.opacity)
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
.padding(.horizontal, 4)
}
}
.animation(.smooth(duration: 0.3), value: shouldShowLabel)
.onPreferenceChange(FocusLabelWidthPreferenceKey.self) { value in
labelIntrinsicWidth = value
}
}
private var labelColor: Color {
@ -385,14 +418,6 @@ struct DoNotDisturbLiveActivity: View {
.background(Color.black)
}
private struct FocusLabelWidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
private final class SymbolAvailabilityCache {
static let shared = SymbolAvailabilityCache()
private var cache: [String: Bool] = [:]
@ -418,3 +443,16 @@ private final class SymbolAvailabilityCache {
return available
}
}
private enum FocusLabelMetrics {
static let baselineText = "Do Not Disturb"
static let baselineWidth: CGFloat = {
#if canImport(AppKit)
let font = NSFont.systemFont(ofSize: 12, weight: .semibold)
return (baselineText as NSString).size(withAttributes: [.font: font]).width
#else
return 0
#endif
}()
}

View file

@ -87,9 +87,12 @@ struct FileShareView: View {
Text(selectedProvider.id)
.font(.system(.headline, design: .rounded))
.foregroundColor(.white.opacity(0.8))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
}
.padding(18)
.frame(maxWidth: .infinity)
// Loading overlay
if isProcessing || quickShare.isPickerOpen {

View file

@ -442,7 +442,7 @@ extension Defaults.Keys {
static let sneakPeekStyles = Key<SneakPeekStyle>("sneakPeekStyles", default: .standard)
static let showSneakPeekOnTrackChange = Key<Bool>("showSneakPeekOnTrackChange", default: true)
static let enableFullscreenMediaDetection = Key<Bool>("enableFullscreenMediaDetection", default: true)
static let enableParallaxEffect = Key<Bool>("enableParallaxEffect", default: false)
static let enableParallaxEffect = Key<Bool>("enableParallaxEffect", default: true)
static let waitInterval = Key<Double>("waitInterval", default: 3)
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: true)
static let showMediaOutputControl = Key<Bool>("showMediaOutputControl", default: false)
@ -474,7 +474,7 @@ extension Defaults.Keys {
static let lockScreenWeatherProviderSource = Key<LockScreenWeatherProviderSource>("lockScreenWeatherProviderSource", default: .openMeteo)
static let lockScreenWeatherVerticalOffset = Key<Double>("lockScreenWeatherVerticalOffset", default: 0)
static let lockScreenMusicVerticalOffset = Key<Double>("lockScreenMusicVerticalOffset", default: 0)
static let lockScreenMusicAlbumParallaxEnabled = Key<Bool>("lockScreenMusicAlbumParallaxEnabled", default: true)
static let lockScreenMusicAlbumParallaxEnabled = Key<Bool>("lockScreenMusicAlbumParallaxEnabled", default: false)
static let lockScreenTimerVerticalOffset = Key<Double>("lockScreenTimerVerticalOffset", default: 0)
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
static let lockScreenShowAppIcon = Key<Bool>("lockScreenShowAppIcon", default: false)