general: its the final countdown

This commit is contained in:
Michael Freno
2026-01-16 22:58:53 -05:00
parent 44445f2fd5
commit f46ccdc4ca
12 changed files with 798 additions and 339 deletions

View File

@@ -11,21 +11,12 @@ struct EyeTrackingCalibrationView: View {
@StateObject private var calibrationManager = CalibrationManager.shared
@Environment(\.dismiss) private var dismiss
@State private var countdownValue = 3
@State private var isCountingDown = false
var body: some View {
ZStack {
// Full-screen black background
Color.black.ignoresSafeArea()
if calibrationManager.isCalibrating {
calibrationContentView
} else {
introductionScreenView
}
introductionScreenView
}
.frame(minWidth: 800, minHeight: 600)
.frame(minWidth: 600, minHeight: 500)
}
// MARK: - Introduction Screen
@@ -77,7 +68,7 @@ struct EyeTrackingCalibrationView: View {
.keyboardShortcut(.escape, modifiers: [])
Button("Start Calibration") {
startCalibration()
startFullscreenCalibration()
}
.keyboardShortcut(.return, modifiers: [])
.buttonStyle(.borderedProminent)
@@ -88,168 +79,14 @@ struct EyeTrackingCalibrationView: View {
.frame(maxWidth: 600)
}
// MARK: - Calibration Content
// MARK: - Actions
private var calibrationContentView: some View {
ZStack {
// Progress indicator at top
VStack {
progressBar
Spacer()
}
// Calibration target
if let step = calibrationManager.currentStep {
calibrationTarget(for: step)
}
// Skip button at bottom
VStack {
Spacer()
skipButton
}
}
}
// MARK: - Progress Bar
private var progressBar: some View {
VStack(spacing: 10) {
HStack {
Text("Calibrating...")
.foregroundStyle(.white)
Spacer()
Text(calibrationManager.progressText)
.foregroundStyle(.white.opacity(0.7))
}
ProgressView(value: calibrationManager.progress)
.progressViewStyle(.linear)
.tint(.blue)
}
.padding()
.background(Color.black.opacity(0.5))
}
// MARK: - Calibration Target
@ViewBuilder
private func calibrationTarget(for step: CalibrationStep) -> some View {
let position = targetPosition(for: step)
VStack(spacing: 20) {
// Target circle with countdown
ZStack {
// Outer ring (pulsing)
Circle()
.stroke(Color.blue.opacity(0.3), lineWidth: 3)
.frame(width: 100, height: 100)
.scaleEffect(isCountingDown ? 1.2 : 1.0)
.animation(
.easeInOut(duration: 0.6).repeatForever(autoreverses: true),
value: isCountingDown)
// Inner circle
Circle()
.fill(Color.blue)
.frame(width: 60, height: 60)
// Countdown number or checkmark
if isCountingDown && countdownValue > 0 {
Text("\(countdownValue)")
.font(.system(size: 36, weight: .bold))
.foregroundStyle(.white)
} else if calibrationManager.samplesCollected > 0 {
Image(systemName: "checkmark")
.font(.system(size: 30, weight: .bold))
.foregroundStyle(.white)
}
}
// Instruction text
Text(step.instructionText)
.font(.title2)
.foregroundStyle(.white)
.padding(.horizontal, 40)
.padding(.vertical, 15)
.background(Color.black.opacity(0.7))
.cornerRadius(10)
}
.position(position)
.onAppear {
startStepCountdown()
}
}
// MARK: - Skip Button
private var skipButton: some View {
Button {
calibrationManager.skipStep()
} label: {
Text("Skip this position")
.foregroundStyle(.white)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(Color.white.opacity(0.2))
.cornerRadius(8)
}
.padding(.bottom, 40)
}
// MARK: - Helper Methods
private func startCalibration() {
calibrationManager.startCalibration()
}
private func startStepCountdown() {
countdownValue = 3
isCountingDown = true
// Countdown 3, 2, 1
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if countdownValue > 0 {
countdownValue -= 1
} else {
timer.invalidate()
isCountingDown = false
}
}
}
private func targetPosition(for step: CalibrationStep) -> CGPoint {
let screenBounds = NSScreen.main?.frame ?? CGRect(x: 0, y: 0, width: 1920, height: 1080)
let width = screenBounds.width
let height = screenBounds.height
let centerX = width / 2
let centerY = height / 2
let margin: CGFloat = 150
switch step {
case .center:
return CGPoint(x: centerX, y: centerY)
case .left:
return CGPoint(x: centerX - width / 4, y: centerY)
case .right:
return CGPoint(x: centerX + width / 4, y: centerY)
case .farLeft:
return CGPoint(x: margin, y: centerY)
case .farRight:
return CGPoint(x: width - margin, y: centerY)
case .up:
return CGPoint(x: centerX, y: margin)
case .down:
return CGPoint(x: centerX, y: height - margin)
case .topLeft:
return CGPoint(x: margin, y: margin)
case .topRight:
return CGPoint(x: width - margin, y: margin)
case .bottomLeft:
return CGPoint(x: margin, y: height - margin)
case .bottomRight:
return CGPoint(x: width - margin, y: height - margin)
private func startFullscreenCalibration() {
dismiss()
// Small delay to allow sheet dismissal animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
CalibrationWindowManager.shared.showCalibrationOverlay()
}
}
}