fix: preview fixed

This commit is contained in:
Michael Freno
2026-01-14 01:01:03 -05:00
parent 780da2951b
commit 23000589cf
4 changed files with 116 additions and 54 deletions

View File

@@ -27,6 +27,7 @@ class EnforceModeService: ObservableObject {
self.settingsManager = SettingsManager.shared self.settingsManager = SettingsManager.shared
self.eyeTrackingService = EyeTrackingService.shared self.eyeTrackingService = EyeTrackingService.shared
setupObservers() setupObservers()
initializeEnforceModeState()
} }
private func setupObservers() { private func setupObservers() {
@@ -37,6 +38,20 @@ class EnforceModeService: ObservableObject {
.store(in: &cancellables) .store(in: &cancellables)
} }
private func initializeEnforceModeState() {
let cameraService = CameraAccessService.shared
let settingsEnabled = settingsManager.settings.enforcementMode
// If settings say it's enabled AND camera is authorized, mark as enabled
if settingsEnabled && cameraService.isCameraAuthorized {
isEnforceModeEnabled = true
print("✓ Enforce mode initialized as enabled (camera authorized)")
} else {
isEnforceModeEnabled = false
print("🔒 Enforce mode initialized as disabled")
}
}
func enableEnforceMode() async { func enableEnforceMode() async {
print("🔒 enableEnforceMode called") print("🔒 enableEnforceMode called")
guard !isEnforceModeEnabled else { guard !isEnforceModeEnabled else {

View File

@@ -21,11 +21,23 @@ class EyeTrackingService: NSObject, ObservableObject {
private var captureSession: AVCaptureSession? private var captureSession: AVCaptureSession?
private var videoOutput: AVCaptureVideoDataOutput? private var videoOutput: AVCaptureVideoDataOutput?
private let videoDataOutputQueue = DispatchQueue(label: "com.gaze.videoDataOutput", qos: .userInitiated) private let videoDataOutputQueue = DispatchQueue(label: "com.gaze.videoDataOutput", qos: .userInitiated)
private var _previewLayer: AVCaptureVideoPreviewLayer?
var previewLayer: AVCaptureVideoPreviewLayer? { var previewLayer: AVCaptureVideoPreviewLayer? {
guard let session = captureSession else { return nil } guard let session = captureSession else {
_previewLayer = nil
return nil
}
// Reuse existing layer if session hasn't changed
if let existing = _previewLayer, existing.session === session {
return existing
}
// Create new layer only when session changes
let layer = AVCaptureVideoPreviewLayer(session: session) let layer = AVCaptureVideoPreviewLayer(session: session)
layer.videoGravity = .resizeAspectFill layer.videoGravity = .resizeAspectFill
_previewLayer = layer
return layer return layer
} }
@@ -66,6 +78,7 @@ class EyeTrackingService: NSObject, ObservableObject {
captureSession?.stopRunning() captureSession?.stopRunning()
captureSession = nil captureSession = nil
videoOutput = nil videoOutput = nil
_previewLayer = nil
isEyeTrackingActive = false isEyeTrackingActive = false
isEyesClosed = false isEyesClosed = false
userLookingAtScreen = true userLookingAtScreen = true

View File

@@ -12,20 +12,36 @@ struct CameraPreviewView: NSViewRepresentable {
let previewLayer: AVCaptureVideoPreviewLayer let previewLayer: AVCaptureVideoPreviewLayer
let borderColor: NSColor let borderColor: NSColor
func makeNSView(context: Context) -> NSView { func makeNSView(context: Context) -> PreviewContainerView {
let view = PreviewContainerView() let view = PreviewContainerView()
view.wantsLayer = true view.wantsLayer = true
previewLayer.frame = view.bounds // Add the preview layer once
view.layer?.addSublayer(previewLayer) if view.layer?.sublayers?.first as? AVCaptureVideoPreviewLayer !== previewLayer {
view.layer?.sublayers?.forEach { $0.removeFromSuperlayer() }
previewLayer.frame = view.bounds
view.layer?.addSublayer(previewLayer)
}
updateBorder(view: view, color: borderColor) updateBorder(view: view, color: borderColor)
return view return view
} }
func updateNSView(_ nsView: NSView, context: Context) { func updateNSView(_ nsView: PreviewContainerView, context: Context) {
previewLayer.frame = nsView.bounds // Only update border color and frame, don't recreate layer
let currentLayer = nsView.layer?.sublayers?.first as? AVCaptureVideoPreviewLayer
if currentLayer !== previewLayer {
// Layer changed, need to replace
nsView.layer?.sublayers?.forEach { $0.removeFromSuperlayer() }
previewLayer.frame = nsView.bounds
nsView.layer?.addSublayer(previewLayer)
} else {
// Same layer, just update frame
previewLayer.frame = nsView.bounds
}
updateBorder(view: nsView, color: borderColor) updateBorder(view: nsView, color: borderColor)
} }

View File

@@ -5,6 +5,7 @@
// Created by Mike Freno on 1/13/26. // Created by Mike Freno on 1/13/26.
// //
import AVFoundation
import SwiftUI import SwiftUI
struct EnforceModeSetupView: View { struct EnforceModeSetupView: View {
@@ -15,6 +16,7 @@ struct EnforceModeSetupView: View {
@State private var isProcessingToggle = false @State private var isProcessingToggle = false
@State private var isTestModeActive = false @State private var isTestModeActive = false
@State private var cachedPreviewLayer: AVCaptureVideoPreviewLayer?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -102,9 +104,13 @@ struct EnforceModeSetupView: View {
if isTestModeActive { if isTestModeActive {
enforceModeService.stopTestMode() enforceModeService.stopTestMode()
isTestModeActive = false isTestModeActive = false
cachedPreviewLayer = nil
} else { } else {
await enforceModeService.startTestMode() await enforceModeService.startTestMode()
isTestModeActive = enforceModeService.isCameraActive isTestModeActive = enforceModeService.isCameraActive
if isTestModeActive {
cachedPreviewLayer = eyeTrackingService.previewLayer
}
} }
} }
}) { }) {
@@ -123,13 +129,21 @@ struct EnforceModeSetupView: View {
private var testModePreviewView: some View { private var testModePreviewView: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
if let previewLayer = eyeTrackingService.previewLayer { let lookingAway = !eyeTrackingService.userLookingAtScreen
let lookingAway = !eyeTrackingService.userLookingAtScreen let borderColor: NSColor = lookingAway ? .systemGreen : .systemRed
let borderColor: NSColor = lookingAway ? .systemGreen : .systemRed
CameraPreviewView(previewLayer: previewLayer, borderColor: borderColor) // Cache the preview layer to avoid recreating it
let previewLayer = eyeTrackingService.previewLayer ?? cachedPreviewLayer
if let layer = previewLayer {
CameraPreviewView(previewLayer: layer, borderColor: borderColor)
.frame(height: 300) .frame(height: 300)
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12)) .glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
.onAppear {
if cachedPreviewLayer == nil {
cachedPreviewLayer = eyeTrackingService.previewLayer
}
}
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
Text("Live Tracking Status") Text("Live Tracking Status")
@@ -149,11 +163,14 @@ struct EnforceModeSetupView: View {
) )
} }
Text(lookingAway ? "✓ Break compliance detected" : "⚠️ Please look away from screen") Text(
.font(.caption) lookingAway
.foregroundColor(lookingAway ? .green : .orange) ? "✓ Break compliance detected" : "⚠️ Please look away from screen"
.frame(maxWidth: .infinity, alignment: .center) )
.padding(.top, 4) .font(.caption)
.foregroundColor(lookingAway ? .green : .orange)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 4)
} }
.padding() .padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12)) .glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
@@ -281,7 +298,8 @@ struct EnforceModeSetupView: View {
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.padding() .padding()
.glassEffectIfAvailable(GlassStyle.regular.tint(.blue.opacity(0.1)), in: .rect(cornerRadius: 12)) .glassEffectIfAvailable(
GlassStyle.regular.tint(.blue.opacity(0.1)), in: .rect(cornerRadius: 12))
} }
private func privacyBullet(_ text: String) -> some View { private func privacyBullet(_ text: String) -> some View {