From dce626e9c2a32346650dce489ec648c89f1fd9b8 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Fri, 16 Jan 2026 09:07:53 -0500 Subject: [PATCH] general:made logging consistent --- Gaze/AppDelegate.swift | 56 +++++----- Gaze/Services/EyeTrackingService.swift | 14 ++- Gaze/Services/LoggingManager.swift | 7 +- Gaze/Services/PupilDetector.swift | 74 +++++++++++-- Gaze/Services/TimerEngine.swift | 117 ++++++++++---------- Gaze/Views/MenuBar/MenuBarContentView.swift | 1 - 6 files changed, 159 insertions(+), 110 deletions(-) diff --git a/Gaze/AppDelegate.swift b/Gaze/AppDelegate.swift index c816ab5..7f28c86 100644 --- a/Gaze/AppDelegate.swift +++ b/Gaze/AppDelegate.swift @@ -8,7 +8,6 @@ import AppKit import Combine import SwiftUI -import os.log @MainActor class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { @@ -20,43 +19,36 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private var hasStartedTimers = false private var isSettingsWindowOpen = false private var isOnboardingWindowOpen = false - - // Logging manager - private let logger = LoggingManager.shared - + // Convenience accessor for settings private var settingsManager: any SettingsProviding { serviceContainer.settingsManager } - + override init() { self.serviceContainer = ServiceContainer.shared self.windowManager = WindowManager.shared super.init() - + // Setup window close observers setupWindowCloseObservers() } - + /// Initializer for testing with injectable dependencies init(serviceContainer: ServiceContainer, windowManager: WindowManaging) { self.serviceContainer = serviceContainer self.windowManager = windowManager super.init() } - + func applicationDidFinishLaunching(_ notification: Notification) { // Set activation policy to hide dock icon NSApplication.shared.setActivationPolicy(.accessory) - - // Initialize logging - logger.configureLogging() - logger.appLogger.info("πŸš€ Application did finish launching") - // Get timer engine from service container + logInfo("πŸš€ Application did finish launching") + timerEngine = serviceContainer.timerEngine - // Setup smart mode services through container serviceContainer.setupSmartModeServices() // Check if onboarding needs to be shown automatically @@ -87,7 +79,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { .removeDuplicates() .sink { [weak self] smartMode in guard let self = self else { return } - self.serviceContainer.idleService?.updateThreshold(minutes: smartMode.idleThresholdMinutes) + self.serviceContainer.idleService?.updateThreshold( + minutes: smartMode.idleThresholdMinutes) self.serviceContainer.usageTrackingService?.updateResetThreshold( minutes: smartMode.usageResetAfterMinutes) @@ -110,7 +103,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private func startTimers() { guard !hasStartedTimers else { return } hasStartedTimers = true - logger.appLogger.info("Starting timers") + logInfo("Starting timers") timerEngine?.start() observeReminderEvents() } @@ -128,13 +121,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } } .store(in: &cancellables) - + // Also observe smart mode settings observeSmartModeSettings() } func applicationWillTerminate(_ notification: Notification) { - logger.appLogger.info(" applicationWill terminate") + logInfo(" applicationWill terminate") settingsManager.saveImmediately() timerEngine?.stop() } @@ -156,13 +149,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } @objc private func systemWillSleep() { - logger.systemLogger.info("System will sleep") + logInfo("System will sleep") timerEngine?.handleSystemSleep() settingsManager.saveImmediately() } @objc private func systemDidWake() { - logger.systemLogger.info("System did wake") + logInfo("System did wake") timerEngine?.handleSystemWake() } @@ -185,21 +178,21 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { self?.timerEngine?.dismissReminder() } windowManager.showReminderWindow(view, windowType: .overlay) - + case .blinkTriggered: let sizePercentage = settingsManager.settings.subtleReminderSize.percentage let view = BlinkReminderView(sizePercentage: sizePercentage) { [weak self] in self?.timerEngine?.dismissReminder() } windowManager.showReminderWindow(view, windowType: .subtle) - + case .postureTriggered: let sizePercentage = settingsManager.settings.subtleReminderSize.percentage let view = PostureReminderView(sizePercentage: sizePercentage) { [weak self] in self?.timerEngine?.dismissReminder() } windowManager.showReminderWindow(view, windowType: .subtle) - + case .userTimerTriggered(let timer): if timer.type == .overlay { let view = UserTimerOverlayReminderView(timer: timer) { [weak self] in @@ -208,7 +201,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { windowManager.showReminderWindow(view, windowType: .overlay) } else { let sizePercentage = settingsManager.settings.subtleReminderSize.percentage - let view = UserTimerReminderView(timer: timer, sizePercentage: sizePercentage) { [weak self] in + let view = UserTimerReminderView(timer: timer, sizePercentage: sizePercentage) { + [weak self] in self?.timerEngine?.dismissReminder() } windowManager.showReminderWindow(view, windowType: .subtle) @@ -228,7 +222,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } return } - + handleMenuDismissal() isSettingsWindowOpen = true @@ -247,7 +241,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } return } - + handleMenuDismissal() // Explicitly set the flag to true when we're about to show the onboarding window isOnboardingWindowOpen = true @@ -262,7 +256,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { NotificationCenter.default.post(name: Notification.Name("CloseMenuBarPopover"), object: nil) windowManager.dismissOverlayReminder() } - + private func setupWindowCloseObservers() { // Observe settings window closing NotificationCenter.default.addObserver( @@ -271,7 +265,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { name: Notification.Name("SettingsWindowDidClose"), object: nil ) - + // Observe onboarding window closing NotificationCenter.default.addObserver( self, @@ -280,11 +274,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { object: nil ) } - + @objc private func settingsWindowDidClose() { isSettingsWindowOpen = false } - + @objc private func onboardingWindowDidClose() { // Reset the flag when we receive the close notification isOnboardingWindowOpen = false diff --git a/Gaze/Services/EyeTrackingService.swift b/Gaze/Services/EyeTrackingService.swift index 85ca855..b7a21a6 100644 --- a/Gaze/Services/EyeTrackingService.swift +++ b/Gaze/Services/EyeTrackingService.swift @@ -24,7 +24,12 @@ class EyeTrackingService: NSObject, ObservableObject { @Published var debugRightPupilRatio: Double? @Published var debugYaw: Double? @Published var debugPitch: Double? - @Published var enableDebugLogging: Bool = false + @Published var enableDebugLogging: Bool = false { + didSet { + // Sync with PupilDetector's diagnostic logging + PupilDetector.enableDiagnosticLogging = enableDebugLogging + } + } // Throttle for debug logging private var lastDebugLogTime: Date = .distantPast @@ -228,6 +233,10 @@ class EyeTrackingService: NSObject, ObservableObject { result.faceDetected = true let face = observations.first! + // Always extract yaw/pitch from face, even if landmarks aren't available + result.debugYaw = face.yaw?.doubleValue ?? 0.0 + result.debugPitch = face.pitch?.doubleValue ?? 0.0 + guard let landmarks = face.landmarks else { return result } @@ -660,6 +669,9 @@ extension EyeTrackingService: AVCaptureVideoDataOutputSampleBufferDelegate { return } + // Advance frame counter for pupil detector frame skipping + PupilDetector.advanceFrame() + let request = VNDetectFaceLandmarksRequest { [weak self] request, error in guard let self = self else { return } diff --git a/Gaze/Services/LoggingManager.swift b/Gaze/Services/LoggingManager.swift index e4160f9..c785a75 100644 --- a/Gaze/Services/LoggingManager.swift +++ b/Gaze/Services/LoggingManager.swift @@ -46,15 +46,12 @@ final class LoggingManager { // MARK: - Initialization private init() { - // Private initializer to enforce singleton pattern } // MARK: - Public Methods - /// Configure the logging system for verbose output when needed func configureLogging() { - // For now, we'll use standard OSLog behavior. - // This can be extended in the future to support runtime log level changes. + //nothing needed for now } /// Convenience method for debug logging @@ -86,8 +83,6 @@ final class LoggingManager { } } -// MARK: - Global Convenience Functions - /// Log an info message using the shared LoggingManager public func logInfo(_ message: String, category: String = "General") { LoggingManager.shared.info(message, category: category) diff --git a/Gaze/Services/PupilDetector.swift b/Gaze/Services/PupilDetector.swift index 7839238..ee92da2 100644 --- a/Gaze/Services/PupilDetector.swift +++ b/Gaze/Services/PupilDetector.swift @@ -63,7 +63,9 @@ final class PupilCalibration: @unchecked Sendable { } } - private nonisolated func findBestThreshold(eyeData: UnsafePointer, width: Int, height: Int) -> Int { + private nonisolated func findBestThreshold( + eyeData: UnsafePointer, width: Int, height: Int + ) -> Int { let averageIrisSize = 0.48 var bestThreshold = 50 var bestDiff = Double.greatestFiniteMagnitude @@ -91,7 +93,9 @@ final class PupilCalibration: @unchecked Sendable { return bestThreshold } - private nonisolated static func irisSize(data: UnsafePointer, width: Int, height: Int) -> Double { + private nonisolated static func irisSize(data: UnsafePointer, width: Int, height: Int) + -> Double + { let margin = 5 guard width > margin * 2, height > margin * 2 else { return 0 } @@ -145,15 +149,17 @@ final class PupilDetector: @unchecked Sendable { nonisolated(unsafe) static var enableDebugImageSaving = false nonisolated(unsafe) static var enablePerformanceLogging = false + nonisolated(unsafe) static var enableDiagnosticLogging = false nonisolated(unsafe) static var frameSkipCount = 10 // Process every Nth frame // MARK: - State (protected by lock) private nonisolated(unsafe) static var _debugImageCounter = 0 private nonisolated(unsafe) static var _frameCounter = 0 - private nonisolated(unsafe) static var _lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) = ( - nil, nil - ) + private nonisolated(unsafe) static var _lastPupilPositions: + (left: PupilPosition?, right: PupilPosition?) = ( + nil, nil + ) private nonisolated(unsafe) static var _metrics = PupilDetectorMetrics() nonisolated(unsafe) static let calibration = PupilCalibration() @@ -170,7 +176,8 @@ final class PupilDetector: @unchecked Sendable { set { _frameCounter = newValue } } - private nonisolated static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) { + private nonisolated static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) + { get { _lastPupilPositions } set { _lastPupilPositions = newValue } } @@ -218,6 +225,11 @@ final class PupilDetector: @unchecked Sendable { // MARK: - Public API + /// Call once per video frame to enable proper frame skipping + nonisolated static func advanceFrame() { + frameCounter += 1 + } + /// Detects pupil position with frame skipping for performance /// Returns cached result on skipped frames nonisolated static func detectPupil( @@ -252,7 +264,7 @@ final class PupilDetector: @unchecked Sendable { let elapsed = (CFAbsoluteTimeGetCurrent() - startTime) * 1000 metrics.recordProcessingTime(elapsed) if metrics.processedFrameCount % 30 == 0 { - print( + logDebug( "πŸ‘ PupilDetector: \(String(format: "%.2f", elapsed))ms (avg: \(String(format: "%.2f", metrics.averageProcessingTimeMs))ms)" ) } @@ -266,10 +278,18 @@ final class PupilDetector: @unchecked Sendable { imageSize: imageSize ) - guard eyePoints.count >= 6 else { return nil } + guard eyePoints.count >= 6 else { + if enableDiagnosticLogging { + logDebug("πŸ‘ PupilDetector: Failed - eyePoints.count=\(eyePoints.count) < 6") + } + return nil + } // Step 2: Create eye region bounding box with margin guard let eyeRegion = createEyeRegion(from: eyePoints, imageSize: imageSize) else { + if enableDiagnosticLogging { + logDebug("πŸ‘ PupilDetector: Failed - createEyeRegion returned nil") + } return nil } @@ -285,6 +305,9 @@ final class PupilDetector: @unchecked Sendable { let eyeBuf = eyeBuffer, let tmpBuf = tempBuffer else { + if enableDiagnosticLogging { + logDebug("πŸ‘ PupilDetector: Failed - buffers not allocated") + } return nil } @@ -293,6 +316,9 @@ final class PupilDetector: @unchecked Sendable { extractGrayscaleDataOptimized( from: pixelBuffer, to: grayBuffer, width: frameWidth, height: frameHeight) else { + if enableDiagnosticLogging { + logDebug("πŸ‘ PupilDetector: Failed - grayscale extraction failed") + } return nil } @@ -301,7 +327,13 @@ final class PupilDetector: @unchecked Sendable { let eyeHeight = Int(eyeRegion.frame.height) // Early exit for tiny regions (less than 10x10 pixels) - guard eyeWidth >= 10, eyeHeight >= 10 else { return nil } + guard eyeWidth >= 10, eyeHeight >= 10 else { + if enableDiagnosticLogging { + logDebug( + "πŸ‘ PupilDetector: Failed - eye region too small (\(eyeWidth)x\(eyeHeight))") + } + return nil + } guard isolateEyeWithMaskOptimized( @@ -313,6 +345,9 @@ final class PupilDetector: @unchecked Sendable { output: eyeBuf ) else { + if enableDiagnosticLogging { + logDebug("πŸ‘ PupilDetector: Failed - isolateEyeWithMask failed") + } return nil } @@ -352,9 +387,20 @@ final class PupilDetector: @unchecked Sendable { height: eyeHeight ) else { + if enableDiagnosticLogging { + logDebug( + "πŸ‘ PupilDetector: Failed - findPupilFromContours returned nil (not enough dark pixels)" + ) + } return nil } + if enableDiagnosticLogging { + logDebug( + "πŸ‘ PupilDetector: Success - centroid at (\(String(format: "%.1f", centroidX)), \(String(format: "%.1f", centroidY))) in \(eyeWidth)x\(eyeHeight) region" + ) + } + let pupilPosition = PupilPosition(x: CGFloat(centroidX), y: CGFloat(centroidY)) // Cache result @@ -738,7 +784,9 @@ final class PupilDetector: @unchecked Sendable { } } - private nonisolated static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) -> EyeRegion? { + private nonisolated static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) + -> EyeRegion? + { guard !points.isEmpty else { return nil } let margin: CGFloat = 5 @@ -792,10 +840,12 @@ final class PupilDetector: @unchecked Sendable { CGImageDestinationAddImage(destination, cgImage, nil) CGImageDestinationFinalize(destination) - print("πŸ’Ύ Saved debug image: \(url.path)") + logDebug("πŸ’Ύ Saved debug image: \(url.path)") } - private nonisolated static func createCGImage(from data: UnsafePointer, width: Int, height: Int) + private nonisolated static func createCGImage( + from data: UnsafePointer, width: Int, height: Int + ) -> CGImage? { let mutableData = UnsafeMutablePointer.allocate(capacity: width * height) diff --git a/Gaze/Services/TimerEngine.swift b/Gaze/Services/TimerEngine.swift index 85c3999..e1ac835 100644 --- a/Gaze/Services/TimerEngine.swift +++ b/Gaze/Services/TimerEngine.swift @@ -7,7 +7,6 @@ import Combine import Foundation -import os.log @MainActor class TimerEngine: ObservableObject { @@ -17,20 +16,17 @@ class TimerEngine: ObservableObject { private var timerSubscription: AnyCancellable? private let settingsProvider: any SettingsProviding private var sleepStartTime: Date? - + /// Time provider for deterministic testing (defaults to system time) private let timeProvider: TimeProviding - + // For enforce mode integration private var enforceModeService: EnforceModeService? - + // Smart Mode services private var fullscreenService: FullscreenDetectionService? private var idleService: IdleMonitoringService? private var cancellables = Set() - - // Logging manager - private let logger = LoggingManager.shared.timerLogger convenience init( settingsManager: any SettingsProviding, @@ -51,19 +47,19 @@ class TimerEngine: ObservableObject { self.settingsProvider = settingsManager self.enforceModeService = enforceModeService ?? EnforceModeService.shared self.timeProvider = timeProvider - + Task { @MainActor in self.enforceModeService?.setTimerEngine(self) } } - + func setupSmartMode( fullscreenService: FullscreenDetectionService?, idleService: IdleMonitoringService? ) { self.fullscreenService = fullscreenService self.idleService = idleService - + // Subscribe to fullscreen state changes fullscreenService?.$isFullscreenActive .sink { [weak self] isFullscreen in @@ -72,7 +68,7 @@ class TimerEngine: ObservableObject { } } .store(in: &cancellables) - + // Subscribe to idle state changes idleService?.$isIdle .sink { [weak self] isIdle in @@ -82,31 +78,31 @@ class TimerEngine: ObservableObject { } .store(in: &cancellables) } - + private func handleFullscreenChange(isFullscreen: Bool) { guard settingsProvider.settings.smartMode.autoPauseOnFullscreen else { return } - + if isFullscreen { pauseAllTimers(reason: .fullscreen) - logger.info("⏸️ Timers paused: fullscreen detected") + logInfo("⏸️ Timers paused: fullscreen detected") } else { resumeAllTimers(reason: .fullscreen) - logger.info("▢️ Timers resumed: fullscreen exited") + logInfo("▢️ Timers resumed: fullscreen exited") } } - + private func handleIdleChange(isIdle: Bool) { guard settingsProvider.settings.smartMode.autoPauseOnIdle else { return } - + if isIdle { pauseAllTimers(reason: .idle) - logger.info("⏸️ Timers paused: user idle") + logInfo("⏸️ Timers paused: user idle") } else { resumeAllTimers(reason: .idle) - logger.info("▢️ Timers resumed: user active") + logInfo("▢️ Timers resumed: user active") } } - + private func pauseAllTimers(reason: PauseReason) { for (id, var state) in timerStates { state.pauseReasons.insert(reason) @@ -114,7 +110,7 @@ class TimerEngine: ObservableObject { timerStates[id] = state } } - + private func resumeAllTimers(reason: PauseReason) { for (id, var state) in timerStates { state.pauseReasons.remove(reason) @@ -129,12 +125,12 @@ class TimerEngine: ObservableObject { updateConfigurations() return } - + // Initial start - create all timer states stop() var newStates: [TimerIdentifier: TimerState] = [:] - + // Add built-in timers for timerType in TimerType.allCases { let config = settingsProvider.timerConfiguration(for: timerType) @@ -148,7 +144,7 @@ class TimerEngine: ObservableObject { ) } } - + // Add user timers for userTimer in settingsProvider.settings.userTimers where userTimer.enabled { let identifier = TimerIdentifier.user(id: userTimer.id) @@ -159,7 +155,7 @@ class TimerEngine: ObservableObject { isActive: true ) } - + // Assign the entire dictionary at once to trigger @Published timerStates = newStates @@ -171,27 +167,27 @@ class TimerEngine: ObservableObject { } } } - + /// Check if enforce mode is active and should affect timer behavior func checkEnforceMode() { // Deprecated - camera is now activated in handleTick before timer triggers } - + private func updateConfigurations() { - logger.debug("Updating timer configurations") + logDebug("Updating timer configurations") var newStates: [TimerIdentifier: TimerState] = [:] - + // Update built-in timers for timerType in TimerType.allCases { let config = settingsProvider.timerConfiguration(for: timerType) let identifier = TimerIdentifier.builtIn(timerType) - + if config.enabled { if let existingState = timerStates[identifier] { // Timer exists - check if interval changed if existingState.originalIntervalSeconds != config.intervalSeconds { // Interval changed - reset with new interval - logger.debug("Timer interval changed") + logDebug("Timer interval changed") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: config.intervalSeconds, @@ -204,7 +200,7 @@ class TimerEngine: ObservableObject { } } else { // Timer was just enabled - create new state - logger.debug("Timer enabled") + logDebug("Timer enabled") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: config.intervalSeconds, @@ -215,18 +211,18 @@ class TimerEngine: ObservableObject { } // If config.enabled is false and timer exists, it will be removed } - + // Update user timers for userTimer in settingsProvider.settings.userTimers { let identifier = TimerIdentifier.user(id: userTimer.id) let newIntervalSeconds = userTimer.intervalMinutes * 60 - + if userTimer.enabled { if let existingState = timerStates[identifier] { // Check if interval changed if existingState.originalIntervalSeconds != newIntervalSeconds { // Interval changed - reset with new interval - logger.debug("User timer interval changed") + logDebug("User timer interval changed") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: newIntervalSeconds, @@ -239,7 +235,7 @@ class TimerEngine: ObservableObject { } } else { // New timer - create state - logger.debug("User timer created") + logDebug("User timer created") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: newIntervalSeconds, @@ -250,7 +246,7 @@ class TimerEngine: ObservableObject { } // If timer is disabled, it will be removed } - + // Assign the entire dictionary at once to trigger @Published timerStates = newStates } @@ -276,14 +272,14 @@ class TimerEngine: ObservableObject { timerStates[id] = state } } - + func pauseTimer(identifier: TimerIdentifier) { guard var state = timerStates[identifier] else { return } state.pauseReasons.insert(.manual) state.isPaused = true timerStates[identifier] = state } - + func resumeTimer(identifier: TimerIdentifier) { guard var state = timerStates[identifier] else { return } state.pauseReasons.remove(.manual) @@ -293,17 +289,18 @@ class TimerEngine: ObservableObject { func skipNext(identifier: TimerIdentifier) { guard let state = timerStates[identifier] else { return } - + let intervalSeconds: Int switch identifier { case .builtIn(let type): let config = settingsProvider.timerConfiguration(for: type) intervalSeconds = config.intervalSeconds case .user(let id): - guard let userTimer = settingsProvider.settings.userTimers.first(where: { $0.id == id }) else { return } + guard let userTimer = settingsProvider.settings.userTimers.first(where: { $0.id == id }) + else { return } intervalSeconds = userTimer.intervalMinutes * 60 } - + timerStates[identifier] = TimerState( identifier: identifier, intervalSeconds: intervalSeconds, @@ -319,7 +316,7 @@ class TimerEngine: ObservableObject { let identifier = reminder.identifier skipNext(identifier: identifier) resumeTimer(identifier: identifier) - + enforceModeService?.handleReminderDismissed() } @@ -327,7 +324,7 @@ class TimerEngine: ObservableObject { for (identifier, state) in timerStates { guard !state.isPaused else { continue } guard state.isActive else { continue } - + if state.targetDate < timeProvider.now() - 3.0 { skipNext(identifier: identifier) continue @@ -340,12 +337,13 @@ class TimerEngine: ObservableObject { if case .builtIn(.lookAway) = identifier { if enforceModeService?.shouldEnforceBreak(for: identifier) == true { Task { @MainActor in - await enforceModeService?.startCameraForLookawayTimer(secondsRemaining: updatedState.remainingSeconds) + await enforceModeService?.startCameraForLookawayTimer( + secondsRemaining: updatedState.remainingSeconds) } } } } - + if updatedState.remainingSeconds <= 0 { triggerReminder(for: identifier) break @@ -357,7 +355,7 @@ class TimerEngine: ObservableObject { func triggerReminder(for identifier: TimerIdentifier) { // Pause only the timer that triggered pauseTimer(identifier: identifier) - + switch identifier { case .builtIn(let type): switch type { @@ -384,16 +382,16 @@ class TimerEngine: ObservableObject { func getFormattedTimeRemaining(for identifier: TimerIdentifier) -> String { return getTimeRemaining(for: identifier).formatAsTimerDurationFull() } - + func isTimerPaused(_ identifier: TimerIdentifier) -> Bool { return timerStates[identifier]?.isPaused ?? true } - + /// Handles system sleep event /// - Saves current time for elapsed calculation /// - Pauses all active timers func handleSystemSleep() { - logger.debug("System going to sleep") + logDebug("System going to sleep") sleepStartTime = timeProvider.now() for (id, var state) in timerStates { state.pauseReasons.insert(.system) @@ -401,24 +399,24 @@ class TimerEngine: ObservableObject { timerStates[id] = state } } - + /// Handles system wake event /// - Calculates elapsed time during sleep /// - Adjusts remaining time for all active timers /// - Timers that expired during sleep will trigger immediately (1s delay) /// - Resumes all timers func handleSystemWake() { - logger.debug("System waking up") + logDebug("System waking up") guard let sleepStart = sleepStartTime else { return } - + defer { sleepStartTime = nil } - + let elapsedSeconds = Int(timeProvider.now().timeIntervalSince(sleepStart)) - + guard elapsedSeconds >= 1 else { for (id, var state) in timerStates { state.pauseReasons.remove(.system) @@ -427,18 +425,19 @@ class TimerEngine: ObservableObject { } return } - + for (identifier, state) in timerStates where state.isActive { var updatedState = state updatedState.remainingSeconds = max(0, state.remainingSeconds - elapsedSeconds) - + if updatedState.remainingSeconds <= 0 { updatedState.remainingSeconds = 1 } - + updatedState.pauseReasons.remove(.system) updatedState.isPaused = !updatedState.pauseReasons.isEmpty timerStates[identifier] = updatedState } } -} \ No newline at end of file +} + diff --git a/Gaze/Views/MenuBar/MenuBarContentView.swift b/Gaze/Views/MenuBar/MenuBarContentView.swift index 1494cf8..d7f2bc0 100644 --- a/Gaze/Views/MenuBar/MenuBarContentView.swift +++ b/Gaze/Views/MenuBar/MenuBarContentView.swift @@ -201,7 +201,6 @@ struct MenuBarContentView: View { .padding(.vertical, 6) } .buttonStyle(MenuBarHoverButtonStyle()) - .padding(.horizontal, 8) .padding(.vertical, 8) Spacer() Text(