general: foregroundColor -> foregroundStyle (will change in future)

This commit is contained in:
Michael Freno
2026-01-16 18:16:04 -05:00
parent c825ce16e2
commit 44445f2fd5
20 changed files with 449 additions and 367 deletions

View File

@@ -10,15 +10,15 @@ import SwiftUI
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 {
@@ -27,51 +27,55 @@ struct EyeTrackingCalibrationView: View {
}
.frame(minWidth: 800, minHeight: 600)
}
// MARK: - Introduction Screen
private var introductionScreenView: some View {
VStack(spacing: 30) {
Image(systemName: "eye.circle.fill")
.font(.system(size: 80))
.foregroundColor(.blue)
.foregroundStyle(.blue)
Text("Eye Tracking Calibration")
.font(.largeTitle)
.foregroundStyle(.white)
.fontWeight(.bold)
Text("This calibration will help improve eye tracking accuracy.")
.font(.title3)
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 15) {
InstructionRow(icon: "1.circle.fill", text: "Look at each target on the screen")
InstructionRow(icon: "2.circle.fill", text: "Keep your head still, only move your eyes")
InstructionRow(
icon: "2.circle.fill", text: "Keep your head still, only move your eyes")
InstructionRow(icon: "3.circle.fill", text: "Follow the countdown at each position")
InstructionRow(icon: "4.circle.fill", text: "Takes about 30-45 seconds")
}
.padding(.vertical, 20)
if calibrationManager.calibrationData.isComplete {
VStack(spacing: 10) {
Text("Last calibration:")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.gray)
Text(calibrationManager.getCalibrationSummary())
.font(.caption)
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.foregroundStyle(.gray)
}
.padding(.vertical)
}
HStack(spacing: 20) {
Button("Cancel") {
dismiss()
}
.foregroundStyle(.white)
.buttonStyle(.plain)
.keyboardShortcut(.escape, modifiers: [])
Button("Start Calibration") {
startCalibration()
}
@@ -83,9 +87,9 @@ struct EyeTrackingCalibrationView: View {
.padding(60)
.frame(maxWidth: 600)
}
// MARK: - Calibration Content
private var calibrationContentView: some View {
ZStack {
// Progress indicator at top
@@ -93,12 +97,12 @@ struct EyeTrackingCalibrationView: View {
progressBar
Spacer()
}
// Calibration target
if let step = calibrationManager.currentStep {
calibrationTarget(for: step)
}
// Skip button at bottom
VStack {
Spacer()
@@ -106,19 +110,19 @@ struct EyeTrackingCalibrationView: View {
}
}
}
// MARK: - Progress Bar
private var progressBar: some View {
VStack(spacing: 10) {
HStack {
Text("Calibrating...")
.foregroundColor(.white)
.foregroundStyle(.white)
Spacer()
Text(calibrationManager.progressText)
.foregroundColor(.white.opacity(0.7))
.foregroundStyle(.white.opacity(0.7))
}
ProgressView(value: calibrationManager.progress)
.progressViewStyle(.linear)
.tint(.blue)
@@ -126,13 +130,13 @@ struct EyeTrackingCalibrationView: View {
.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 {
@@ -141,29 +145,31 @@ struct EyeTrackingCalibrationView: View {
.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)
.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))
.foregroundColor(.white)
.foregroundStyle(.white)
} else if calibrationManager.samplesCollected > 0 {
Image(systemName: "checkmark")
.font(.system(size: 30, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
}
}
// Instruction text
Text(step.instructionText)
.font(.title2)
.foregroundColor(.white)
.foregroundStyle(.white)
.padding(.horizontal, 40)
.padding(.vertical, 15)
.background(Color.black.opacity(0.7))
@@ -174,15 +180,15 @@ struct EyeTrackingCalibrationView: View {
startStepCountdown()
}
}
// MARK: - Skip Button
private var skipButton: some View {
Button {
calibrationManager.skipStep()
} label: {
Text("Skip this position")
.foregroundColor(.white)
.foregroundStyle(.white)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(Color.white.opacity(0.2))
@@ -190,17 +196,17 @@ struct EyeTrackingCalibrationView: View {
}
.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 {
@@ -211,16 +217,16 @@ struct EyeTrackingCalibrationView: View {
}
}
}
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)
@@ -253,15 +259,16 @@ struct EyeTrackingCalibrationView: View {
struct InstructionRow: View {
let icon: String
let text: String
var body: some View {
HStack(spacing: 15) {
Image(systemName: icon)
.font(.title2)
.foregroundColor(.blue)
.foregroundStyle(.blue)
.frame(width: 30)
Text(text)
.foregroundStyle(.white)
.font(.body)
}
}

View File

@@ -9,7 +9,7 @@ import SwiftUI
struct GazeOverlayView: View {
@ObservedObject var eyeTrackingService: EyeTrackingService
var body: some View {
VStack(spacing: 8) {
inFrameIndicator
@@ -19,7 +19,7 @@ struct GazeOverlayView: View {
}
.padding(12)
}
private var inFrameIndicator: some View {
HStack(spacing: 6) {
Circle()
@@ -28,7 +28,7 @@ struct GazeOverlayView: View {
Text(eyeTrackingService.isInFrame ? "In Frame" : "No Face")
.font(.caption2)
.fontWeight(.semibold)
.foregroundColor(.white)
.foregroundStyle(.white)
}
.padding(.horizontal, 10)
.padding(.vertical, 6)
@@ -37,16 +37,18 @@ struct GazeOverlayView: View {
.fill(Color.black.opacity(0.6))
)
}
private var gazeDirectionGrid: some View {
let currentDirection = eyeTrackingService.gazeDirection
let currentPos = currentDirection.gridPosition
return VStack(spacing: 2) {
ForEach(0..<3, id: \.self) { row in
HStack(spacing: 2) {
ForEach(0..<3, id: \.self) { col in
let isActive = currentPos.x == col && currentPos.y == row && eyeTrackingService.isInFrame
let isActive =
currentPos.x == col && currentPos.y == row
&& eyeTrackingService.isInFrame
gridCell(row: row, col: col, isActive: isActive)
}
}
@@ -58,21 +60,21 @@ struct GazeOverlayView: View {
.fill(Color.black.opacity(0.5))
)
}
private func gridCell(row: Int, col: Int, isActive: Bool) -> some View {
let direction = directionFor(row: row, col: col)
return ZStack {
RoundedRectangle(cornerRadius: 4)
.fill(isActive ? Color.green : Color.white.opacity(0.2))
Text(direction.rawValue)
.font(.system(size: 14, weight: .bold))
.foregroundColor(isActive ? .white : .white.opacity(0.6))
.foregroundStyle(isActive ? .white : .white.opacity(0.6))
}
.frame(width: 28, height: 28)
}
private func directionFor(row: Int, col: Int) -> GazeDirection {
switch (col, row) {
case (0, 0): return .upLeft
@@ -87,7 +89,7 @@ struct GazeOverlayView: View {
default: return .center
}
}
private var ratioDebugView: some View {
VStack(alignment: .leading, spacing: 2) {
// Show individual L/R ratios
@@ -95,38 +97,39 @@ struct GazeOverlayView: View {
if let leftH = eyeTrackingService.debugLeftPupilRatio {
Text("L.H: \(String(format: "%.2f", leftH))")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundColor(.white)
.foregroundStyle(.white)
}
if let rightH = eyeTrackingService.debugRightPupilRatio {
Text("R.H: \(String(format: "%.2f", rightH))")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundColor(.white)
.foregroundStyle(.white)
}
}
HStack(spacing: 8) {
if let leftV = eyeTrackingService.debugLeftVerticalRatio {
Text("L.V: \(String(format: "%.2f", leftV))")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundColor(.white)
.foregroundStyle(.white)
}
if let rightV = eyeTrackingService.debugRightVerticalRatio {
Text("R.V: \(String(format: "%.2f", rightV))")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundColor(.white)
.foregroundStyle(.white)
}
}
// Show averaged ratios
if let leftH = eyeTrackingService.debugLeftPupilRatio,
let rightH = eyeTrackingService.debugRightPupilRatio,
let leftV = eyeTrackingService.debugLeftVerticalRatio,
let rightV = eyeTrackingService.debugRightVerticalRatio {
let rightH = eyeTrackingService.debugRightPupilRatio,
let leftV = eyeTrackingService.debugLeftVerticalRatio,
let rightV = eyeTrackingService.debugRightVerticalRatio
{
let avgH = (leftH + rightH) / 2.0
let avgV = (leftV + rightV) / 2.0
Text("Avg H:\(String(format: "%.2f", avgH)) V:\(String(format: "%.2f", avgV))")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundColor(.yellow)
.foregroundStyle(.yellow)
}
}
.padding(.horizontal, 8)
@@ -136,15 +139,15 @@ struct GazeOverlayView: View {
.fill(Color.black.opacity(0.5))
)
}
private var eyeImagesDebugView: some View {
HStack(spacing: 12) {
// Left eye
VStack(spacing: 4) {
Text("Left")
.font(.system(size: 8, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
HStack(spacing: 4) {
eyeImageView(
image: eyeTrackingService.debugLeftEyeInput,
@@ -160,13 +163,13 @@ struct GazeOverlayView: View {
)
}
}
// Right eye
VStack(spacing: 4) {
Text("Right")
.font(.system(size: 8, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
HStack(spacing: 4) {
eyeImageView(
image: eyeTrackingService.debugRightEyeInput,
@@ -189,10 +192,12 @@ struct GazeOverlayView: View {
.fill(Color.black.opacity(0.5))
)
}
private func eyeImageView(image: NSImage?, pupilPosition: PupilPosition?, eyeSize: CGSize?, label: String) -> some View {
private func eyeImageView(
image: NSImage?, pupilPosition: PupilPosition?, eyeSize: CGSize?, label: String
) -> some View {
let displaySize: CGFloat = 50
return VStack(spacing: 2) {
ZStack {
if let nsImage = image {
@@ -201,15 +206,17 @@ struct GazeOverlayView: View {
.interpolation(.none)
.aspectRatio(contentMode: .fit)
.frame(width: displaySize, height: displaySize)
// Draw pupil position marker
if let pupil = pupilPosition, let size = eyeSize, size.width > 0, size.height > 0 {
if let pupil = pupilPosition, let size = eyeSize, size.width > 0,
size.height > 0
{
let scaleX = displaySize / size.width
let scaleY = displaySize / size.height
let scale = min(scaleX, scaleY)
let scaledWidth = size.width * scale
let scaledHeight = size.height * scale
Circle()
.fill(Color.red)
.frame(width: 4, height: 4)
@@ -224,15 +231,15 @@ struct GazeOverlayView: View {
.frame(width: displaySize, height: displaySize)
Text("--")
.font(.system(size: 10))
.foregroundColor(.white.opacity(0.5))
.foregroundStyle(.white.opacity(0.5))
}
}
.frame(width: displaySize, height: displaySize)
.clipShape(RoundedRectangle(cornerRadius: 4))
Text(label)
.font(.system(size: 7))
.foregroundColor(.white.opacity(0.7))
.foregroundStyle(.white.opacity(0.7))
}
}
}

View File

@@ -10,27 +10,27 @@ import SwiftUI
struct InfoBox: View {
let text: String
let url: String?
var body: some View {
HStack(spacing: 12) {
if let url = url, let urlObj = URL(string: url) {
Button(action: {
#if os(iOS)
UIApplication.shared.open(urlObj)
UIApplication.shared.open(urlObj)
#elseif os(macOS)
NSWorkspace.shared.open(urlObj)
NSWorkspace.shared.open(urlObj)
#endif
}) {
Image(systemName: "info.circle")
.foregroundColor(.white)
.foregroundStyle(.white)
}.buttonStyle(.plain)
} else {
Image(systemName: "info.circle")
.foregroundColor(.white)
.foregroundStyle(.white)
}
Text(text)
.font(.headline)
.foregroundColor(.white)
.foregroundStyle(.white)
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
@@ -38,6 +38,10 @@ struct InfoBox: View {
}
#Preview {
InfoBox(text: "This is an informational message that provides helpful context to the user.", url: "https://www.healthline.com/health/eye-health/20-20-20-rule")
.padding()
}
InfoBox(
text: "This is an informational message that provides helpful context to the user.",
url: "https://www.healthline.com/health/eye-health/20-20-20-rule"
)
.padding()
}

View File

@@ -10,17 +10,18 @@ import SwiftUI
/// Draws pupil detection markers directly on top of the camera preview
struct PupilOverlayView: View {
@ObservedObject var eyeTrackingService: EyeTrackingService
var body: some View {
GeometryReader { geometry in
let viewSize = geometry.size
// Draw eye regions and pupil markers
ZStack {
// Left eye
if let leftRegion = eyeTrackingService.debugLeftEyeRegion,
let leftPupil = eyeTrackingService.debugLeftPupilPosition,
let imageSize = eyeTrackingService.debugImageSize {
let leftPupil = eyeTrackingService.debugLeftPupilPosition,
let imageSize = eyeTrackingService.debugImageSize
{
EyeOverlayShape(
eyeRegion: leftRegion,
pupilPosition: leftPupil,
@@ -30,11 +31,12 @@ struct PupilOverlayView: View {
label: "L"
)
}
// Right eye
if let rightRegion = eyeTrackingService.debugRightEyeRegion,
let rightPupil = eyeTrackingService.debugRightPupilPosition,
let imageSize = eyeTrackingService.debugImageSize {
let rightPupil = eyeTrackingService.debugRightPupilPosition,
let imageSize = eyeTrackingService.debugImageSize
{
EyeOverlayShape(
eyeRegion: rightRegion,
pupilPosition: rightPupil,
@@ -57,7 +59,7 @@ private struct EyeOverlayShape: View {
let viewSize: CGSize
let color: Color
let label: String
private var transformedCoordinates: (eyeRect: CGRect, pupilPoint: CGPoint) {
// Standard macOS Camera Coordinate System (Landscape):
// Raw Buffer:
@@ -71,20 +73,20 @@ private struct EyeOverlayShape: View {
// - Screen Y increases Down
// - BUT the image content is flipped horizontally
// (Raw Left is Screen Right, Raw Right is Screen Left)
// Use dimensions directly (no rotation swap)
let rawImageWidth = imageSize.width
let rawImageHeight = imageSize.height
// Calculate aspect-fill scaling
// We compare the raw aspect ratio to the view aspect ratio
let imageAspect = rawImageWidth / rawImageHeight
let viewAspect = viewSize.width / viewSize.height
let scale: CGFloat
let offsetX: CGFloat
let offsetY: CGFloat
if imageAspect > viewAspect {
// Image is wider than view - crop sides (pillarbox behavior in aspect fill)
// Wait, aspect fill means we fill the view, so we crop the excess.
@@ -98,7 +100,7 @@ private struct EyeOverlayShape: View {
offsetX = 0
offsetY = (viewSize.height - rawImageHeight * scale) / 2
}
// Transform Eye Region
// Mirroring X: The 'left' of the raw image becomes the 'right' of the screen
// Raw Rect: x, y, w, h
@@ -107,47 +109,47 @@ private struct EyeOverlayShape: View {
let eyeRawY = eyeRegion.frame.origin.y
let eyeRawW = eyeRegion.frame.width
let eyeRawH = eyeRegion.frame.height
// Calculate Screen Coordinates
let eyeScreenX = (rawImageWidth - (eyeRawX + eyeRawW)) * scale + offsetX
let eyeScreenY = eyeRawY * scale + offsetY
let eyeScreenW = eyeRawW * scale
let eyeScreenH = eyeRawH * scale
// Transform Pupil Position
// Global Raw Pupil X = eyeRawX + pupilPosition.x
// Global Raw Pupil Y = eyeRawY + pupilPosition.y
let pupilGlobalRawX = eyeRawX + pupilPosition.x
let pupilGlobalRawY = eyeRawY + pupilPosition.y
// Mirror X for Pupil
let pupilScreenX = (rawImageWidth - pupilGlobalRawX) * scale + offsetX
let pupilScreenY = pupilGlobalRawY * scale + offsetY
return (
eyeRect: CGRect(x: eyeScreenX, y: eyeScreenY, width: eyeScreenW, height: eyeScreenH),
pupilPoint: CGPoint(x: pupilScreenX, y: pupilScreenY)
)
}
var body: some View {
let coords = transformedCoordinates
let eyeRect = coords.eyeRect
let pupilPoint = coords.pupilPoint
ZStack {
// Eye region rectangle
Rectangle()
.stroke(color, lineWidth: 2)
.frame(width: eyeRect.width, height: eyeRect.height)
.position(x: eyeRect.midX, y: eyeRect.midY)
// Pupil marker (red dot)
Circle()
.fill(Color.red)
.frame(width: 8, height: 8)
.position(x: pupilPoint.x, y: pupilPoint.y)
// Crosshair at pupil position
Path { path in
path.move(to: CGPoint(x: pupilPoint.x - 6, y: pupilPoint.y))
@@ -156,18 +158,18 @@ private struct EyeOverlayShape: View {
path.addLine(to: CGPoint(x: pupilPoint.x, y: pupilPoint.y + 6))
}
.stroke(Color.red, lineWidth: 1)
// Label
Text(label)
.font(.system(size: 10, weight: .bold))
.foregroundColor(color)
.foregroundStyle(color)
.position(x: eyeRect.minX + 8, y: eyeRect.minY - 8)
// Debug: Show raw coordinates
Text("\(label): (\(Int(pupilPosition.x)), \(Int(pupilPosition.y)))")
.font(.system(size: 8, design: .monospaced))
.foregroundColor(.white)
.background(Color.black.opacity(0.7))
.foregroundStyle(.white)
.background(.black.opacity(0.7))
.position(x: eyeRect.midX, y: eyeRect.maxY + 10)
}
}

View File

@@ -16,7 +16,7 @@ struct SetupHeader: View {
VStack(spacing: 16) {
Image(systemName: icon)
.font(.system(size: 60))
.foregroundColor(color)
.foregroundStyle(color)
Text(title)
.font(.system(size: 28, weight: .bold))
}

View File

@@ -42,7 +42,7 @@ struct SliderSection: View {
VStack(alignment: .leading, spacing: 12) {
Text("Remind me every:")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack {
Slider(
value: Binding(
@@ -62,7 +62,7 @@ struct SliderSection: View {
if let range = countdownSettings.range {
Text("Look away for:")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack {
Slider(
value: Binding(
@@ -89,14 +89,14 @@ struct SliderSection: View {
reminderText
)
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
} else {
Text(
"\(type) reminders are currently disabled."
)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Button(action: {
@@ -104,10 +104,10 @@ struct SliderSection: View {
}) {
HStack(spacing: 8) {
Image(systemName: "eye")
.foregroundColor(.white)
.foregroundStyle(.white)
Text("Preview Reminder")
.font(.headline)
.foregroundColor(.white)
.foregroundStyle(.white)
}
.padding(.horizontal, 16)
.padding(.vertical, 10)

View File

@@ -90,7 +90,8 @@ final class OnboardingWindowPresenter {
self?.windowController = nil
self?.removeCloseObserver()
}
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
NotificationCenter.default.post(
name: Notification.Name("OnboardingWindowDidClose"), object: nil)
}
}
@@ -142,9 +143,9 @@ struct OnboardingContainerView: View {
}
}
#if APPSTORE
.frame(minWidth: 1000, minHeight: 700)
.frame(minWidth: 1000, minHeight: 700)
#else
.frame(minWidth: 1000, minHeight: 900)
.frame(minWidth: 1000, minHeight: 900)
#endif
}
@@ -159,11 +160,12 @@ struct OnboardingContainerView: View {
}
.font(.headline)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
.foregroundColor(.primary)
.foregroundStyle(.primary)
.contentShape(RoundedRectangle(cornerRadius: 10))
}
.buttonStyle(.plain)
.glassEffectIfAvailable(GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
.glassEffectIfAvailable(
GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
}
Button(action: {
@@ -173,11 +175,14 @@ struct OnboardingContainerView: View {
currentPage += 1
}
}) {
Text(currentPage == 0 ? "Let's Get Started" : currentPage == 5 ? "Get Started" : "Continue")
.font(.headline)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
.foregroundColor(.white)
.contentShape(RoundedRectangle(cornerRadius: 10))
Text(
currentPage == 0
? "Let's Get Started" : currentPage == 5 ? "Get Started" : "Continue"
)
.font(.headline)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
.foregroundStyle(.white)
.contentShape(RoundedRectangle(cornerRadius: 10))
}
.buttonStyle(.plain)
.glassEffectIfAvailable(

View File

@@ -50,7 +50,7 @@ struct MenuBarHoverButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(isHovered ? .white : .primary)
.foregroundStyle(isHovered ? .white : .primary)
.glassEffectIfAvailable(
isHovered
? GlassStyle.regular.tint(.accentColor).interactive()
@@ -83,7 +83,7 @@ struct MenuBarContentView: View {
VStack(alignment: .leading, spacing: 12) {
Text("Active Timers")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.padding(.horizontal)
.padding(.top, 8)
@@ -176,7 +176,7 @@ struct MenuBarContentView: View {
}) {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
Text("Complete Onboarding")
Spacer()
}
@@ -193,7 +193,7 @@ struct MenuBarContentView: View {
Button(action: onQuit) {
HStack {
Image(systemName: "power")
.foregroundColor(.red)
.foregroundStyle(.red)
Text("Quit Gaze")
Spacer()
}
@@ -207,7 +207,7 @@ struct MenuBarContentView: View {
"v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0")"
)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
@@ -335,20 +335,20 @@ struct TimerStatusRowWithIndividualControls: View {
}
Image(systemName: iconName)
.foregroundColor(isHoveredBody ? .white : color)
.foregroundStyle(isHoveredBody ? .white : color)
.frame(width: 20)
VStack(alignment: .leading, spacing: 2) {
Text(displayName)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(isHoveredBody ? .white : .primary)
.foregroundStyle(isHoveredBody ? .white : .primary)
.lineLimit(1)
if let state = state {
Text(state.remainingSeconds.asTimerDuration)
.font(.caption)
.foregroundColor(isHoveredBody ? .white.opacity(0.8) : .secondary)
.foregroundStyle(isHoveredBody ? .white.opacity(0.8) : .secondary)
.monospacedDigit()
}
}
@@ -365,7 +365,7 @@ struct TimerStatusRowWithIndividualControls: View {
Button(action: onDevTrigger) {
Image(systemName: "bolt.fill")
.font(.caption)
.foregroundColor(isHoveredDevTrigger ? .white : .yellow)
.foregroundStyle(isHoveredDevTrigger ? .white : .yellow)
.padding(6)
.contentShape(Circle())
}
@@ -394,7 +394,7 @@ struct TimerStatusRowWithIndividualControls: View {
systemName: isPaused ? "play.circle" : "pause.circle"
)
.font(.caption)
.foregroundColor(isHoveredPauseButton ? .white : .accentColor)
.foregroundStyle(isHoveredPauseButton ? .white : .accentColor)
.padding(6)
.contentShape(Circle())
}
@@ -416,7 +416,7 @@ struct TimerStatusRowWithIndividualControls: View {
Button(action: onSkip) {
Image(systemName: "forward.fill")
.font(.caption)
.foregroundColor(isHoveredSkip ? .white : .accentColor)
.foregroundStyle(isHoveredSkip ? .white : .accentColor)
.padding(6)
.contentShape(Circle())
}

View File

@@ -33,11 +33,11 @@ struct LookAwayReminderView: View {
VStack(spacing: 40) {
Text("Look Away")
.font(.system(size: 64, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
Text("Look at something 20 feet away")
.font(.system(size: 28))
.foregroundColor(.white.opacity(0.9))
.foregroundStyle(.white.opacity(0.9))
GazeLottieView(
animationName: AnimationAsset.lookAway.fileName,
@@ -62,14 +62,14 @@ struct LookAwayReminderView: View {
Text("\(remainingSeconds)")
.font(.system(size: 48, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
.monospacedDigit()
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.countdownLabel)
}
Text("Press ESC or Space to skip")
.font(.subheadline)
.foregroundColor(.white.opacity(0.6))
.foregroundStyle(.white.opacity(0.6))
}
// Skip button in corner
@@ -79,7 +79,7 @@ struct LookAwayReminderView: View {
Button(action: dismiss) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 32))
.foregroundColor(.white.opacity(0.7))
.foregroundStyle(.white.opacity(0.7))
}
.buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.dismissButton)

View File

@@ -22,7 +22,7 @@ struct PostureReminderView: View {
VStack {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: scale))
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
}
.opacity(opacity)
.offset(y: yOffset)

View File

@@ -32,19 +32,19 @@ struct UserTimerOverlayReminderView: View {
VStack(spacing: 40) {
Text(timer.title)
.font(.system(size: 64, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
if let message = timer.message, !message.isEmpty {
Text(message)
.font(.system(size: 28))
.foregroundColor(.white.opacity(0.9))
.foregroundStyle(.white.opacity(0.9))
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
}
Image(systemName: "clock.fill")
.font(.system(size: 120))
.foregroundColor(timer.color)
.foregroundStyle(timer.color)
.padding(.vertical, 30)
// Countdown display
@@ -62,13 +62,13 @@ struct UserTimerOverlayReminderView: View {
Text("\(remainingSeconds)")
.font(.system(size: 48, weight: .bold))
.foregroundColor(.white)
.foregroundStyle(.white)
.monospacedDigit()
}
Text("Press ESC or Space to dismiss")
.font(.subheadline)
.foregroundColor(.white.opacity(0.6))
.foregroundStyle(.white.opacity(0.6))
}
// Dismiss button in corner
@@ -78,7 +78,7 @@ struct UserTimerOverlayReminderView: View {
Button(action: dismiss) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 32))
.foregroundColor(.white.opacity(0.7))
.foregroundStyle(.white.opacity(0.7))
}
.buttonStyle(.plain)
.padding(30)

View File

@@ -27,12 +27,12 @@ struct UserTimerReminderView: View {
VStack(spacing: 12) {
Image(systemName: "clock.fill")
.font(.system(size: baseSize * 0.4))
.foregroundColor(timer.color)
.foregroundStyle(timer.color)
if let message = timer.message, !message.isEmpty {
Text(message)
.font(.system(size: baseSize * 0.24))
.foregroundColor(.primary)
.foregroundStyle(.primary)
.multilineTextAlignment(.center)
.lineLimit(2)
}

View File

@@ -21,45 +21,62 @@ struct BlinkSetupView: View {
VStack(spacing: 30) {
HStack(spacing: 12) {
Button(action: {
if let url = URL(string: "https://www.aao.org/eye-health/tips-prevention/computer-usage#:~:text=Humans normally blink about 15 times in one minute. However, studies show that we only blink about 5 to 7 times in a minute while using computers and other digital screen devices.") {
if let url = URL(
string:
"https://www.aao.org/eye-health/tips-prevention/computer-usage#:~:text=Humans normally blink about 15 times in one minute. However, studies show that we only blink about 5 to 7 times in a minute while using computers and other digital screen devices."
) {
NSWorkspace.shared.open(url)
}
}) {
Image(systemName: "info.circle")
.foregroundColor(.white)
.foregroundStyle(.white)
}
.buttonStyle(.plain)
Text("We blink much less when focusing on screens. Regular blink reminders help prevent dry eyes.")
.font(.headline)
.foregroundColor(.white)
Text(
"We blink much less when focusing on screens. Regular blink reminders help prevent dry eyes."
)
.font(.headline)
.foregroundStyle(.white)
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
.glassEffectIfAvailable(
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
VStack(alignment: .leading, spacing: 20) {
Toggle("Enable Blink Reminders", isOn: $settingsManager.settings.blinkTimer.enabled)
.font(.headline)
Toggle(
"Enable Blink Reminders", isOn: $settingsManager.settings.blinkTimer.enabled
)
.font(.headline)
if settingsManager.settings.blinkTimer.enabled {
VStack(alignment: .leading, spacing: 12) {
Text("Remind me every:")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack {
Slider(
value: Binding(
get: { Double(settingsManager.settings.blinkTimer.intervalSeconds / 60) },
set: { settingsManager.settings.blinkTimer.intervalSeconds = Int($0) * 60 }
get: {
Double(
settingsManager.settings.blinkTimer.intervalSeconds
/ 60)
},
set: {
settingsManager.settings.blinkTimer.intervalSeconds =
Int($0) * 60
}
),
in: 1...20,
step: 1
)
Text("\(settingsManager.settings.blinkTimer.intervalSeconds / 60) min")
.frame(width: 60, alignment: .trailing)
.monospacedDigit()
Text(
"\(settingsManager.settings.blinkTimer.intervalSeconds / 60) min"
)
.frame(width: 60, alignment: .trailing)
.monospacedDigit()
}
}
}
@@ -68,29 +85,33 @@ struct BlinkSetupView: View {
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
if settingsManager.settings.blinkTimer.enabled {
Text("You will be subtly reminded every \(settingsManager.settings.blinkTimer.intervalSeconds / 60) minutes to blink")
.font(.subheadline)
.foregroundColor(.secondary)
Text(
"You will be subtly reminded every \(settingsManager.settings.blinkTimer.intervalSeconds / 60) minutes to blink"
)
.font(.subheadline)
.foregroundStyle(.secondary)
} else {
Text("Blink reminders are currently disabled.")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Button(action: showPreviewWindow) {
HStack(spacing: 8) {
Image(systemName: "eye")
.foregroundColor(.white)
.foregroundStyle(.white)
Text("Preview Reminder")
.font(.headline)
.foregroundColor(.white)
.foregroundStyle(.white)
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
.contentShape(RoundedRectangle(cornerRadius: 10))
}
.buttonStyle(.plain)
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10))
.glassEffectIfAvailable(
GlassStyle.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10)
)
}
Spacer()
@@ -104,7 +125,9 @@ struct BlinkSetupView: View {
guard let screen = NSScreen.main else { return }
previewWindowController = PreviewWindowHelper.showPreview(
on: screen,
content: BlinkReminderView(sizePercentage: settingsManager.settings.subtleReminderSize.percentage) { [weak previewWindowController] in
content: BlinkReminderView(
sizePercentage: settingsManager.settings.subtleReminderSize.percentage
) { [weak previewWindowController] in
previewWindowController?.window?.close()
}
)

View File

@@ -11,55 +11,55 @@ struct CompletionView: View {
var body: some View {
VStack(spacing: 30) {
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 80))
.foregroundColor(.green)
.foregroundStyle(.green)
Text("You're All Set!")
.font(.system(size: 36, weight: .bold))
Text("Gaze will now help you take care of your eyes and posture")
.font(.title3)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
VStack(alignment: .leading, spacing: 16) {
Text("What happens next:")
.font(.headline)
.padding(.horizontal)
HStack(spacing: 16) {
Image(systemName: "menubar.rectangle")
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
.frame(width: 30)
Text("Gaze will appear in your menu bar")
.font(.subheadline)
}
.padding(.horizontal)
HStack(spacing: 16) {
Image(systemName: "clock")
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
.frame(width: 30)
Text("Timers will start automatically")
.font(.subheadline)
}
.padding(.horizontal)
HStack(spacing: 16) {
Image(systemName: "gearshape")
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
.frame(width: 30)
Text("Adjust settings anytime from the menu bar")
.font(.subheadline)
}
.padding(.horizontal)
HStack(spacing: 16) {
Image(systemName: "plus.circle")
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
.frame(width: 30)
Text("Create custom timers in Settings for additional reminders")
.font(.subheadline)
@@ -68,7 +68,7 @@ struct CompletionView: View {
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
Spacer()
}
.frame(width: 600, height: 450)

View File

@@ -6,8 +6,8 @@
//
import AVFoundation
import SwiftUI
import Foundation
import SwiftUI
struct EnforceModeSetupView: View {
@Bindable var settingsManager: SettingsManager
@@ -33,7 +33,7 @@ struct EnforceModeSetupView: View {
VStack(spacing: 30) {
Text("Use your camera to ensure you take breaks")
.font(.title3)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
VStack(spacing: 20) {
@@ -43,7 +43,7 @@ struct EnforceModeSetupView: View {
.font(.headline)
Text("Camera activates 3 seconds before lookaway reminders")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
Toggle(
@@ -149,7 +149,7 @@ struct EnforceModeSetupView: View {
HStack {
Image(systemName: "target")
.font(.title3)
.foregroundColor(.blue)
.foregroundStyle(.blue)
Text("Eye Tracking Calibration")
.font(.headline)
}
@@ -158,7 +158,7 @@ struct EnforceModeSetupView: View {
VStack(alignment: .leading, spacing: 8) {
Text(calibrationManager.getCalibrationSummary())
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
if calibrationManager.needsRecalibration() {
Label(
@@ -166,17 +166,17 @@ struct EnforceModeSetupView: View {
systemImage: "exclamationmark.triangle.fill"
)
.font(.caption)
.foregroundColor(.orange)
.foregroundStyle(.orange)
} else {
Label("Calibration active and valid", systemImage: "checkmark.circle.fill")
.font(.caption)
.foregroundColor(.green)
.foregroundStyle(.green)
}
}
} else {
Text("Not calibrated - using default thresholds")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Button(action: {
@@ -214,10 +214,10 @@ struct EnforceModeSetupView: View {
if let layer = previewLayer {
ZStack {
CameraPreviewView(previewLayer: layer, borderColor: borderColor)
// Pupil detection overlay (drawn on video)
PupilOverlayView(eyeTrackingService: eyeTrackingService)
// Debug info overlay (top-right corner)
VStack {
HStack {
@@ -258,7 +258,7 @@ struct EnforceModeSetupView: View {
/*? " Break compliance detected" : " Please look away from screen"*/
/*)*/
/*.font(.caption)*/
/*.foregroundColor(lookingAway ? .green : .orange)*/
/*.foregroundStyle(lookingAway ? .green : .orange)*/
/*.frame(maxWidth: .infinity, alignment: .center)*/
/*.padding(.top, 4)*/
/*}*/
@@ -277,15 +277,15 @@ struct EnforceModeSetupView: View {
if cameraService.isCameraAuthorized {
Label("Authorized", systemImage: "checkmark.circle.fill")
.font(.caption)
.foregroundColor(.green)
.foregroundStyle(.green)
} else if let error = cameraService.cameraError {
Label(error.localizedDescription, systemImage: "exclamationmark.triangle.fill")
.font(.caption)
.foregroundColor(.orange)
.foregroundStyle(.orange)
} else {
Label("Not authorized", systemImage: "xmark.circle.fill")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
}
@@ -337,14 +337,14 @@ struct EnforceModeSetupView: View {
HStack {
Image(systemName: "timer")
.font(.title2)
.foregroundColor(.orange)
.foregroundStyle(.orange)
VStack(alignment: .leading, spacing: 4) {
Text("Camera Ready")
.font(.headline)
Text("Will activate 3 seconds before lookaway reminder")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
@@ -357,11 +357,11 @@ struct EnforceModeSetupView: View {
VStack(spacing: 8) {
Image(systemName: icon)
.font(.title2)
.foregroundColor(isActive ? .green : .secondary)
.foregroundStyle(isActive ? .green : .secondary)
Text(title)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
@@ -372,7 +372,7 @@ struct EnforceModeSetupView: View {
HStack {
Image(systemName: "lock.shield.fill")
.font(.title3)
.foregroundColor(.blue)
.foregroundStyle(.blue)
Text("Privacy Information")
.font(.headline)
}
@@ -381,11 +381,10 @@ struct EnforceModeSetupView: View {
privacyBullet("All processing happens on-device")
privacyBullet("No images are stored or transmitted")
privacyBullet("Camera only active during lookaway reminders (3 second window)")
privacyBullet("Eyes closed does not affect countdown")
privacyBullet("You can disable at any time")
privacyBullet("You can always force quit with cmd+q")
}
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
.padding()
.glassEffectIfAvailable(
@@ -396,7 +395,7 @@ struct EnforceModeSetupView: View {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "checkmark")
.font(.caption2)
.foregroundColor(.blue)
.foregroundStyle(.blue)
Text(text)
}
}
@@ -442,7 +441,7 @@ struct EnforceModeSetupView: View {
systemName: eyeTrackingService.enableDebugLogging
? "ant.circle.fill" : "ant.circle"
)
.foregroundColor(eyeTrackingService.enableDebugLogging ? .orange : .secondary)
.foregroundStyle(eyeTrackingService.enableDebugLogging ? .orange : .secondary)
}
.buttonStyle(.plain)
.help("Toggle console debug logging")
@@ -461,7 +460,7 @@ struct EnforceModeSetupView: View {
Text("Live Values:")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
if let leftRatio = eyeTrackingService.debugLeftPupilRatio,
let rightRatio = eyeTrackingService.debugRightPupilRatio
@@ -470,23 +469,23 @@ struct EnforceModeSetupView: View {
VStack(alignment: .leading, spacing: 2) {
Text("Left Pupil: \(String(format: "%.3f", leftRatio))")
.font(.caption2)
.foregroundColor(
.foregroundStyle(
!EyeTrackingConstants.minPupilEnabled
&& !EyeTrackingConstants.maxPupilEnabled
? .secondary
: (leftRatio < EyeTrackingConstants.minPupilRatio
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
? .orange : .green
? Color.orange : Color.green
)
Text("Right Pupil: \(String(format: "%.3f", rightRatio))")
.font(.caption2)
.foregroundColor(
.foregroundStyle(
!EyeTrackingConstants.minPupilEnabled
&& !EyeTrackingConstants.maxPupilEnabled
? .secondary
: (rightRatio < EyeTrackingConstants.minPupilRatio
|| rightRatio > EyeTrackingConstants.maxPupilRatio)
? .orange : .green
? Color.orange : Color.green
)
}
@@ -497,7 +496,7 @@ struct EnforceModeSetupView: View {
"Range: \(String(format: "%.2f", EyeTrackingConstants.minPupilRatio)) - \(String(format: "%.2f", EyeTrackingConstants.maxPupilRatio))"
)
.font(.caption2)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
let bothEyesOut =
(leftRatio < EyeTrackingConstants.minPupilRatio
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
@@ -505,13 +504,13 @@ struct EnforceModeSetupView: View {
|| rightRatio > EyeTrackingConstants.maxPupilRatio)
Text(bothEyesOut ? "Both Out ⚠️" : "In Range ✓")
.font(.caption2)
.foregroundColor(bothEyesOut ? .orange : .green)
.foregroundStyle(bothEyesOut ? .orange : .green)
}
}
} else {
Text("Pupil data unavailable")
.font(.caption2)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
if let yaw = eyeTrackingService.debugYaw,
@@ -521,21 +520,21 @@ struct EnforceModeSetupView: View {
VStack(alignment: .leading, spacing: 2) {
Text("Yaw: \(String(format: "%.3f", yaw))")
.font(.caption2)
.foregroundColor(
.foregroundStyle(
!EyeTrackingConstants.yawEnabled
? .secondary
: abs(yaw) > EyeTrackingConstants.yawThreshold
? .orange : .green
? Color.orange : Color.green
)
Text("Pitch: \(String(format: "%.3f", pitch))")
.font(.caption2)
.foregroundColor(
.foregroundStyle(
!EyeTrackingConstants.pitchUpEnabled
&& !EyeTrackingConstants.pitchDownEnabled
? .secondary
: (pitch > EyeTrackingConstants.pitchUpThreshold
|| pitch < EyeTrackingConstants.pitchDownThreshold)
? .orange : .green
? Color.orange : Color.green
)
}
@@ -546,12 +545,12 @@ struct EnforceModeSetupView: View {
"Yaw Max: \(String(format: "%.2f", EyeTrackingConstants.yawThreshold))"
)
.font(.caption2)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
Text(
"Pitch: \(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) to \(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold))"
)
.font(.caption2)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
}
}
@@ -565,48 +564,54 @@ struct EnforceModeSetupView: View {
Text("Current Threshold Values:")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack {
Text("Yaw Threshold:")
Spacer()
Text("\(String(format: "%.2f", EyeTrackingConstants.yawThreshold)) rad")
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
HStack {
Text("Pitch Up Threshold:")
Spacer()
Text("\(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold)) rad")
.foregroundColor(.secondary)
Text(
"\(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold)) rad"
)
.foregroundStyle(.secondary)
}
HStack {
Text("Pitch Down Threshold:")
Spacer()
Text("\(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) rad")
.foregroundColor(.secondary)
Text(
"\(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) rad"
)
.foregroundStyle(.secondary)
}
HStack {
Text("Min Pupil Ratio:")
Spacer()
Text("\(String(format: "%.2f", EyeTrackingConstants.minPupilRatio))")
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
HStack {
Text("Max Pupil Ratio:")
Spacer()
Text("\(String(format: "%.2f", EyeTrackingConstants.maxPupilRatio))")
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
HStack {
Text("Eye Closed Threshold:")
Spacer()
Text("\(String(format: "%.3f", EyeTrackingConstants.eyeClosedThreshold))")
.foregroundColor(.secondary)
Text(
"\(String(format: "%.3f", EyeTrackingConstants.eyeClosedThreshold))"
)
.foregroundStyle(.secondary)
}
}
.padding(.top, 8)
@@ -622,7 +627,7 @@ struct EnforceModeSetupView: View {
VStack(alignment: .leading, spacing: 12) {
Text("Debug Eye Tracking Data")
.font(.headline)
.foregroundColor(.blue)
.foregroundStyle(.blue)
VStack(alignment: .leading, spacing: 8) {
Text("Face Detected: \(eyeTrackingService.faceDetected ? "Yes" : "No")")
@@ -643,7 +648,7 @@ struct EnforceModeSetupView: View {
}
}
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))

View File

@@ -14,26 +14,28 @@ struct GeneralSetupView: View {
var body: some View {
VStack(spacing: 0) {
SetupHeader(icon: "gearshape.fill", title: isOnboarding ? "Final Settings" : "General Settings", color: .accentColor)
SetupHeader(
icon: "gearshape.fill", title: isOnboarding ? "Final Settings" : "General Settings",
color: .accentColor)
Spacer()
VStack(spacing: 30) {
Text("Configure app preferences and support the project")
.font(.title3)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
VStack(spacing: 20) {
launchAtLoginToggle
#if !APPSTORE
softwareUpdatesSection
softwareUpdatesSection
#endif
subtleReminderSizeSection
#if !APPSTORE
supportSection
supportSection
#endif
}
}
@@ -51,7 +53,7 @@ struct GeneralSetupView: View {
.font(.headline)
Text("Start Gaze automatically when you log in")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
Toggle("", isOn: $settingsManager.settings.launchAtLogin)
@@ -65,42 +67,45 @@ struct GeneralSetupView: View {
}
#if !APPSTORE
private var softwareUpdatesSection: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Software Updates")
.font(.headline)
private var softwareUpdatesSection: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Software Updates")
.font(.headline)
if let lastCheck = updateManager.lastUpdateCheckDate {
Text("Last checked: \(lastCheck, style: .relative)")
.font(.caption)
.foregroundColor(.secondary)
.italic()
} else {
Text("Never checked for updates")
.font(.caption)
.foregroundColor(.secondary)
.italic()
if let lastCheck = updateManager.lastUpdateCheckDate {
Text("Last checked: \(lastCheck, style: .relative)")
.font(.caption)
.foregroundStyle(.secondary)
.italic()
} else {
Text("Never checked for updates")
.font(.caption)
.foregroundStyle(.secondary)
.italic()
}
}
Spacer()
Button("Check for Updates Now") {
updateManager.checkForUpdates()
}
.buttonStyle(.bordered)
Toggle(
"Automatically check for updates",
isOn: Binding(
get: { updateManager.automaticallyChecksForUpdates },
set: { updateManager.automaticallyChecksForUpdates = $0 }
)
)
.labelsHidden()
.help("Check for new versions of Gaze in the background")
}
Spacer()
Button("Check for Updates Now") {
updateManager.checkForUpdates()
}
.buttonStyle(.bordered)
Toggle("Automatically check for updates", isOn: Binding(
get: { updateManager.automaticallyChecksForUpdates },
set: { updateManager.automaticallyChecksForUpdates = $0 }
))
.labelsHidden()
.help("Check for new versions of Gaze in the background")
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
}
#endif
private var subtleReminderSizeSection: some View {
@@ -110,20 +115,28 @@ struct GeneralSetupView: View {
Text("Adjust the size of blink and posture reminders")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack(spacing: 12) {
ForEach(ReminderSize.allCases, id: \.self) { size in
Button(action: { settingsManager.settings.subtleReminderSize = size }) {
VStack(spacing: 8) {
Circle()
.fill(settingsManager.settings.subtleReminderSize == size ? Color.accentColor : Color.secondary.opacity(0.3))
.fill(
settingsManager.settings.subtleReminderSize == size
? Color.accentColor : Color.secondary.opacity(0.3)
)
.frame(width: iconSize(for: size), height: iconSize(for: size))
Text(size.displayName)
.font(.caption)
.fontWeight(settingsManager.settings.subtleReminderSize == size ? .semibold : .regular)
.foregroundColor(settingsManager.settings.subtleReminderSize == size ? .primary : .secondary)
.fontWeight(
settingsManager.settings.subtleReminderSize == size
? .semibold : .regular
)
.foregroundStyle(
settingsManager.settings.subtleReminderSize == size
? .primary : .secondary)
}
.frame(maxWidth: .infinity, minHeight: 60)
.padding(.vertical, 12)
@@ -142,31 +155,31 @@ struct GeneralSetupView: View {
}
#if !APPSTORE
private var supportSection: some View {
VStack(spacing: 12) {
Text("Support & Contribute")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
private var supportSection: some View {
VStack(spacing: 12) {
Text("Support & Contribute")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
ExternalLinkButton(
icon: "chevron.left.forwardslash.chevron.right",
title: "View on GitHub",
subtitle: "Star the repo, report issues, contribute",
url: "https://github.com/mikefreno/Gaze",
tint: nil
)
ExternalLinkButton(
icon: "chevron.left.forwardslash.chevron.right",
title: "View on GitHub",
subtitle: "Star the repo, report issues, contribute",
url: "https://github.com/mikefreno/Gaze",
tint: nil
)
ExternalLinkButton(
icon: "cup.and.saucer.fill",
iconColor: .brown,
title: "Buy Me a Coffee",
subtitle: "Support development of Gaze",
url: "https://buymeacoffee.com/mikefreno",
tint: .orange
)
ExternalLinkButton(
icon: "cup.and.saucer.fill",
iconColor: .brown,
title: "Buy Me a Coffee",
subtitle: "Support development of Gaze",
url: "https://buymeacoffee.com/mikefreno",
tint: .orange
)
}
.padding()
}
.padding()
}
#endif
private func applyLaunchAtLoginSetting(enabled: Bool) {
@@ -205,14 +218,14 @@ struct ExternalLinkButton: View {
HStack {
Image(systemName: icon)
.font(.title3)
.foregroundColor(iconColor)
.foregroundStyle(iconColor)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline)
.fontWeight(.semibold)
Text(subtitle)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "arrow.up.right")
@@ -224,7 +237,8 @@ struct ExternalLinkButton: View {
}
.buttonStyle(.plain)
.glassEffectIfAvailable(
tint != nil ? GlassStyle.regular.tint(tint!).interactive() : GlassStyle.regular.interactive(),
tint != nil
? GlassStyle.regular.tint(tint!).interactive() : GlassStyle.regular.interactive(),
in: .rect(cornerRadius: 10)
)
}

View File

@@ -21,21 +21,27 @@ struct PostureSetupView: View {
VStack(spacing: 30) {
HStack(spacing: 12) {
Button(action: {
if let url = URL(string: "https://pubmed.ncbi.nlm.nih.gov/40111906/#:~:text=For%20studies%20exploring%20sitting%20posture%2C%20seven%20found%20a%20relationship%20with%20LBP.%20Regarding%20studies%20on%20sitting%20behavior%2C%20only%20one%20showed%20no%20relationship%20between%20LBP%20prevalence") {
if let url = URL(
string:
"https://pubmed.ncbi.nlm.nih.gov/40111906/#:~:text=For%20studies%20exploring%20sitting%20posture%2C%20seven%20found%20a%20relationship%20with%20LBP.%20Regarding%20studies%20on%20sitting%20behavior%2C%20only%20one%20showed%20no%20relationship%20between%20LBP%20prevalence"
) {
NSWorkspace.shared.open(url)
}
}) {
Image(systemName: "info.circle")
.foregroundColor(.white)
.foregroundStyle(.white)
}
.buttonStyle(.plain)
Text("Regular posture checks help prevent back and neck pain from prolonged sitting")
.font(.headline)
.foregroundColor(.white)
Text(
"Regular posture checks help prevent back and neck pain from prolonged sitting"
)
.font(.headline)
.foregroundStyle(.white)
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
.glassEffectIfAvailable(
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
SliderSection(
intervalSettings: Binding(
@@ -46,7 +52,8 @@ struct PostureSetupView: View {
)
},
set: { newValue in
settingsManager.settings.postureTimer.intervalSeconds = (newValue.val ?? 30) * 60
settingsManager.settings.postureTimer.intervalSeconds =
(newValue.val ?? 30) * 60
}
),
countdownSettings: nil,
@@ -67,7 +74,9 @@ struct PostureSetupView: View {
guard let screen = NSScreen.main else { return }
previewWindowController = PreviewWindowHelper.showPreview(
on: screen,
content: PostureReminderView(sizePercentage: settingsManager.settings.subtleReminderSize.percentage) { [weak previewWindowController] in
content: PostureReminderView(
sizePercentage: settingsManager.settings.subtleReminderSize.percentage
) { [weak previewWindowController] in
previewWindowController?.window?.close()
}
)

View File

@@ -14,10 +14,10 @@ struct SmartModeSetupView: View {
var body: some View {
VStack(spacing: 0) {
SetupHeader(icon: "brain.fill", title: "Smart Mode", color: .purple)
Text("Automatically manage timers based on your activity")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
.padding(.bottom, 30)
Spacer()
@@ -42,18 +42,21 @@ struct SmartModeSetupView: View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "arrow.up.left.and.arrow.down.right")
.foregroundColor(.blue)
.foregroundStyle(.blue)
Text("Auto-pause on Fullscreen")
.font(.headline)
}
Text("Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)")
.font(.caption)
.foregroundColor(.secondary)
Text(
"Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)"
)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnFullscreen)
.labelsHidden()
.onChange(of: settingsManager.settings.smartMode.autoPauseOnFullscreen) { _, newValue in
.onChange(of: settingsManager.settings.smartMode.autoPauseOnFullscreen) {
_, newValue in
if newValue {
permissionManager.requestAuthorizationIfNeeded()
}
@@ -61,7 +64,8 @@ struct SmartModeSetupView: View {
}
if settingsManager.settings.smartMode.autoPauseOnFullscreen,
permissionManager.authorizationStatus != .authorized {
permissionManager.authorizationStatus != .authorized
{
permissionWarningView
}
}
@@ -81,7 +85,7 @@ struct SmartModeSetupView: View {
Text("macOS requires Screen Recording permission to detect other apps in fullscreen.")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack {
Button("Grant Access") {
@@ -107,13 +111,13 @@ struct SmartModeSetupView: View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "moon.zzz.fill")
.foregroundColor(.indigo)
.foregroundStyle(.indigo)
Text("Auto-pause on Idle")
.font(.headline)
}
Text("Timers will pause when you're inactive for more than the threshold below")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnIdle)
@@ -139,13 +143,15 @@ struct SmartModeSetupView: View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "chart.line.uptrend.xyaxis")
.foregroundColor(.green)
.foregroundStyle(.green)
Text("Track Usage Statistics")
.font(.headline)
}
Text("Monitor active and idle time, with automatic reset after the specified duration")
.font(.caption)
.foregroundColor(.secondary)
Text(
"Monitor active and idle time, with automatic reset after the specified duration"
)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Toggle("", isOn: $settingsManager.settings.smartMode.trackUsage)
@@ -182,7 +188,7 @@ struct ThresholdSlider: View {
Spacer()
Text("\(value) \(unit)")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Slider(

View File

@@ -17,7 +17,7 @@ struct UserTimersView: View {
VStack(spacing: 16) {
Image(systemName: "clock.badge.checkmark")
.font(.system(size: 60))
.foregroundColor(.purple)
.foregroundStyle(.purple)
Text("Custom Timers")
.font(.system(size: 28, weight: .bold))
}
@@ -28,14 +28,14 @@ struct UserTimersView: View {
VStack(spacing: 30) {
Text("Create your own reminder schedules")
.font(.title3)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
HStack(spacing: 12) {
Image(systemName: "info.circle")
.foregroundColor(.white)
.foregroundStyle(.white)
Text("Add up to 3 custom timers with your own intervals and messages")
.font(.headline)
.foregroundColor(.white)
.foregroundStyle(.white)
}
.padding()
.glassEffectIfAvailable(
@@ -66,13 +66,13 @@ struct UserTimersView: View {
VStack(spacing: 12) {
Image(systemName: "clock.badge.questionmark")
.font(.system(size: 40))
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
Text("No custom timers yet")
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
Text("Click 'Add Timer' to create your first custom reminder")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
.padding(40)
@@ -153,7 +153,7 @@ struct UserTimerRow: View {
.frame(width: 12, height: 12)
Image(systemName: timer.type == .subtle ? "eye.circle" : "rectangle.on.rectangle")
.foregroundColor(timer.color)
.foregroundStyle(timer.color)
.frame(width: 24)
VStack(alignment: .leading, spacing: 4) {
@@ -165,7 +165,7 @@ struct UserTimerRow: View {
"\(timer.type.displayName)\(timer.timeOnScreenSeconds)s on screen • \(timer.intervalMinutes) min interval"
)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
Spacer()
@@ -179,14 +179,14 @@ struct UserTimerRow: View {
Button(action: onEdit) {
Image(systemName: "pencil.circle.fill")
.font(.title3)
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(.plain)
Button(action: { showingDeleteConfirmation = true }) {
Image(systemName: "trash.circle.fill")
.font(.title3)
.foregroundColor(.red)
.foregroundStyle(.red)
}
.buttonStyle(.plain)
.confirmationDialog("Delete Timer", isPresented: $showingDeleteConfirmation) {
@@ -264,7 +264,7 @@ struct UserTimerEditSheet: View {
.textFieldStyle(.roundedBorder)
Text("Example: \"Stretch Break\", \"Eye Rest\", \"Water Break\"")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
VStack(alignment: .leading, spacing: 8) {
@@ -318,7 +318,7 @@ struct UserTimerEditSheet: View {
: "Full screen reminder with animation"
)
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
if type == .overlay {
@@ -359,7 +359,7 @@ struct UserTimerEditSheet: View {
}
Text("How often this reminder will appear (in minutes)")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
VStack(alignment: .leading, spacing: 8) {
@@ -369,7 +369,7 @@ struct UserTimerEditSheet: View {
.textFieldStyle(.roundedBorder)
Text("Leave blank to show a default timer notification")
.font(.caption)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
}
.padding()

View File

@@ -14,14 +14,14 @@ struct WelcomeView: View {
Image(systemName: "eye.fill")
.font(.system(size: 80))
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
Text("Welcome to Gaze")
.font(.system(size: 36, weight: .bold))
Text("Take care of your eyes and posture")
.font(.title3)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 16) {
FeatureRow(
@@ -66,7 +66,7 @@ struct FeatureRow: View {
HStack(alignment: .top, spacing: 16) {
Image(systemName: icon)
.font(.title2)
.foregroundColor(iconColor)
.foregroundStyle(iconColor)
.frame(width: 30)
VStack(alignment: .leading, spacing: 4) {
@@ -74,7 +74,7 @@ struct FeatureRow: View {
.font(.headline)
Text(description)
.font(.subheadline)
.foregroundColor(.secondary)
.foregroundStyle(.secondary)
}
}
}