mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 15:44:32 +00:00
commit
f612f15eb4
4 changed files with 88 additions and 34 deletions
|
|
@ -248,14 +248,14 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
.onChange(of: vm.isBatteryPopoverActive) { _, newPopoverState in
|
.onChange(of: vm.isBatteryPopoverActive) { _, newPopoverState in
|
||||||
runAfter(0.1) {
|
runAfter(0.1) {
|
||||||
if !newPopoverState && !isHovering && vm.notchState == .open && !vm.isStatsPopoverActive && !vm.isMediaOutputPopoverActive && !vm.isReminderPopoverActive {
|
if !newPopoverState && !isHovering && vm.notchState == .open && !shouldPreventAutoClose() {
|
||||||
vm.close()
|
vm.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: vm.isStatsPopoverActive) { _, newPopoverState in
|
.onChange(of: vm.isStatsPopoverActive) { _, newPopoverState in
|
||||||
runAfter(0.1) {
|
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()
|
vm.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +263,14 @@ struct ContentView: View {
|
||||||
.onChange(of: vm.shouldRecheckHover) { _, _ in
|
.onChange(of: vm.shouldRecheckHover) { _, _ in
|
||||||
// Recheck hover state when popovers are closed
|
// Recheck hover state when popovers are closed
|
||||||
runAfter(0.1) {
|
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()
|
vm.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -737,7 +744,9 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.dropEvent = false
|
vm.dropEvent = false
|
||||||
vm.close()
|
if !shouldPreventAutoClose() {
|
||||||
|
vm.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -800,7 +809,7 @@ struct ContentView: View {
|
||||||
self.isHovering = false
|
self.isHovering = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.vm.notchState == .open && !self.hasAnyActivePopovers() {
|
if self.vm.notchState == .open && !self.shouldPreventAutoClose() {
|
||||||
self.vm.close()
|
self.vm.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -818,6 +827,10 @@ struct ContentView: View {
|
||||||
vm.isMediaOutputPopoverActive ||
|
vm.isMediaOutputPopoverActive ||
|
||||||
vm.isReminderPopoverActive
|
vm.isReminderPopoverActive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func shouldPreventAutoClose() -> Bool {
|
||||||
|
hasAnyActivePopovers() || SharingStateManager.shared.preventNotchClose
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to prevent rapid haptic feedback
|
// Helper to prevent rapid haptic feedback
|
||||||
private func triggerHapticIfAllowed() {
|
private func triggerHapticIfAllowed() {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
@State private var scaleResetTask: Task<Void, Never>?
|
@State private var scaleResetTask: Task<Void, Never>?
|
||||||
@State private var collapseTask: Task<Void, Never>?
|
@State private var collapseTask: Task<Void, Never>?
|
||||||
@State private var cleanupTask: Task<Void, Never>?
|
@State private var cleanupTask: Task<Void, Never>?
|
||||||
@State private var labelIntrinsicWidth: CGFloat = 0
|
|
||||||
|
|
||||||
private enum ToastTiming {
|
private enum ToastTiming {
|
||||||
static let activeDisplay: UInt64 = 1800 // focus enabled toast linger
|
static let activeDisplay: UInt64 = 1800 // focus enabled toast linger
|
||||||
|
|
@ -119,14 +118,17 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var desiredLabelWidth: CGFloat {
|
private var desiredLabelWidth: CGFloat {
|
||||||
let measuredWidth = labelIntrinsicWidth + 8 // horizontal padding inside the label
|
|
||||||
let fallbackWidth = max(collapsedNotchWidth * 0.52, 136)
|
let fallbackWidth = max(collapsedNotchWidth * 0.52, 136)
|
||||||
var width = max(measuredWidth, fallbackWidth)
|
var width = fallbackWidth
|
||||||
|
|
||||||
if focusMode == .doNotDisturb && shouldShowLabel {
|
if focusMode == .doNotDisturb && shouldShowLabel {
|
||||||
width = max(width, 164)
|
width = max(width, 164)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !shouldMarqueeLabel {
|
||||||
|
width = max(width, labelIntrinsicWidth + 8)
|
||||||
|
}
|
||||||
|
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,6 +136,31 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
focusToastMode ? (isExpanded && !labelText.isEmpty) : (showLabelSetting && isExpanded && !labelText.isEmpty)
|
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
|
// MARK: - Focus metadata
|
||||||
|
|
||||||
private var focusMode: FocusModeType {
|
private var focusMode: FocusModeType {
|
||||||
|
|
@ -247,25 +274,31 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
Color.clear
|
Color.clear
|
||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .trailing) {
|
||||||
if shouldShowLabel {
|
if shouldShowLabel {
|
||||||
Text(labelText)
|
Group {
|
||||||
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
if shouldMarqueeLabel {
|
||||||
.foregroundColor(labelColor)
|
MarqueeText(
|
||||||
.lineLimit(1)
|
.constant(labelText),
|
||||||
.minimumScaleFactor(0.8)
|
font: focusLabelFont,
|
||||||
.contentTransition(.opacity)
|
nsFont: .caption1,
|
||||||
.background(
|
textColor: labelColor,
|
||||||
GeometryReader { proxy in
|
minDuration: 0.4,
|
||||||
Color.clear
|
frameWidth: marqueeFrameWidth
|
||||||
.preference(key: FocusLabelWidthPreferenceKey.self, value: proxy.size.width)
|
)
|
||||||
}
|
.frame(width: marqueeFrameWidth, alignment: .trailing)
|
||||||
)
|
} else {
|
||||||
.padding(.horizontal, 4)
|
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)
|
.animation(.smooth(duration: 0.3), value: shouldShowLabel)
|
||||||
.onPreferenceChange(FocusLabelWidthPreferenceKey.self) { value in
|
|
||||||
labelIntrinsicWidth = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var labelColor: Color {
|
private var labelColor: Color {
|
||||||
|
|
@ -385,14 +418,6 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
.background(Color.black)
|
.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 {
|
private final class SymbolAvailabilityCache {
|
||||||
static let shared = SymbolAvailabilityCache()
|
static let shared = SymbolAvailabilityCache()
|
||||||
private var cache: [String: Bool] = [:]
|
private var cache: [String: Bool] = [:]
|
||||||
|
|
@ -418,3 +443,16 @@ private final class SymbolAvailabilityCache {
|
||||||
return available
|
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
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,12 @@ struct FileShareView: View {
|
||||||
Text(selectedProvider.id)
|
Text(selectedProvider.id)
|
||||||
.font(.system(.headline, design: .rounded))
|
.font(.system(.headline, design: .rounded))
|
||||||
.foregroundColor(.white.opacity(0.8))
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(18)
|
.padding(18)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
// Loading overlay
|
// Loading overlay
|
||||||
if isProcessing || quickShare.isPickerOpen {
|
if isProcessing || quickShare.isPickerOpen {
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ extension Defaults.Keys {
|
||||||
static let sneakPeekStyles = Key<SneakPeekStyle>("sneakPeekStyles", default: .standard)
|
static let sneakPeekStyles = Key<SneakPeekStyle>("sneakPeekStyles", default: .standard)
|
||||||
static let showSneakPeekOnTrackChange = Key<Bool>("showSneakPeekOnTrackChange", default: true)
|
static let showSneakPeekOnTrackChange = Key<Bool>("showSneakPeekOnTrackChange", default: true)
|
||||||
static let enableFullscreenMediaDetection = Key<Bool>("enableFullscreenMediaDetection", 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 waitInterval = Key<Double>("waitInterval", default: 3)
|
||||||
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: true)
|
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: true)
|
||||||
static let showMediaOutputControl = Key<Bool>("showMediaOutputControl", default: false)
|
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 lockScreenWeatherProviderSource = Key<LockScreenWeatherProviderSource>("lockScreenWeatherProviderSource", default: .openMeteo)
|
||||||
static let lockScreenWeatherVerticalOffset = Key<Double>("lockScreenWeatherVerticalOffset", default: 0)
|
static let lockScreenWeatherVerticalOffset = Key<Double>("lockScreenWeatherVerticalOffset", default: 0)
|
||||||
static let lockScreenMusicVerticalOffset = Key<Double>("lockScreenMusicVerticalOffset", 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 lockScreenTimerVerticalOffset = Key<Double>("lockScreenTimerVerticalOffset", default: 0)
|
||||||
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
|
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
|
||||||
static let lockScreenShowAppIcon = Key<Bool>("lockScreenShowAppIcon", default: false)
|
static let lockScreenShowAppIcon = Key<Bool>("lockScreenShowAppIcon", default: false)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue