This commit is contained in:
Michael Freno
2026-01-27 18:46:15 -05:00
parent f8868c9253
commit 224f6d2a68
8 changed files with 108 additions and 82 deletions

View File

@@ -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<AnyCancellable>()
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() {

View File

@@ -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?,

View File

@@ -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()
}
}

View File

@@ -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 {

View File

@@ -32,7 +32,8 @@ struct AdditionalModifiersView: View {
)
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")

View File

@@ -132,7 +132,8 @@ 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
}
@@ -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)

View File

@@ -136,7 +136,7 @@ struct OnboardingContainerView: View {
.tag(0)
.tabItem { Image(systemName: "hand.wave.fill") }
MenuBarWelcomeView()
MenuBarTargetView()
.tag(1)
.tabItem { Image(systemName: "menubar.rectangle") }

View File

@@ -1,5 +1,5 @@
//
// MenuBarWelcomeView.swift
// MenuBarTargetView.swift
// Gaze
//
// Created by Mike Freno on 1/17/26.
@@ -7,7 +7,7 @@
import SwiftUI
struct MenuBarWelcomeView: View {
struct MenuBarTargetView: View {
@Environment(\.isCompactLayout) private var isCompact
private var iconSize: CGFloat {
@@ -64,5 +64,5 @@ struct MenuBarWelcomeView: View {
}
#Preview("Menu Bar Welcome") {
MenuBarWelcomeView()
MenuBarTargetView()
}