general: basic cleanup
This commit is contained in:
@@ -28,11 +28,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
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
|
||||
@@ -40,16 +37,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
// Set activation policy to hide dock icon
|
||||
NSApplication.shared.setActivationPolicy(.accessory)
|
||||
|
||||
logInfo("🚀 Application did finish launching")
|
||||
|
||||
timerEngine = serviceContainer.timerEngine
|
||||
|
||||
serviceContainer.setupSmartModeServices()
|
||||
|
||||
|
||||
// Initialize update manager after onboarding is complete
|
||||
if settingsManager.settings.hasCompletedOnboarding {
|
||||
updateManager = UpdateManager.shared
|
||||
@@ -62,24 +55,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
if settingsManager.settings.hasCompletedOnboarding {
|
||||
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
|
||||
@@ -105,7 +80,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
func onboardingCompleted() {
|
||||
startTimers()
|
||||
|
||||
// Start update checks after onboarding
|
||||
if updateManager == nil {
|
||||
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() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ struct GazeApp: App {
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizability(.contentSize)
|
||||
.defaultSize(width: 700, height: 700)
|
||||
.defaultSize(width: 1000, height: 700)
|
||||
.commands {
|
||||
CommandGroup(replacing: .newItem) {}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
// Fullscreen overlay view for eye tracking calibration targets.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import AVFoundation
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct CalibrationOverlayView: View {
|
||||
@StateObject private var calibrationManager = CalibrationManager.shared
|
||||
@StateObject private var eyeTrackingService = EyeTrackingService.shared
|
||||
@StateObject private var viewModel = CalibrationOverlayViewModel()
|
||||
|
||||
|
||||
let onDismiss: () -> Void
|
||||
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
|
||||
// Camera preview at 50% opacity (mirrored for natural feel)
|
||||
if let previewLayer = eyeTrackingService.previewLayer {
|
||||
CameraPreviewView(previewLayer: previewLayer, borderColor: .clear)
|
||||
@@ -28,14 +28,16 @@ struct CalibrationOverlayView: View {
|
||||
.opacity(0.5)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
|
||||
if let error = viewModel.showError {
|
||||
errorView(error)
|
||||
} else if !viewModel.cameraStarted {
|
||||
startingCameraView
|
||||
} else if calibrationManager.isCalibrating {
|
||||
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
|
||||
completionView
|
||||
} else if viewModel.calibrationStarted {
|
||||
@@ -45,10 +47,12 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await viewModel.startCamera(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
await viewModel.startCamera(
|
||||
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
}
|
||||
.onDisappear {
|
||||
viewModel.cleanup(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
viewModel.cleanup(
|
||||
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
}
|
||||
.onChange(of: calibrationManager.currentStep) { oldStep, newStep in
|
||||
if newStep != nil && oldStep != newStep {
|
||||
@@ -56,38 +60,38 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Starting Camera View
|
||||
|
||||
|
||||
private var startingCameraView: some View {
|
||||
VStack(spacing: 20) {
|
||||
ProgressView()
|
||||
.scaleEffect(2)
|
||||
.tint(.white)
|
||||
|
||||
|
||||
Text("Starting camera...")
|
||||
.font(.title2)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Error View
|
||||
|
||||
|
||||
private func errorView(_ message: String) -> some View {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundStyle(.orange)
|
||||
|
||||
|
||||
Text("Camera Error")
|
||||
.font(.title)
|
||||
.foregroundStyle(.white)
|
||||
|
||||
|
||||
Text(message)
|
||||
.font(.body)
|
||||
.foregroundStyle(.gray)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
|
||||
Button("Close") {
|
||||
onDismiss()
|
||||
}
|
||||
@@ -96,20 +100,20 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
.padding(40)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Calibration Content
|
||||
|
||||
|
||||
private func calibrationContentView(screenSize: CGSize) -> some View {
|
||||
ZStack {
|
||||
VStack {
|
||||
progressBar
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
||||
if let step = calibrationManager.currentStep {
|
||||
calibrationTarget(for: step, screenSize: screenSize)
|
||||
}
|
||||
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
@@ -122,7 +126,7 @@ struct CalibrationOverlayView: View {
|
||||
.padding(.horizontal, 40)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
|
||||
// Face detection indicator
|
||||
VStack {
|
||||
HStack {
|
||||
@@ -133,9 +137,9 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Progress Bar
|
||||
|
||||
|
||||
private var progressBar: some View {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
@@ -145,7 +149,7 @@ struct CalibrationOverlayView: View {
|
||||
Text(calibrationManager.progressText)
|
||||
.foregroundStyle(.white.opacity(0.7))
|
||||
}
|
||||
|
||||
|
||||
ProgressView(value: calibrationManager.progress)
|
||||
.progressViewStyle(.linear)
|
||||
.tint(.blue)
|
||||
@@ -153,15 +157,15 @@ struct CalibrationOverlayView: View {
|
||||
.padding()
|
||||
.background(Color.black.opacity(0.7))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Face Detection Indicator
|
||||
|
||||
|
||||
private var faceDetectionIndicator: some View {
|
||||
HStack(spacing: 8) {
|
||||
Circle()
|
||||
.fill(viewModel.stableFaceDetected ? Color.green : Color.red)
|
||||
.frame(width: 12, height: 12)
|
||||
|
||||
|
||||
Text(viewModel.stableFaceDetected ? "Face detected" : "No face detected")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
@@ -173,13 +177,13 @@ struct CalibrationOverlayView: View {
|
||||
.padding()
|
||||
.animation(.easeInOut(duration: 0.3), value: viewModel.stableFaceDetected)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Calibration Target
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
private func calibrationTarget(for step: CalibrationStep, screenSize: CGSize) -> some View {
|
||||
let position = targetPosition(for: step, screenSize: screenSize)
|
||||
|
||||
|
||||
VStack(spacing: 20) {
|
||||
ZStack {
|
||||
// Outer ring (pulsing when counting down)
|
||||
@@ -188,11 +192,11 @@ struct CalibrationOverlayView: View {
|
||||
.frame(width: 100, height: 100)
|
||||
.scaleEffect(viewModel.isCountingDown ? 1.2 : 1.0)
|
||||
.animation(
|
||||
viewModel.isCountingDown
|
||||
viewModel.isCountingDown
|
||||
? .easeInOut(duration: 0.6).repeatForever(autoreverses: true)
|
||||
: .default,
|
||||
value: viewModel.isCountingDown)
|
||||
|
||||
|
||||
// Progress ring when collecting
|
||||
if calibrationManager.isCollectingSamples {
|
||||
Circle()
|
||||
@@ -200,15 +204,17 @@ struct CalibrationOverlayView: View {
|
||||
.stroke(Color.green, lineWidth: 4)
|
||||
.frame(width: 90, height: 90)
|
||||
.rotationEffect(.degrees(-90))
|
||||
.animation(.linear(duration: 0.1), value: calibrationManager.samplesCollected)
|
||||
.animation(
|
||||
.linear(duration: 0.1), value: calibrationManager.samplesCollected)
|
||||
}
|
||||
|
||||
|
||||
// Inner circle
|
||||
Circle()
|
||||
.fill(calibrationManager.isCollectingSamples ? Color.green : Color.blue)
|
||||
.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
|
||||
if viewModel.isCountingDown && viewModel.countdownValue > 0 {
|
||||
Text("\(viewModel.countdownValue)")
|
||||
@@ -220,7 +226,7 @@ struct CalibrationOverlayView: View {
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Text(instructionText(for: step))
|
||||
.font(.title2)
|
||||
.foregroundStyle(.white)
|
||||
@@ -231,7 +237,7 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
.position(position)
|
||||
}
|
||||
|
||||
|
||||
private func instructionText(for step: CalibrationStep) -> String {
|
||||
if viewModel.isCountingDown && viewModel.countdownValue > 0 {
|
||||
return "Get ready..."
|
||||
@@ -241,9 +247,9 @@ struct CalibrationOverlayView: View {
|
||||
return step.instructionText
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Buttons
|
||||
|
||||
|
||||
private var skipButton: some View {
|
||||
Button {
|
||||
viewModel.skipCurrentStep(calibrationManager: calibrationManager)
|
||||
@@ -257,10 +263,11 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
|
||||
private var cancelButton: some View {
|
||||
Button {
|
||||
viewModel.cleanup(eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
viewModel.cleanup(
|
||||
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
onDismiss()
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
@@ -276,24 +283,24 @@ struct CalibrationOverlayView: View {
|
||||
.buttonStyle(.plain)
|
||||
.keyboardShortcut(.escape, modifiers: [])
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Completion View
|
||||
|
||||
|
||||
private var completionView: some View {
|
||||
VStack(spacing: 30) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 80))
|
||||
.foregroundStyle(.green)
|
||||
|
||||
|
||||
Text("Calibration Complete!")
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(.white)
|
||||
.fontWeight(.bold)
|
||||
|
||||
|
||||
Text("Your eye tracking has been calibrated successfully.")
|
||||
.font(.title3)
|
||||
.foregroundStyle(.gray)
|
||||
|
||||
|
||||
Button("Done") {
|
||||
onDismiss()
|
||||
}
|
||||
@@ -307,18 +314,18 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
|
||||
private func targetPosition(for step: CalibrationStep, screenSize: CGSize) -> CGPoint {
|
||||
let width = screenSize.width
|
||||
let height = screenSize.height
|
||||
|
||||
|
||||
let centerX = width / 2
|
||||
let centerY = height / 2
|
||||
let marginX: CGFloat = 150
|
||||
let marginY: CGFloat = 120
|
||||
|
||||
|
||||
switch step {
|
||||
case .center:
|
||||
return CGPoint(x: centerX, y: centerY)
|
||||
@@ -356,23 +363,24 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
@Published var showError: String?
|
||||
@Published var calibrationStarted = false
|
||||
@Published var stableFaceDetected = false // Debounced face detection
|
||||
|
||||
|
||||
private var countdownTask: Task<Void, Never>?
|
||||
private var faceDetectionCancellable: AnyCancellable?
|
||||
private var lastFaceDetectedTime: Date = .distantPast
|
||||
private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce
|
||||
|
||||
func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager) async {
|
||||
|
||||
func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager)
|
||||
async
|
||||
{
|
||||
do {
|
||||
try await eyeTrackingService.startEyeTracking()
|
||||
cameraStarted = true
|
||||
|
||||
|
||||
// Set up debounced face detection
|
||||
setupFaceDetectionObserver(eyeTrackingService: eyeTrackingService)
|
||||
|
||||
// Small delay to let camera stabilize
|
||||
try? await Task.sleep(nanoseconds: 500_000_000)
|
||||
|
||||
|
||||
try? await Task.sleep(for: .seconds(0.5))
|
||||
|
||||
// Reset any previous calibration data before starting fresh
|
||||
calibrationManager.resetForNewCalibration()
|
||||
calibrationManager.startCalibration()
|
||||
@@ -382,13 +390,13 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
showError = "Failed to start camera: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func setupFaceDetectionObserver(eyeTrackingService: EyeTrackingService) {
|
||||
faceDetectionCancellable = eyeTrackingService.$faceDetected
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] detected in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
||||
if detected {
|
||||
// Face detected - update immediately
|
||||
self.lastFaceDetectedTime = Date()
|
||||
@@ -402,39 +410,39 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cleanup(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager) {
|
||||
countdownTask?.cancel()
|
||||
countdownTask = nil
|
||||
faceDetectionCancellable?.cancel()
|
||||
faceDetectionCancellable = nil
|
||||
isCountingDown = false
|
||||
|
||||
|
||||
if calibrationManager.isCalibrating {
|
||||
calibrationManager.cancelCalibration()
|
||||
}
|
||||
|
||||
|
||||
eyeTrackingService.stopEyeTracking()
|
||||
}
|
||||
|
||||
|
||||
func skipCurrentStep(calibrationManager: CalibrationManager) {
|
||||
countdownTask?.cancel()
|
||||
countdownTask = nil
|
||||
isCountingDown = false
|
||||
calibrationManager.skipStep()
|
||||
}
|
||||
|
||||
|
||||
func startStepCountdown(calibrationManager: CalibrationManager) {
|
||||
countdownTask?.cancel()
|
||||
countdownTask = nil
|
||||
countdownValue = 1
|
||||
isCountingDown = true
|
||||
|
||||
|
||||
countdownTask = Task { @MainActor in
|
||||
// Just 1 second countdown
|
||||
try? await Task.sleep(for: .seconds(1))
|
||||
if Task.isCancelled { return }
|
||||
|
||||
|
||||
// Done counting, start collecting
|
||||
isCountingDown = false
|
||||
countdownValue = 0
|
||||
@@ -446,4 +454,3 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
#Preview {
|
||||
CalibrationOverlayView(onDismiss: {})
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ final class OnboardingWindowPresenter {
|
||||
windowController = nil
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NSApp.unhide(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
@@ -83,7 +83,9 @@ final class OnboardingWindowPresenter {
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.center()
|
||||
window.isReleasedWhenClosed = true
|
||||
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary]
|
||||
window.collectionBehavior = [
|
||||
.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary,
|
||||
]
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: OnboardingContainerView(settingsManager: settingsManager)
|
||||
|
||||
Reference in New Issue
Block a user