From 224f6d2a68052bb0045a44d5d5fdd7017e4f0c0f Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Tue, 27 Jan 2026 18:46:15 -0500 Subject: [PATCH] fixed --- Gaze/AppDelegate.swift | 37 +++-------- Gaze/Protocols/TimerEngineProviding.swift | 6 -- Gaze/Services/SystemSleepManager.swift | 63 +++++++++++++++++++ Gaze/Services/TimerEngine.swift | 24 ++----- .../Containers/AdditionalModifiersView.swift | 21 +++++-- .../Containers/MenuBarGuideOverlayView.swift | 23 +++---- .../Containers/OnboardingContainerView.swift | 2 +- ...comeView.swift => MenuBarTargetView.swift} | 14 ++--- 8 files changed, 108 insertions(+), 82 deletions(-) create mode 100644 Gaze/Services/SystemSleepManager.swift rename Gaze/Views/Setup/{MenuBarWelcomeView.swift => MenuBarTargetView.swift} (95%) diff --git a/Gaze/AppDelegate.swift b/Gaze/AppDelegate.swift index 9ab3c30..0dfe686 100644 --- a/Gaze/AppDelegate.swift +++ b/Gaze/AppDelegate.swift @@ -15,6 +15,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private let serviceContainer: ServiceContainer private let windowManager: WindowManaging private var updateManager: UpdateManager? + private var systemSleepManager: SystemSleepManager? private var cancellables = Set() private var hasStartedTimers = false @@ -46,6 +47,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } timerEngine = serviceContainer.timerEngine + systemSleepManager = SystemSleepManager( + timerEngine: timerEngine, + settingsManager: settingsManager + ) + systemSleepManager?.startObserving() serviceContainer.setupSmartModeServices() @@ -54,8 +60,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { updateManager = UpdateManager.shared } - setupLifecycleObservers() - observeSettingsChanges() if settingsManager.settings.hasCompletedOnboarding { @@ -129,34 +133,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { func applicationWillTerminate(_ notification: Notification) { logInfo(" applicationWill terminate") settingsManager.saveImmediately() + stopLifecycleObservers() timerEngine?.stop() } - private func setupLifecycleObservers() { - NSWorkspace.shared.notificationCenter.addObserver( - self, - selector: #selector(systemWillSleep), - name: NSWorkspace.willSleepNotification, - object: nil - ) - - NSWorkspace.shared.notificationCenter.addObserver( - self, - selector: #selector(systemDidWake), - name: NSWorkspace.didWakeNotification, - object: nil - ) - } - - @objc private func systemWillSleep() { - logInfo("System will sleep") - timerEngine?.handleSystemSleep() - settingsManager.saveImmediately() - } - - @objc private func systemDidWake() { - logInfo("System did wake") - timerEngine?.handleSystemWake() + private func stopLifecycleObservers() { + systemSleepManager?.stopObserving() + systemSleepManager = nil } private func observeReminderEvents() { diff --git a/Gaze/Protocols/TimerEngineProviding.swift b/Gaze/Protocols/TimerEngineProviding.swift index c6a246c..d7ba5ab 100644 --- a/Gaze/Protocols/TimerEngineProviding.swift +++ b/Gaze/Protocols/TimerEngineProviding.swift @@ -60,12 +60,6 @@ protocol TimerEngineProviding: AnyObject, ObservableObject { /// Checks if a timer is currently paused func isTimerPaused(_ identifier: TimerIdentifier) -> Bool - /// Handles system sleep event - func handleSystemSleep() - - /// Handles system wake event - func handleSystemWake() - /// Sets up smart mode with fullscreen and idle detection services func setupSmartMode( fullscreenService: FullscreenDetectionService?, diff --git a/Gaze/Services/SystemSleepManager.swift b/Gaze/Services/SystemSleepManager.swift new file mode 100644 index 0000000..1367a9e --- /dev/null +++ b/Gaze/Services/SystemSleepManager.swift @@ -0,0 +1,63 @@ +// +// SystemSleepManager.swift +// Gaze +// +// Coordinates system sleep/wake handling. +// + +import AppKit +import Foundation + +@MainActor +final class SystemSleepManager { + private let settingsManager: any SettingsProviding + private weak var timerEngine: (any TimerEngineProviding)? + private var observers: [NSObjectProtocol] = [] + + init(timerEngine: (any TimerEngineProviding)?, settingsManager: any SettingsProviding) { + self.timerEngine = timerEngine + self.settingsManager = settingsManager + } + + func startObserving() { + guard observers.isEmpty else { return } + + let center = NSWorkspace.shared.notificationCenter + let willSleep = center.addObserver( + forName: NSWorkspace.willSleepNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.handleSystemWillSleep() + } + + let didWake = center.addObserver( + forName: NSWorkspace.didWakeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.handleSystemDidWake() + } + + observers = [willSleep, didWake] + } + + func stopObserving() { + let center = NSWorkspace.shared.notificationCenter + for observer in observers { + center.removeObserver(observer) + } + observers.removeAll() + } + + private func handleSystemWillSleep() { + logInfo("System will sleep") + timerEngine?.stop() + settingsManager.saveImmediately() + } + + private func handleSystemDidWake() { + logInfo("System did wake") + timerEngine?.start() + } +} diff --git a/Gaze/Services/TimerEngine.swift b/Gaze/Services/TimerEngine.swift index a805244..0cf509b 100644 --- a/Gaze/Services/TimerEngine.swift +++ b/Gaze/Services/TimerEngine.swift @@ -138,7 +138,7 @@ class TimerEngine: ObservableObject { let intervalSeconds = getTimerInterval(for: identifier) stateManager.resetTimer(identifier: identifier, intervalSeconds: intervalSeconds) } - + /// Unified way to get interval for any timer type private func getTimerInterval(for identifier: TimerIdentifier) -> Int { switch identifier { @@ -146,7 +146,8 @@ class TimerEngine: ObservableObject { let config = settingsProvider.timerConfiguration(for: type) return config.intervalSeconds case .user(let id): - guard let userTimer = settingsProvider.settings.userTimers.first(where: { $0.id == id }) else { + guard let userTimer = settingsProvider.settings.userTimers.first(where: { $0.id == id }) + else { return 0 } return userTimer.intervalMinutes * 60 @@ -183,7 +184,8 @@ class TimerEngine: ObservableObject { secondsRemaining: updatedState.remainingSeconds ) { Task { @MainActor in - await reminderService.prepareEnforceMode(secondsRemaining: updatedState.remainingSeconds) + await reminderService.prepareEnforceMode( + secondsRemaining: updatedState.remainingSeconds) } } @@ -215,22 +217,6 @@ class TimerEngine: ObservableObject { return stateManager.isTimerPaused(identifier) } - // System sleep/wake handling is now managed by SystemSleepManager - // This method is kept for compatibility but will be removed in future versions - /// Handles system sleep event - deprecated - @available(*, deprecated, message: "Use SystemSleepManager instead") - func handleSystemSleep() { - logDebug("System going to sleep (deprecated)") - // This functionality has been moved to SystemSleepManager - } - - /// Handles system wake event - deprecated - @available(*, deprecated, message: "Use SystemSleepManager instead") - func handleSystemWake() { - logDebug("System waking up (deprecated)") - // This functionality has been moved to SystemSleepManager - } - private func timerConfigurations() -> [TimerIdentifier: TimerConfiguration] { var configurations: [TimerIdentifier: TimerConfiguration] = [:] for timerType in TimerType.allCases { diff --git a/Gaze/Views/Containers/AdditionalModifiersView.swift b/Gaze/Views/Containers/AdditionalModifiersView.swift index 06b3b0f..a9b01e7 100644 --- a/Gaze/Views/Containers/AdditionalModifiersView.swift +++ b/Gaze/Views/Containers/AdditionalModifiersView.swift @@ -21,7 +21,7 @@ struct AdditionalModifiersView: View { GeometryReader { geometry in let availableWidth = geometry.size.width - 80 // Account for padding let availableHeight = geometry.size.height - 200 // Account for header and nav - + let cardWidth = min( max(availableWidth * 0.85, AdaptiveLayout.Card.minWidth), AdaptiveLayout.Card.maxWidth @@ -30,9 +30,10 @@ struct AdditionalModifiersView: View { max(availableHeight * 0.75, AdaptiveLayout.Card.minHeight), AdaptiveLayout.Card.maxHeight ) - + VStack(spacing: 0) { - SetupHeader(icon: "slider.horizontal.3", title: "Additional Options", color: .purple) + SetupHeader( + icon: "slider.horizontal.3", title: "Additional Options", color: .purple) Text("Optional features to enhance your experience") .font(isCompact ? .subheadline : .title3) @@ -77,7 +78,7 @@ struct AdditionalModifiersView: View { HStack(spacing: isCompact ? 10 : 16) { cardIndicator(index: 0, icon: "video.fill", label: "Enforce") cardIndicator(index: 1, icon: "brain.fill", label: "Smart") - } + }.padding(.all, 20) Button(action: { swapCards() }) { Image(systemName: "chevron.right") @@ -211,7 +212,11 @@ struct AdditionalModifiersView: View { private var enforceModeContent: some View { VStack(spacing: isCompact ? 10 : 16) { Image(systemName: "video.fill") - .font(.system(size: isCompact ? AdaptiveLayout.Font.cardIconSmall : AdaptiveLayout.Font.cardIcon)) + .font( + .system( + size: isCompact + ? AdaptiveLayout.Font.cardIconSmall : AdaptiveLayout.Font.cardIcon) + ) .foregroundStyle(Color.accentColor) Text("Enforce Mode") @@ -292,7 +297,11 @@ struct AdditionalModifiersView: View { private var smartModeContent: some View { VStack(spacing: isCompact ? 10 : 16) { Image(systemName: "brain.fill") - .font(.system(size: isCompact ? AdaptiveLayout.Font.cardIconSmall : AdaptiveLayout.Font.cardIcon)) + .font( + .system( + size: isCompact + ? AdaptiveLayout.Font.cardIconSmall : AdaptiveLayout.Font.cardIcon) + ) .foregroundStyle(.purple) Text("Smart Mode") diff --git a/Gaze/Views/Containers/MenuBarGuideOverlayView.swift b/Gaze/Views/Containers/MenuBarGuideOverlayView.swift index e0eb86e..49c3109 100644 --- a/Gaze/Views/Containers/MenuBarGuideOverlayView.swift +++ b/Gaze/Views/Containers/MenuBarGuideOverlayView.swift @@ -118,13 +118,13 @@ final class MenuBarGuideOverlayPresenter { let overlayView = MenuBarGuideOverlayView() window.contentView = NSHostingView(rootView: overlayView) } - + func setupOnboardingWindowObserver() { // Remove any existing observer to prevent duplicates if let observer = onboardingWindowObserver { NotificationCenter.default.removeObserver(observer) } - + // Add observer for when the onboarding window is closed onboardingWindowObserver = NotificationCenter.default.addObserver( forName: NSWindow.willCloseNotification, @@ -132,10 +132,11 @@ final class MenuBarGuideOverlayPresenter { queue: .main ) { [weak self] notification in guard let window = notification.object as? NSWindow, - window.identifier == WindowIdentifiers.onboarding else { + window.identifier == WindowIdentifiers.onboarding + else { return } - + // Hide the overlay when onboarding window closes self?.hide() } @@ -176,8 +177,8 @@ struct MenuBarGuideOverlayView: View { $0.identifier == WindowIdentifiers.onboarding }) { let windowFrame = onboardingWindow.frame - let textRightX = windowFrame.midX + 210 - let textY = screenFrame.maxY - windowFrame.maxY + 505 + let textRightX = windowFrame.midX + let textY = screenFrame.maxY - windowFrame.maxY + 305 return CGPoint(x: textRightX, y: textY) } return CGPoint(x: screenSize.width * 0.5, y: screenSize.height * 0.45) @@ -195,7 +196,6 @@ struct HandDrawnArrowShape: Shape { // This creates a more playful, hand-drawn feel let dx = end.x - start.x - let dy = end.y - start.y // First control point: go DOWN and slightly toward target let ctrl1 = CGPoint( @@ -203,23 +203,14 @@ struct HandDrawnArrowShape: Shape { y: start.y + 120 // Go DOWN first ) - // Second control point: curve back up toward target let ctrl2 = CGPoint( x: start.x + dx * 0.6, y: start.y + 80 ) - // Third control point: approach target from below-ish - let ctrl3 = CGPoint( - x: end.x - dx * 0.15, - y: end.y + 60 - ) - - // Add slight hand-drawn wobble let wobble: CGFloat = 2.5 let wobbledCtrl1 = CGPoint(x: ctrl1.x + wobble, y: ctrl1.y - wobble) let wobbledCtrl2 = CGPoint(x: ctrl2.x - wobble, y: ctrl2.y + wobble) - let wobbledCtrl3 = CGPoint(x: ctrl3.x + wobble * 0.5, y: ctrl3.y - wobble) path.move(to: start) path.addCurve(to: end, control1: wobbledCtrl1, control2: wobbledCtrl2) diff --git a/Gaze/Views/Containers/OnboardingContainerView.swift b/Gaze/Views/Containers/OnboardingContainerView.swift index b9b3dc9..971bfe0 100644 --- a/Gaze/Views/Containers/OnboardingContainerView.swift +++ b/Gaze/Views/Containers/OnboardingContainerView.swift @@ -136,7 +136,7 @@ struct OnboardingContainerView: View { .tag(0) .tabItem { Image(systemName: "hand.wave.fill") } - MenuBarWelcomeView() + MenuBarTargetView() .tag(1) .tabItem { Image(systemName: "menubar.rectangle") } diff --git a/Gaze/Views/Setup/MenuBarWelcomeView.swift b/Gaze/Views/Setup/MenuBarTargetView.swift similarity index 95% rename from Gaze/Views/Setup/MenuBarWelcomeView.swift rename to Gaze/Views/Setup/MenuBarTargetView.swift index 247e82e..5c88e1e 100644 --- a/Gaze/Views/Setup/MenuBarWelcomeView.swift +++ b/Gaze/Views/Setup/MenuBarTargetView.swift @@ -1,5 +1,5 @@ // -// MenuBarWelcomeView.swift +// MenuBarTargetView.swift // Gaze // // Created by Mike Freno on 1/17/26. @@ -7,21 +7,21 @@ import SwiftUI -struct MenuBarWelcomeView: View { +struct MenuBarTargetView: View { @Environment(\.isCompactLayout) private var isCompact - + private var iconSize: CGFloat { isCompact ? AdaptiveLayout.Font.heroIconSmall : AdaptiveLayout.Font.heroIcon } - + private var titleSize: CGFloat { isCompact ? AdaptiveLayout.Font.heroTitleSmall : AdaptiveLayout.Font.heroTitle } - + private var spacing: CGFloat { isCompact ? AdaptiveLayout.Spacing.compact : AdaptiveLayout.Spacing.standard } - + var body: some View { VStack(spacing: spacing * 1.5) { Spacer() @@ -64,5 +64,5 @@ struct MenuBarWelcomeView: View { } #Preview("Menu Bar Welcome") { - MenuBarWelcomeView() + MenuBarTargetView() }