general: basic cleanup

This commit is contained in:
Michael Freno
2026-01-17 09:09:09 -05:00
parent 03ab6160d2
commit a528a549b9
8 changed files with 259 additions and 298 deletions

View File

@@ -28,11 +28,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
self.windowManager = WindowManager.shared self.windowManager = WindowManager.shared
super.init() super.init()
// Setup window close observers
setupWindowCloseObservers()
} }
/// Initializer for testing with injectable dependencies
init(serviceContainer: ServiceContainer, windowManager: WindowManaging) { init(serviceContainer: ServiceContainer, windowManager: WindowManaging) {
self.serviceContainer = serviceContainer self.serviceContainer = serviceContainer
self.windowManager = windowManager self.windowManager = windowManager
@@ -40,16 +37,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
} }
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
// Set activation policy to hide dock icon
NSApplication.shared.setActivationPolicy(.accessory) NSApplication.shared.setActivationPolicy(.accessory)
logInfo("🚀 Application did finish launching")
timerEngine = serviceContainer.timerEngine timerEngine = serviceContainer.timerEngine
serviceContainer.setupSmartModeServices() serviceContainer.setupSmartModeServices()
// Initialize update manager after onboarding is complete // Initialize update manager after onboarding is complete
if settingsManager.settings.hasCompletedOnboarding { if settingsManager.settings.hasCompletedOnboarding {
updateManager = UpdateManager.shared updateManager = UpdateManager.shared
@@ -62,24 +55,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
if settingsManager.settings.hasCompletedOnboarding { if settingsManager.settings.hasCompletedOnboarding {
startTimers() startTimers()
} }
// DEBUG: Auto-start eye tracking test mode if launch argument is present
#if DEBUG
if CommandLine.arguments.contains("--debug-eye-tracking") {
NSLog("🔬 DEBUG: Auto-starting eye tracking test mode")
Task { @MainActor in
// Enable enforce mode if not already
if !settingsManager.settings.enforcementMode {
settingsManager.settings.enforcementMode = true
}
// Start test mode after a brief delay
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
NSLog("🔬 DEBUG: Starting test mode now...")
await EnforceModeService.shared.startTestMode()
NSLog("🔬 DEBUG: Test mode started")
}
}
#endif
} }
// Note: Smart mode setup is now handled by ServiceContainer // Note: Smart mode setup is now handled by ServiceContainer
@@ -105,7 +80,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
func onboardingCompleted() { func onboardingCompleted() {
startTimers() startTimers()
// Start update checks after onboarding
if updateManager == nil { if updateManager == nil {
updateManager = UpdateManager.shared updateManager = UpdateManager.shared
} }
@@ -249,24 +223,4 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
} }
} }
private func setupWindowCloseObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(settingsWindowDidClose),
name: Notification.Name("SettingsWindowDidClose"),
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(onboardingWindowDidClose),
name: Notification.Name("OnboardingWindowDidClose"),
object: nil
)
}
@objc private func settingsWindowDidClose() {}
@objc private func onboardingWindowDidClose() {}
} }

View File

@@ -41,7 +41,7 @@ struct GazeApp: App {
} }
.windowStyle(.hiddenTitleBar) .windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize) .windowResizability(.contentSize)
.defaultSize(width: 700, height: 700) .defaultSize(width: 1000, height: 700)
.commands { .commands {
CommandGroup(replacing: .newItem) {} CommandGroup(replacing: .newItem) {}
} }

View File

@@ -5,9 +5,9 @@
// Fullscreen overlay view for eye tracking calibration targets. // Fullscreen overlay view for eye tracking calibration targets.
// //
import SwiftUI
import Combine
import AVFoundation import AVFoundation
import Combine
import SwiftUI
struct CalibrationOverlayView: View { struct CalibrationOverlayView: View {
@StateObject private var calibrationManager = CalibrationManager.shared @StateObject private var calibrationManager = CalibrationManager.shared
@@ -35,7 +35,9 @@ struct CalibrationOverlayView: View {
startingCameraView startingCameraView
} else if calibrationManager.isCalibrating { } else if calibrationManager.isCalibrating {
calibrationContentView(screenSize: geometry.size) calibrationContentView(screenSize: geometry.size)
} else if viewModel.calibrationStarted && calibrationManager.calibrationData.isComplete { } else if viewModel.calibrationStarted
&& calibrationManager.calibrationData.isComplete
{
// Only show completion if we started calibration this session AND it completed // Only show completion if we started calibration this session AND it completed
completionView completionView
} else if viewModel.calibrationStarted { } else if viewModel.calibrationStarted {
@@ -45,10 +47,12 @@ struct CalibrationOverlayView: View {
} }
} }
.task { .task {
await viewModel.startCamera(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) await viewModel.startCamera(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
} }
.onDisappear { .onDisappear {
viewModel.cleanup(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) viewModel.cleanup(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
} }
.onChange(of: calibrationManager.currentStep) { oldStep, newStep in .onChange(of: calibrationManager.currentStep) { oldStep, newStep in
if newStep != nil && oldStep != newStep { if newStep != nil && oldStep != newStep {
@@ -200,14 +204,16 @@ struct CalibrationOverlayView: View {
.stroke(Color.green, lineWidth: 4) .stroke(Color.green, lineWidth: 4)
.frame(width: 90, height: 90) .frame(width: 90, height: 90)
.rotationEffect(.degrees(-90)) .rotationEffect(.degrees(-90))
.animation(.linear(duration: 0.1), value: calibrationManager.samplesCollected) .animation(
.linear(duration: 0.1), value: calibrationManager.samplesCollected)
} }
// Inner circle // Inner circle
Circle() Circle()
.fill(calibrationManager.isCollectingSamples ? Color.green : Color.blue) .fill(calibrationManager.isCollectingSamples ? Color.green : Color.blue)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.animation(.easeInOut(duration: 0.3), value: calibrationManager.isCollectingSamples) .animation(
.easeInOut(duration: 0.3), value: calibrationManager.isCollectingSamples)
// Countdown number or collecting indicator // Countdown number or collecting indicator
if viewModel.isCountingDown && viewModel.countdownValue > 0 { if viewModel.isCountingDown && viewModel.countdownValue > 0 {
@@ -260,7 +266,8 @@ struct CalibrationOverlayView: View {
private var cancelButton: some View { private var cancelButton: some View {
Button { Button {
viewModel.cleanup(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) viewModel.cleanup(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
onDismiss() onDismiss()
} label: { } label: {
HStack(spacing: 6) { HStack(spacing: 6) {
@@ -362,7 +369,9 @@ class CalibrationOverlayViewModel: ObservableObject {
private var lastFaceDetectedTime: Date = .distantPast private var lastFaceDetectedTime: Date = .distantPast
private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce
func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager) async { func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager)
async
{
do { do {
try await eyeTrackingService.startEyeTracking() try await eyeTrackingService.startEyeTracking()
cameraStarted = true cameraStarted = true
@@ -370,8 +379,7 @@ class CalibrationOverlayViewModel: ObservableObject {
// Set up debounced face detection // Set up debounced face detection
setupFaceDetectionObserver(eyeTrackingService: eyeTrackingService) setupFaceDetectionObserver(eyeTrackingService: eyeTrackingService)
// Small delay to let camera stabilize try? await Task.sleep(for: .seconds(0.5))
try? await Task.sleep(nanoseconds: 500_000_000)
// Reset any previous calibration data before starting fresh // Reset any previous calibration data before starting fresh
calibrationManager.resetForNewCalibration() calibrationManager.resetForNewCalibration()
@@ -446,4 +454,3 @@ class CalibrationOverlayViewModel: ObservableObject {
#Preview { #Preview {
CalibrationOverlayView(onDismiss: {}) CalibrationOverlayView(onDismiss: {})
} }

View File

@@ -83,7 +83,9 @@ final class OnboardingWindowPresenter {
window.titlebarAppearsTransparent = true window.titlebarAppearsTransparent = true
window.center() window.center()
window.isReleasedWhenClosed = true window.isReleasedWhenClosed = true
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary] window.collectionBehavior = [
.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary,
]
window.contentView = NSHostingView( window.contentView = NSHostingView(
rootView: OnboardingContainerView(settingsManager: settingsManager) rootView: OnboardingContainerView(settingsManager: settingsManager)

View File

@@ -6,6 +6,7 @@
// //
import XCTest import XCTest
@testable import Gaze @testable import Gaze
@MainActor @MainActor
@@ -30,20 +31,16 @@ final class AppDelegateTestabilityTests: XCTestCase {
func testWindowManagerReceivesReminderEvents() async throws { func testWindowManagerReceivesReminderEvents() async throws {
let appDelegate = testEnv.createAppDelegate() let appDelegate = testEnv.createAppDelegate()
// Simulate app launch
let notification = Notification(name: NSApplication.didFinishLaunchingNotification) let notification = Notification(name: NSApplication.didFinishLaunchingNotification)
appDelegate.applicationDidFinishLaunching(notification) appDelegate.applicationDidFinishLaunching(notification)
// Give time for setup try await Task.sleep(for: .milliseconds(100))
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
// Trigger a reminder through timer engine
if let timerEngine = appDelegate.timerEngine { if let timerEngine = appDelegate.timerEngine {
let timerId = TimerIdentifier.builtIn(.blink) let timerId = TimerIdentifier.builtIn(.blink)
timerEngine.triggerReminder(for: timerId) timerEngine.triggerReminder(for: timerId)
// Give time for reminder to propagate try await Task.sleep(for: .milliseconds(100))
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
// Verify window manager received the show command // Verify window manager received the show command
XCTAssertTrue(testEnv.windowManager.didPerformOperation(.showSubtleReminder)) XCTAssertTrue(testEnv.windowManager.didPerformOperation(.showSubtleReminder))
@@ -58,8 +55,7 @@ final class AppDelegateTestabilityTests: XCTestCase {
// Change a setting // Change a setting
testEnv.settingsManager.settings.lookAwayTimer.enabled = false testEnv.settingsManager.settings.lookAwayTimer.enabled = false
// Give time for observation try await Task.sleep(for: .milliseconds(50))
try await Task.sleep(nanoseconds: 50_000_000) // 50ms
// Verify the change propagated // Verify the change propagated
XCTAssertFalse(testEnv.settingsManager.settings.lookAwayTimer.enabled) XCTAssertFalse(testEnv.settingsManager.settings.lookAwayTimer.enabled)
@@ -73,7 +69,8 @@ final class AppDelegateTestabilityTests: XCTestCase {
// Give time for async dispatch // Give time for async dispatch
let expectation = XCTestExpectation(description: "Settings opened") let expectation = XCTestExpectation(description: "Settings opened")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
XCTAssertTrue(self.testEnv.windowManager.didPerformOperation(.showSettings(initialTab: 2))) XCTAssertTrue(
self.testEnv.windowManager.didPerformOperation(.showSettings(initialTab: 2)))
expectation.fulfill() expectation.fulfill()
} }

View File

@@ -7,6 +7,7 @@
import Combine import Combine
import XCTest import XCTest
@testable import Gaze @testable import Gaze
@MainActor @MainActor
@@ -159,8 +160,7 @@ final class TimerEngineTests: XCTestCase {
timerEngine.triggerReminder(for: firstTimer) timerEngine.triggerReminder(for: firstTimer)
// Give time for async operations try await Task.sleep(for: .milliseconds(50))
try await Task.sleep(nanoseconds: 50_000_000)
XCTAssertNotNil(timerEngine.activeReminder) XCTAssertNotNil(timerEngine.activeReminder)
} }

View File

@@ -5,8 +5,11 @@
// Test helpers and utilities for unit testing. // Test helpers and utilities for unit testing.
// //
// MARK: - Import Statement for Combine
import Combine
import Foundation import Foundation
import XCTest import XCTest
@testable import Gaze @testable import Gaze
// MARK: - Enhanced MockSettingsManager // MARK: - Enhanced MockSettingsManager
@@ -25,7 +28,8 @@ final class EnhancedMockSettingsManager: SettingsProviding {
} }
@ObservationIgnored @ObservationIgnored
private let timerConfigKeyPaths: [TimerType: WritableKeyPath<AppSettings, TimerConfiguration>] = [ private let timerConfigKeyPaths: [TimerType: WritableKeyPath<AppSettings, TimerConfiguration>] =
[
.lookAway: \.lookAwayTimer, .lookAway: \.lookAwayTimer,
.blink: \.blinkTimer, .blink: \.blinkTimer,
.posture: \.postureTimer, .posture: \.postureTimer,
@@ -245,7 +249,7 @@ extension XCTestCase {
XCTFail(message) XCTFail(message)
return return
} }
try await Task.sleep(nanoseconds: 10_000_000) // 10ms try await Task.sleep(for: .milliseconds(100))
} }
} }
@@ -269,6 +273,3 @@ extension XCTestCase {
cancellable?.cancel() cancellable?.cancel()
} }
} }
// MARK: - Import Statement for Combine
import Combine

View File

@@ -7,6 +7,7 @@
import Combine import Combine
import XCTest import XCTest
@testable import Gaze @testable import Gaze
@MainActor @MainActor
@@ -106,8 +107,7 @@ final class TimerEngineTestabilityTests: XCTestCase {
let timerId = TimerIdentifier.builtIn(.lookAway) let timerId = TimerIdentifier.builtIn(.lookAway)
timerEngine.triggerReminder(for: timerId) timerEngine.triggerReminder(for: timerId)
// Give time for publisher to fire try await Task.sleep(for: .milliseconds(10))
try await Task.sleep(nanoseconds: 10_000_000)
XCTAssertNotNil(receivedReminder) XCTAssertNotNil(receivedReminder)
} }