mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-29 08:04:32 +00:00
I can't fix it
This commit is contained in:
parent
74b9d2afad
commit
44569d36b5
5 changed files with 162 additions and 20 deletions
|
|
@ -68,6 +68,10 @@ struct ContentView: View {
|
||||||
@State private var isHoverStateChanging: Bool = false
|
@State private var isHoverStateChanging: Bool = false
|
||||||
@State private var isStatsTransitioning: Bool = false
|
@State private var isStatsTransitioning: Bool = false
|
||||||
@State private var isSwitchingToStats: Bool = false
|
@State private var isSwitchingToStats: Bool = false
|
||||||
|
@State private var isViewTransitioning: Bool = false
|
||||||
|
@State private var statsTransitionWorkItem: DispatchWorkItem?
|
||||||
|
@State private var viewTransitionWorkItem: DispatchWorkItem?
|
||||||
|
@State private var sizeChangeWorkItem: DispatchWorkItem?
|
||||||
@State private var lastHapticTime: Date = Date()
|
@State private var lastHapticTime: Date = Date()
|
||||||
|
|
||||||
@State private var gestureProgress: CGFloat = .zero
|
@State private var gestureProgress: CGFloat = .zero
|
||||||
|
|
@ -166,6 +170,15 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onChange(of: vm.notchState) { _, newState in
|
.onChange(of: vm.notchState) { _, newState in
|
||||||
|
// Update smart monitoring based on notch state
|
||||||
|
if enableStatsFeature {
|
||||||
|
let currentViewString = coordinator.currentView == .stats ? "stats" : "other"
|
||||||
|
statsManager.updateMonitoringState(
|
||||||
|
notchIsOpen: newState == .open,
|
||||||
|
currentView: currentViewString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Reset hover state when notch state changes
|
// Reset hover state when notch state changes
|
||||||
if newState == .closed && isHovering {
|
if newState == .closed && isHovering {
|
||||||
// Only reset visually, without triggering the hover logic again
|
// Only reset visually, without triggering the hover logic again
|
||||||
|
|
@ -212,32 +225,89 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: coordinator.currentView) { oldValue, newValue in
|
.onChange(of: coordinator.currentView) { oldValue, newValue in
|
||||||
// Cancel any pending close actions during view transitions
|
// Update smart monitoring based on current view change
|
||||||
|
if enableStatsFeature {
|
||||||
|
let currentViewString = newValue == .stats ? "stats" : "other"
|
||||||
|
statsManager.updateMonitoringState(
|
||||||
|
notchIsOpen: vm.notchState == .open,
|
||||||
|
currentView: currentViewString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel any pending close actions and ALL existing transition timers during view transitions
|
||||||
hoverWorkItem?.cancel()
|
hoverWorkItem?.cancel()
|
||||||
|
statsTransitionWorkItem?.cancel()
|
||||||
|
viewTransitionWorkItem?.cancel()
|
||||||
|
sizeChangeWorkItem?.cancel()
|
||||||
|
|
||||||
|
// Reset all transition flags immediately to prevent conflicts
|
||||||
|
isStatsTransitioning = false
|
||||||
|
isSwitchingToStats = false
|
||||||
|
isViewTransitioning = false
|
||||||
|
|
||||||
|
// Check if this transition will cause a size change
|
||||||
|
let oldSize = oldValue == .stats && statsTabHasExpandedHeight() ?
|
||||||
|
CGSize(width: openNotchSize.width, height: openNotchSize.height + 65) : openNotchSize
|
||||||
|
let newSize = newValue == .stats && statsTabHasExpandedHeight() ?
|
||||||
|
CGSize(width: openNotchSize.width, height: openNotchSize.height + 65) : openNotchSize
|
||||||
|
let sizeWillChange = oldSize != newSize
|
||||||
|
|
||||||
// Set flags for transition tracking
|
// Set flags for transition tracking
|
||||||
isSwitchingToStats = (oldValue != .stats && newValue == .stats)
|
isSwitchingToStats = (oldValue != .stats && newValue == .stats)
|
||||||
|
|
||||||
// Provide protection when switching to stats tab
|
// Set view transition flag if size will change with proper cleanup
|
||||||
|
if sizeWillChange {
|
||||||
|
isViewTransitioning = true
|
||||||
|
// Clear after animation duration + generous buffer
|
||||||
|
let workItem = DispatchWorkItem {
|
||||||
|
isViewTransitioning = false
|
||||||
|
}
|
||||||
|
viewTransitionWorkItem = workItem
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: workItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide protection when switching to stats tab with proper cleanup
|
||||||
if newValue == .stats {
|
if newValue == .stats {
|
||||||
isStatsTransitioning = true
|
isStatsTransitioning = true
|
||||||
|
|
||||||
// Longer delay if stats tab has expanded height (4+ graphs)
|
// Much longer delay if stats tab has expanded height (4+ graphs)
|
||||||
let hasExpandedHeight = statsTabHasExpandedHeight()
|
let hasExpandedHeight = statsTabHasExpandedHeight()
|
||||||
let protectionDelay: Double = hasExpandedHeight ? 1.5 : 0.8
|
let protectionDelay: Double = hasExpandedHeight ? 4.0 : 2.0 // Much longer delays
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + protectionDelay) {
|
let workItem = DispatchWorkItem {
|
||||||
isStatsTransitioning = false
|
isStatsTransitioning = false
|
||||||
isSwitchingToStats = false
|
isSwitchingToStats = false
|
||||||
}
|
}
|
||||||
|
statsTransitionWorkItem = workItem
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + protectionDelay, execute: workItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also provide brief protection when switching FROM stats to prevent premature closure
|
// Also provide brief protection when switching FROM stats to prevent premature closure
|
||||||
if oldValue == .stats && newValue != .stats {
|
if oldValue == .stats && newValue != .stats {
|
||||||
isStatsTransitioning = true
|
isStatsTransitioning = true
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
|
let workItem = DispatchWorkItem {
|
||||||
isStatsTransitioning = false
|
isStatsTransitioning = false
|
||||||
}
|
}
|
||||||
|
statsTransitionWorkItem = workItem
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: workItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: [showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph]) { _, _ in
|
||||||
|
// Protect during graph count changes that might affect size
|
||||||
|
if coordinator.currentView == .stats {
|
||||||
|
// Cancel existing transition timers to prevent conflicts
|
||||||
|
viewTransitionWorkItem?.cancel()
|
||||||
|
statsTransitionWorkItem?.cancel()
|
||||||
|
|
||||||
|
isViewTransitioning = true
|
||||||
|
isStatsTransitioning = true
|
||||||
|
// Clear after animation duration + generous buffer
|
||||||
|
let workItem = DispatchWorkItem {
|
||||||
|
isViewTransitioning = false
|
||||||
|
isStatsTransitioning = false
|
||||||
|
}
|
||||||
|
viewTransitionWorkItem = workItem
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: workItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sensoryFeedback(.alignment, trigger: haptics)
|
.sensoryFeedback(.alignment, trigger: haptics)
|
||||||
|
|
@ -261,10 +331,32 @@ struct ContentView: View {
|
||||||
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height, alignment: .top)
|
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height, alignment: .top)
|
||||||
.animation(.easeInOut(duration: 0.4), value: dynamicNotchSize)
|
.animation(.easeInOut(duration: 0.4), value: dynamicNotchSize)
|
||||||
.animation(.easeInOut(duration: 0.4), value: coordinator.currentView)
|
.animation(.easeInOut(duration: 0.4), value: coordinator.currentView)
|
||||||
|
.onChange(of: dynamicNotchSize) { oldSize, newSize in
|
||||||
|
// Protect against hover interference during frame size changes
|
||||||
|
if oldSize != newSize {
|
||||||
|
// Cancel existing size change timer to prevent conflicts
|
||||||
|
sizeChangeWorkItem?.cancel()
|
||||||
|
|
||||||
|
isViewTransitioning = true
|
||||||
|
let workItem = DispatchWorkItem {
|
||||||
|
isViewTransitioning = false
|
||||||
|
}
|
||||||
|
sizeChangeWorkItem = workItem
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: workItem) // Frame animation + buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
.shadow(color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow]) ? .black.opacity(0.6) : .clear, radius: Defaults[.cornerRadiusScaling] ? 10 : 5)
|
.shadow(color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow]) ? .black.opacity(0.6) : .clear, radius: Defaults[.cornerRadiusScaling] ? 10 : 5)
|
||||||
.background(dragDetector)
|
.background(dragDetector)
|
||||||
.environmentObject(vm)
|
.environmentObject(vm)
|
||||||
.environmentObject(webcamManager)
|
.environmentObject(webcamManager)
|
||||||
|
.onDisappear {
|
||||||
|
// Clean up all timer work items to prevent memory leaks and conflicts
|
||||||
|
hoverWorkItem?.cancel()
|
||||||
|
debounceWorkItem?.cancel()
|
||||||
|
statsTransitionWorkItem?.cancel()
|
||||||
|
viewTransitionWorkItem?.cancel()
|
||||||
|
sizeChangeWorkItem?.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
@ -519,6 +611,7 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
private func doOpen() {
|
private func doOpen() {
|
||||||
// Switch to timer tab if timer is active
|
// Switch to timer tab if timer is active
|
||||||
if timerManager.isTimerActive {
|
if timerManager.isTimerActive {
|
||||||
|
|
@ -623,7 +716,8 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply stronger protection when switching to stats, especially with 4+ graphs
|
// Apply stronger protection when switching to stats, especially with 4+ graphs
|
||||||
if isStatsTransitioning || (isSwitchingToStats && statsTabHasExpandedHeight()) {
|
// Also protect during any size change animations
|
||||||
|
if isStatsTransitioning || (isSwitchingToStats && statsTabHasExpandedHeight()) || isViewTransitioning {
|
||||||
return // Skip closing during transitions, but allow visual updates
|
return // Skip closing during transitions, but allow visual updates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -634,12 +728,12 @@ struct ContentView: View {
|
||||||
let isStatsTab = coordinator.currentView == .stats
|
let isStatsTab = coordinator.currentView == .stats
|
||||||
let hasExpandedHeight = isStatsTab && statsTabHasExpandedHeight()
|
let hasExpandedHeight = isStatsTab && statsTabHasExpandedHeight()
|
||||||
|
|
||||||
// Longer delays for stats tab, especially when it has expanded height
|
// Much longer delays for stats tab, especially when it has expanded height
|
||||||
let delay: Double
|
let delay: Double
|
||||||
if hasExpandedHeight {
|
if hasExpandedHeight {
|
||||||
delay = 1.0 // Extra long for stats with 4+ graphs
|
delay = 2.0 // Very long for stats with 4+ graphs
|
||||||
} else if isStatsTab {
|
} else if isStatsTab {
|
||||||
delay = 0.6 // Medium for stats with 1-3 graphs
|
delay = 1.0 // Long for stats with 1-3 graphs
|
||||||
} else {
|
} else {
|
||||||
delay = 0.2 // Short for other tabs
|
delay = 0.2 // Short for other tabs
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -337,9 +337,7 @@ struct NotchStatsView: View {
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if enableStatsFeature && Defaults[.autoStartStatsMonitoring] && !statsManager.isMonitoring {
|
// Note: Smart monitoring will handle starting/stopping based on notch state and current view
|
||||||
statsManager.startMonitoring()
|
|
||||||
}
|
|
||||||
// Protect against hover interference during view transition
|
// Protect against hover interference during view transition
|
||||||
isResizingForStats = true
|
isResizingForStats = true
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
||||||
|
|
|
||||||
|
|
@ -1586,11 +1586,10 @@ struct StatsSettings: View {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Enable system stats monitoring", key: .enableStatsFeature)
|
Defaults.Toggle("Enable system stats monitoring", key: .enableStatsFeature)
|
||||||
.onChange(of: enableStatsFeature) { _, newValue in
|
.onChange(of: enableStatsFeature) { _, newValue in
|
||||||
if newValue {
|
if !newValue {
|
||||||
statsManager.startMonitoring()
|
|
||||||
} else {
|
|
||||||
statsManager.stopMonitoring()
|
statsManager.stopMonitoring()
|
||||||
}
|
}
|
||||||
|
// Note: Smart monitoring will handle starting when switching to stats tab
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableStatsFeature {
|
if enableStatsFeature {
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,7 @@ struct StatsPanelView: View {
|
||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
.shadow(color: .black.opacity(0.4), radius: 20, x: 0, y: 10)
|
.shadow(color: .black.opacity(0.4), radius: 20, x: 0, y: 10)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if enableStatsFeature && !statsManager.isMonitoring {
|
// Note: Smart monitoring will handle starting/stopping based on UI state
|
||||||
statsManager.startMonitoring()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,15 @@ class StatsManager: ObservableObject {
|
||||||
@Published var diskWriteHistory: [Double] = []
|
@Published var diskWriteHistory: [Double] = []
|
||||||
|
|
||||||
private var monitoringTimer: Timer?
|
private var monitoringTimer: Timer?
|
||||||
|
private var delayedStopTimer: Timer?
|
||||||
|
private var delayedStartTimer: Timer?
|
||||||
private let maxHistoryPoints = 30
|
private let maxHistoryPoints = 30
|
||||||
|
|
||||||
|
// Smart monitoring state
|
||||||
|
private var shouldMonitorForStats: Bool = false
|
||||||
|
private var lastNotchState: String = "closed"
|
||||||
|
private var lastCurrentView: String = "other"
|
||||||
|
|
||||||
// Network monitoring state
|
// Network monitoring state
|
||||||
private var previousNetworkStats: (bytesIn: UInt64, bytesOut: UInt64) = (0, 0)
|
private var previousNetworkStats: (bytesIn: UInt64, bytesOut: UInt64) = (0, 0)
|
||||||
private var previousTimestamp: Date = Date()
|
private var previousTimestamp: Date = Date()
|
||||||
|
|
@ -70,12 +77,51 @@ class StatsManager: ObservableObject {
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
stopMonitoring()
|
stopMonitoring()
|
||||||
|
delayedStartTimer?.invalidate()
|
||||||
|
delayedStopTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public Methods
|
// MARK: - Smart Monitoring
|
||||||
|
func updateMonitoringState(notchIsOpen: Bool, currentView: String) {
|
||||||
|
let notchState = notchIsOpen ? "open" : "closed"
|
||||||
|
|
||||||
|
// Only react to actual state changes
|
||||||
|
guard notchState != lastNotchState || currentView != lastCurrentView else { return }
|
||||||
|
|
||||||
|
lastNotchState = notchState
|
||||||
|
lastCurrentView = currentView
|
||||||
|
|
||||||
|
// Cancel any pending timers
|
||||||
|
delayedStartTimer?.invalidate()
|
||||||
|
delayedStopTimer?.invalidate()
|
||||||
|
|
||||||
|
// Determine if we should be monitoring
|
||||||
|
shouldMonitorForStats = notchIsOpen && (currentView == "stats")
|
||||||
|
|
||||||
|
if shouldMonitorForStats {
|
||||||
|
// Start monitoring after 3.5 seconds (when notch is open and stats tab is active)
|
||||||
|
delayedStartTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.startMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stop monitoring after 3 seconds (when notch is closed or stats tab is not active)
|
||||||
|
let delay = notchIsOpen ? 0.1 : 3.0 // Stop quickly when switching tabs, slowly when closing notch
|
||||||
|
delayedStopTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.stopMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Monitoring Controls
|
||||||
func startMonitoring() {
|
func startMonitoring() {
|
||||||
guard !isMonitoring else { return }
|
guard !isMonitoring else { return }
|
||||||
|
|
||||||
|
print("StatsManager: Starting monitoring...")
|
||||||
|
|
||||||
// Reset baseline for accurate measurement
|
// Reset baseline for accurate measurement
|
||||||
let initialStats = getNetworkStats()
|
let initialStats = getNetworkStats()
|
||||||
previousNetworkStats = initialStats
|
previousNetworkStats = initialStats
|
||||||
|
|
@ -96,14 +142,21 @@ class StatsManager: ObservableObject {
|
||||||
self.updateSystemStats()
|
self.updateSystemStats()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("StatsManager: Monitoring started")
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopMonitoring() {
|
func stopMonitoring() {
|
||||||
guard isMonitoring else { return }
|
guard isMonitoring else { return }
|
||||||
|
|
||||||
|
// Clean up all timers
|
||||||
monitoringTimer?.invalidate()
|
monitoringTimer?.invalidate()
|
||||||
monitoringTimer = nil
|
monitoringTimer = nil
|
||||||
|
delayedStartTimer?.invalidate()
|
||||||
|
delayedStopTimer?.invalidate()
|
||||||
|
|
||||||
isMonitoring = false
|
isMonitoring = false
|
||||||
|
print("StatsManager: Monitoring stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue