general: foregroundColor -> foregroundStyle (will change in future)
This commit is contained in:
@@ -34,20 +34,22 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Image(systemName: "eye.circle.fill")
|
Image(systemName: "eye.circle.fill")
|
||||||
.font(.system(size: 80))
|
.font(.system(size: 80))
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
|
|
||||||
Text("Eye Tracking Calibration")
|
Text("Eye Tracking Calibration")
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
|
.foregroundStyle(.white)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
|
||||||
Text("This calibration will help improve eye tracking accuracy.")
|
Text("This calibration will help improve eye tracking accuracy.")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.gray)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 15) {
|
VStack(alignment: .leading, spacing: 15) {
|
||||||
InstructionRow(icon: "1.circle.fill", text: "Look at each target on the screen")
|
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: "3.circle.fill", text: "Follow the countdown at each position")
|
||||||
InstructionRow(icon: "4.circle.fill", text: "Takes about 30-45 seconds")
|
InstructionRow(icon: "4.circle.fill", text: "Takes about 30-45 seconds")
|
||||||
}
|
}
|
||||||
@@ -57,11 +59,11 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
Text("Last calibration:")
|
Text("Last calibration:")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.gray)
|
||||||
Text(calibrationManager.getCalibrationSummary())
|
Text(calibrationManager.getCalibrationSummary())
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.gray)
|
||||||
}
|
}
|
||||||
.padding(.vertical)
|
.padding(.vertical)
|
||||||
}
|
}
|
||||||
@@ -70,6 +72,8 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
Button("Cancel") {
|
Button("Cancel") {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.buttonStyle(.plain)
|
||||||
.keyboardShortcut(.escape, modifiers: [])
|
.keyboardShortcut(.escape, modifiers: [])
|
||||||
|
|
||||||
Button("Start Calibration") {
|
Button("Start Calibration") {
|
||||||
@@ -113,10 +117,10 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Calibrating...")
|
Text("Calibrating...")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(calibrationManager.progressText)
|
Text(calibrationManager.progressText)
|
||||||
.foregroundColor(.white.opacity(0.7))
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressView(value: calibrationManager.progress)
|
ProgressView(value: calibrationManager.progress)
|
||||||
@@ -141,7 +145,9 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
.stroke(Color.blue.opacity(0.3), lineWidth: 3)
|
.stroke(Color.blue.opacity(0.3), lineWidth: 3)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.scaleEffect(isCountingDown ? 1.2 : 1.0)
|
.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
|
// Inner circle
|
||||||
Circle()
|
Circle()
|
||||||
@@ -152,18 +158,18 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
if isCountingDown && countdownValue > 0 {
|
if isCountingDown && countdownValue > 0 {
|
||||||
Text("\(countdownValue)")
|
Text("\(countdownValue)")
|
||||||
.font(.system(size: 36, weight: .bold))
|
.font(.system(size: 36, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
} else if calibrationManager.samplesCollected > 0 {
|
} else if calibrationManager.samplesCollected > 0 {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.font(.system(size: 30, weight: .bold))
|
.font(.system(size: 30, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instruction text
|
// Instruction text
|
||||||
Text(step.instructionText)
|
Text(step.instructionText)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
.padding(.vertical, 15)
|
.padding(.vertical, 15)
|
||||||
.background(Color.black.opacity(0.7))
|
.background(Color.black.opacity(0.7))
|
||||||
@@ -182,7 +188,7 @@ struct EyeTrackingCalibrationView: View {
|
|||||||
calibrationManager.skipStep()
|
calibrationManager.skipStep()
|
||||||
} label: {
|
} label: {
|
||||||
Text("Skip this position")
|
Text("Skip this position")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.background(Color.white.opacity(0.2))
|
.background(Color.white.opacity(0.2))
|
||||||
@@ -258,10 +264,11 @@ struct InstructionRow: View {
|
|||||||
HStack(spacing: 15) {
|
HStack(spacing: 15) {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
|
|
||||||
Text(text)
|
Text(text)
|
||||||
|
.foregroundStyle(.white)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct GazeOverlayView: View {
|
|||||||
Text(eyeTrackingService.isInFrame ? "In Frame" : "No Face")
|
Text(eyeTrackingService.isInFrame ? "In Frame" : "No Face")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 6)
|
.padding(.vertical, 6)
|
||||||
@@ -46,7 +46,9 @@ struct GazeOverlayView: View {
|
|||||||
ForEach(0..<3, id: \.self) { row in
|
ForEach(0..<3, id: \.self) { row in
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
ForEach(0..<3, id: \.self) { col in
|
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)
|
gridCell(row: row, col: col, isActive: isActive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,7 @@ struct GazeOverlayView: View {
|
|||||||
|
|
||||||
Text(direction.rawValue)
|
Text(direction.rawValue)
|
||||||
.font(.system(size: 14, weight: .bold))
|
.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)
|
.frame(width: 28, height: 28)
|
||||||
}
|
}
|
||||||
@@ -95,12 +97,12 @@ struct GazeOverlayView: View {
|
|||||||
if let leftH = eyeTrackingService.debugLeftPupilRatio {
|
if let leftH = eyeTrackingService.debugLeftPupilRatio {
|
||||||
Text("L.H: \(String(format: "%.2f", leftH))")
|
Text("L.H: \(String(format: "%.2f", leftH))")
|
||||||
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
if let rightH = eyeTrackingService.debugRightPupilRatio {
|
if let rightH = eyeTrackingService.debugRightPupilRatio {
|
||||||
Text("R.H: \(String(format: "%.2f", rightH))")
|
Text("R.H: \(String(format: "%.2f", rightH))")
|
||||||
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,25 +110,26 @@ struct GazeOverlayView: View {
|
|||||||
if let leftV = eyeTrackingService.debugLeftVerticalRatio {
|
if let leftV = eyeTrackingService.debugLeftVerticalRatio {
|
||||||
Text("L.V: \(String(format: "%.2f", leftV))")
|
Text("L.V: \(String(format: "%.2f", leftV))")
|
||||||
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
if let rightV = eyeTrackingService.debugRightVerticalRatio {
|
if let rightV = eyeTrackingService.debugRightVerticalRatio {
|
||||||
Text("R.V: \(String(format: "%.2f", rightV))")
|
Text("R.V: \(String(format: "%.2f", rightV))")
|
||||||
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show averaged ratios
|
// Show averaged ratios
|
||||||
if let leftH = eyeTrackingService.debugLeftPupilRatio,
|
if let leftH = eyeTrackingService.debugLeftPupilRatio,
|
||||||
let rightH = eyeTrackingService.debugRightPupilRatio,
|
let rightH = eyeTrackingService.debugRightPupilRatio,
|
||||||
let leftV = eyeTrackingService.debugLeftVerticalRatio,
|
let leftV = eyeTrackingService.debugLeftVerticalRatio,
|
||||||
let rightV = eyeTrackingService.debugRightVerticalRatio {
|
let rightV = eyeTrackingService.debugRightVerticalRatio
|
||||||
|
{
|
||||||
let avgH = (leftH + rightH) / 2.0
|
let avgH = (leftH + rightH) / 2.0
|
||||||
let avgV = (leftV + rightV) / 2.0
|
let avgV = (leftV + rightV) / 2.0
|
||||||
Text("Avg H:\(String(format: "%.2f", avgH)) V:\(String(format: "%.2f", avgV))")
|
Text("Avg H:\(String(format: "%.2f", avgH)) V:\(String(format: "%.2f", avgV))")
|
||||||
.font(.system(size: 9, weight: .bold, design: .monospaced))
|
.font(.system(size: 9, weight: .bold, design: .monospaced))
|
||||||
.foregroundColor(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
@@ -143,7 +146,7 @@ struct GazeOverlayView: View {
|
|||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
Text("Left")
|
Text("Left")
|
||||||
.font(.system(size: 8, weight: .bold))
|
.font(.system(size: 8, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
eyeImageView(
|
eyeImageView(
|
||||||
@@ -165,7 +168,7 @@ struct GazeOverlayView: View {
|
|||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
Text("Right")
|
Text("Right")
|
||||||
.font(.system(size: 8, weight: .bold))
|
.font(.system(size: 8, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
eyeImageView(
|
eyeImageView(
|
||||||
@@ -190,7 +193,9 @@ struct GazeOverlayView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
let displaySize: CGFloat = 50
|
||||||
|
|
||||||
return VStack(spacing: 2) {
|
return VStack(spacing: 2) {
|
||||||
@@ -203,7 +208,9 @@ struct GazeOverlayView: View {
|
|||||||
.frame(width: displaySize, height: displaySize)
|
.frame(width: displaySize, height: displaySize)
|
||||||
|
|
||||||
// Draw pupil position marker
|
// 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 scaleX = displaySize / size.width
|
||||||
let scaleY = displaySize / size.height
|
let scaleY = displaySize / size.height
|
||||||
let scale = min(scaleX, scaleY)
|
let scale = min(scaleX, scaleY)
|
||||||
@@ -224,7 +231,7 @@ struct GazeOverlayView: View {
|
|||||||
.frame(width: displaySize, height: displaySize)
|
.frame(width: displaySize, height: displaySize)
|
||||||
Text("--")
|
Text("--")
|
||||||
.font(.system(size: 10))
|
.font(.system(size: 10))
|
||||||
.foregroundColor(.white.opacity(0.5))
|
.foregroundStyle(.white.opacity(0.5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: displaySize, height: displaySize)
|
.frame(width: displaySize, height: displaySize)
|
||||||
@@ -232,7 +239,7 @@ struct GazeOverlayView: View {
|
|||||||
|
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(.system(size: 7))
|
.font(.system(size: 7))
|
||||||
.foregroundColor(.white.opacity(0.7))
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ struct InfoBox: View {
|
|||||||
if let url = url, let urlObj = URL(string: url) {
|
if let url = url, let urlObj = URL(string: url) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
UIApplication.shared.open(urlObj)
|
UIApplication.shared.open(urlObj)
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
NSWorkspace.shared.open(urlObj)
|
NSWorkspace.shared.open(urlObj)
|
||||||
#endif
|
#endif
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}.buttonStyle(.plain)
|
}.buttonStyle(.plain)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
Text(text)
|
Text(text)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
||||||
@@ -38,6 +38,10 @@ struct InfoBox: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#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")
|
InfoBox(
|
||||||
.padding()
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ struct PupilOverlayView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
// Left eye
|
// Left eye
|
||||||
if let leftRegion = eyeTrackingService.debugLeftEyeRegion,
|
if let leftRegion = eyeTrackingService.debugLeftEyeRegion,
|
||||||
let leftPupil = eyeTrackingService.debugLeftPupilPosition,
|
let leftPupil = eyeTrackingService.debugLeftPupilPosition,
|
||||||
let imageSize = eyeTrackingService.debugImageSize {
|
let imageSize = eyeTrackingService.debugImageSize
|
||||||
|
{
|
||||||
EyeOverlayShape(
|
EyeOverlayShape(
|
||||||
eyeRegion: leftRegion,
|
eyeRegion: leftRegion,
|
||||||
pupilPosition: leftPupil,
|
pupilPosition: leftPupil,
|
||||||
@@ -33,8 +34,9 @@ struct PupilOverlayView: View {
|
|||||||
|
|
||||||
// Right eye
|
// Right eye
|
||||||
if let rightRegion = eyeTrackingService.debugRightEyeRegion,
|
if let rightRegion = eyeTrackingService.debugRightEyeRegion,
|
||||||
let rightPupil = eyeTrackingService.debugRightPupilPosition,
|
let rightPupil = eyeTrackingService.debugRightPupilPosition,
|
||||||
let imageSize = eyeTrackingService.debugImageSize {
|
let imageSize = eyeTrackingService.debugImageSize
|
||||||
|
{
|
||||||
EyeOverlayShape(
|
EyeOverlayShape(
|
||||||
eyeRegion: rightRegion,
|
eyeRegion: rightRegion,
|
||||||
pupilPosition: rightPupil,
|
pupilPosition: rightPupil,
|
||||||
@@ -160,14 +162,14 @@ private struct EyeOverlayShape: View {
|
|||||||
// Label
|
// Label
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(.system(size: 10, weight: .bold))
|
.font(.system(size: 10, weight: .bold))
|
||||||
.foregroundColor(color)
|
.foregroundStyle(color)
|
||||||
.position(x: eyeRect.minX + 8, y: eyeRect.minY - 8)
|
.position(x: eyeRect.minX + 8, y: eyeRect.minY - 8)
|
||||||
|
|
||||||
// Debug: Show raw coordinates
|
// Debug: Show raw coordinates
|
||||||
Text("\(label): (\(Int(pupilPosition.x)), \(Int(pupilPosition.y)))")
|
Text("\(label): (\(Int(pupilPosition.x)), \(Int(pupilPosition.y)))")
|
||||||
.font(.system(size: 8, design: .monospaced))
|
.font(.system(size: 8, design: .monospaced))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
.background(Color.black.opacity(0.7))
|
.background(.black.opacity(0.7))
|
||||||
.position(x: eyeRect.midX, y: eyeRect.maxY + 10)
|
.position(x: eyeRect.midX, y: eyeRect.maxY + 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct SetupHeader: View {
|
|||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.system(size: 60))
|
.font(.system(size: 60))
|
||||||
.foregroundColor(color)
|
.foregroundStyle(color)
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.system(size: 28, weight: .bold))
|
.font(.system(size: 28, weight: .bold))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct SliderSection: View {
|
|||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Remind me every:")
|
Text("Remind me every:")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
HStack {
|
HStack {
|
||||||
Slider(
|
Slider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
@@ -62,7 +62,7 @@ struct SliderSection: View {
|
|||||||
if let range = countdownSettings.range {
|
if let range = countdownSettings.range {
|
||||||
Text("Look away for:")
|
Text("Look away for:")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
HStack {
|
HStack {
|
||||||
Slider(
|
Slider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
@@ -89,14 +89,14 @@ struct SliderSection: View {
|
|||||||
reminderText
|
reminderText
|
||||||
)
|
)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
"\(type) reminders are currently disabled."
|
"\(type) reminders are currently disabled."
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@@ -104,10 +104,10 @@ struct SliderSection: View {
|
|||||||
}) {
|
}) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "eye")
|
Image(systemName: "eye")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
Text("Preview Reminder")
|
Text("Preview Reminder")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ final class OnboardingWindowPresenter {
|
|||||||
self?.windowController = nil
|
self?.windowController = nil
|
||||||
self?.removeCloseObserver()
|
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
|
#if APPSTORE
|
||||||
.frame(minWidth: 1000, minHeight: 700)
|
.frame(minWidth: 1000, minHeight: 700)
|
||||||
#else
|
#else
|
||||||
.frame(minWidth: 1000, minHeight: 900)
|
.frame(minWidth: 1000, minHeight: 900)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +160,12 @@ struct OnboardingContainerView: View {
|
|||||||
}
|
}
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
|
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
|
||||||
.foregroundColor(.primary)
|
.foregroundStyle(.primary)
|
||||||
.contentShape(RoundedRectangle(cornerRadius: 10))
|
.contentShape(RoundedRectangle(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.glassEffectIfAvailable(GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
|
.glassEffectIfAvailable(
|
||||||
|
GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@@ -173,11 +175,14 @@ struct OnboardingContainerView: View {
|
|||||||
currentPage += 1
|
currentPage += 1
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(currentPage == 0 ? "Let's Get Started" : currentPage == 5 ? "Get Started" : "Continue")
|
Text(
|
||||||
.font(.headline)
|
currentPage == 0
|
||||||
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
|
? "Let's Get Started" : currentPage == 5 ? "Get Started" : "Continue"
|
||||||
.foregroundColor(.white)
|
)
|
||||||
.contentShape(RoundedRectangle(cornerRadius: 10))
|
.font(.headline)
|
||||||
|
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.glassEffectIfAvailable(
|
.glassEffectIfAvailable(
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ struct MenuBarHoverButtonStyle: ButtonStyle {
|
|||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
configuration.label
|
configuration.label
|
||||||
.foregroundColor(isHovered ? .white : .primary)
|
.foregroundStyle(isHovered ? .white : .primary)
|
||||||
.glassEffectIfAvailable(
|
.glassEffectIfAvailable(
|
||||||
isHovered
|
isHovered
|
||||||
? GlassStyle.regular.tint(.accentColor).interactive()
|
? GlassStyle.regular.tint(.accentColor).interactive()
|
||||||
@@ -83,7 +83,7 @@ struct MenuBarContentView: View {
|
|||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Active Timers")
|
Text("Active Timers")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ struct MenuBarContentView: View {
|
|||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
Text("Complete Onboarding")
|
Text("Complete Onboarding")
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ struct MenuBarContentView: View {
|
|||||||
Button(action: onQuit) {
|
Button(action: onQuit) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "power")
|
Image(systemName: "power")
|
||||||
.foregroundColor(.red)
|
.foregroundStyle(.red)
|
||||||
Text("Quit Gaze")
|
Text("Quit Gaze")
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ struct MenuBarContentView: View {
|
|||||||
"v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0")"
|
"v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0")"
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
@@ -335,20 +335,20 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Image(systemName: iconName)
|
Image(systemName: iconName)
|
||||||
.foregroundColor(isHoveredBody ? .white : color)
|
.foregroundStyle(isHoveredBody ? .white : color)
|
||||||
.frame(width: 20)
|
.frame(width: 20)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(displayName)
|
Text(displayName)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(isHoveredBody ? .white : .primary)
|
.foregroundStyle(isHoveredBody ? .white : .primary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if let state = state {
|
if let state = state {
|
||||||
Text(state.remainingSeconds.asTimerDuration)
|
Text(state.remainingSeconds.asTimerDuration)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(isHoveredBody ? .white.opacity(0.8) : .secondary)
|
.foregroundStyle(isHoveredBody ? .white.opacity(0.8) : .secondary)
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,7 +365,7 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
Button(action: onDevTrigger) {
|
Button(action: onDevTrigger) {
|
||||||
Image(systemName: "bolt.fill")
|
Image(systemName: "bolt.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(isHoveredDevTrigger ? .white : .yellow)
|
.foregroundStyle(isHoveredDevTrigger ? .white : .yellow)
|
||||||
.padding(6)
|
.padding(6)
|
||||||
.contentShape(Circle())
|
.contentShape(Circle())
|
||||||
}
|
}
|
||||||
@@ -394,7 +394,7 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
systemName: isPaused ? "play.circle" : "pause.circle"
|
systemName: isPaused ? "play.circle" : "pause.circle"
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(isHoveredPauseButton ? .white : .accentColor)
|
.foregroundStyle(isHoveredPauseButton ? .white : .accentColor)
|
||||||
.padding(6)
|
.padding(6)
|
||||||
.contentShape(Circle())
|
.contentShape(Circle())
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
Button(action: onSkip) {
|
Button(action: onSkip) {
|
||||||
Image(systemName: "forward.fill")
|
Image(systemName: "forward.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(isHoveredSkip ? .white : .accentColor)
|
.foregroundStyle(isHoveredSkip ? .white : .accentColor)
|
||||||
.padding(6)
|
.padding(6)
|
||||||
.contentShape(Circle())
|
.contentShape(Circle())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ struct LookAwayReminderView: View {
|
|||||||
VStack(spacing: 40) {
|
VStack(spacing: 40) {
|
||||||
Text("Look Away")
|
Text("Look Away")
|
||||||
.font(.system(size: 64, weight: .bold))
|
.font(.system(size: 64, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
Text("Look at something 20 feet away")
|
Text("Look at something 20 feet away")
|
||||||
.font(.system(size: 28))
|
.font(.system(size: 28))
|
||||||
.foregroundColor(.white.opacity(0.9))
|
.foregroundStyle(.white.opacity(0.9))
|
||||||
|
|
||||||
GazeLottieView(
|
GazeLottieView(
|
||||||
animationName: AnimationAsset.lookAway.fileName,
|
animationName: AnimationAsset.lookAway.fileName,
|
||||||
@@ -62,14 +62,14 @@ struct LookAwayReminderView: View {
|
|||||||
|
|
||||||
Text("\(remainingSeconds)")
|
Text("\(remainingSeconds)")
|
||||||
.font(.system(size: 48, weight: .bold))
|
.font(.system(size: 48, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.countdownLabel)
|
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.countdownLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Press ESC or Space to skip")
|
Text("Press ESC or Space to skip")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.white.opacity(0.6))
|
.foregroundStyle(.white.opacity(0.6))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip button in corner
|
// Skip button in corner
|
||||||
@@ -79,7 +79,7 @@ struct LookAwayReminderView: View {
|
|||||||
Button(action: dismiss) {
|
Button(action: dismiss) {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
.font(.system(size: 32))
|
.font(.system(size: 32))
|
||||||
.foregroundColor(.white.opacity(0.7))
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.dismissButton)
|
.accessibilityIdentifier(AccessibilityIdentifiers.Reminders.dismissButton)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ struct PostureReminderView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Image(systemName: "arrow.up.circle.fill")
|
Image(systemName: "arrow.up.circle.fill")
|
||||||
.font(.system(size: scale))
|
.font(.system(size: scale))
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
}
|
}
|
||||||
.opacity(opacity)
|
.opacity(opacity)
|
||||||
.offset(y: yOffset)
|
.offset(y: yOffset)
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ struct UserTimerOverlayReminderView: View {
|
|||||||
VStack(spacing: 40) {
|
VStack(spacing: 40) {
|
||||||
Text(timer.title)
|
Text(timer.title)
|
||||||
.font(.system(size: 64, weight: .bold))
|
.font(.system(size: 64, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
if let message = timer.message, !message.isEmpty {
|
if let message = timer.message, !message.isEmpty {
|
||||||
Text(message)
|
Text(message)
|
||||||
.font(.system(size: 28))
|
.font(.system(size: 28))
|
||||||
.foregroundColor(.white.opacity(0.9))
|
.foregroundStyle(.white.opacity(0.9))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
}
|
}
|
||||||
|
|
||||||
Image(systemName: "clock.fill")
|
Image(systemName: "clock.fill")
|
||||||
.font(.system(size: 120))
|
.font(.system(size: 120))
|
||||||
.foregroundColor(timer.color)
|
.foregroundStyle(timer.color)
|
||||||
.padding(.vertical, 30)
|
.padding(.vertical, 30)
|
||||||
|
|
||||||
// Countdown display
|
// Countdown display
|
||||||
@@ -62,13 +62,13 @@ struct UserTimerOverlayReminderView: View {
|
|||||||
|
|
||||||
Text("\(remainingSeconds)")
|
Text("\(remainingSeconds)")
|
||||||
.font(.system(size: 48, weight: .bold))
|
.font(.system(size: 48, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Press ESC or Space to dismiss")
|
Text("Press ESC or Space to dismiss")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.white.opacity(0.6))
|
.foregroundStyle(.white.opacity(0.6))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss button in corner
|
// Dismiss button in corner
|
||||||
@@ -78,7 +78,7 @@ struct UserTimerOverlayReminderView: View {
|
|||||||
Button(action: dismiss) {
|
Button(action: dismiss) {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
.font(.system(size: 32))
|
.font(.system(size: 32))
|
||||||
.foregroundColor(.white.opacity(0.7))
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.padding(30)
|
.padding(30)
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ struct UserTimerReminderView: View {
|
|||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
Image(systemName: "clock.fill")
|
Image(systemName: "clock.fill")
|
||||||
.font(.system(size: baseSize * 0.4))
|
.font(.system(size: baseSize * 0.4))
|
||||||
.foregroundColor(timer.color)
|
.foregroundStyle(timer.color)
|
||||||
|
|
||||||
if let message = timer.message, !message.isEmpty {
|
if let message = timer.message, !message.isEmpty {
|
||||||
Text(message)
|
Text(message)
|
||||||
.font(.system(size: baseSize * 0.24))
|
.font(.system(size: baseSize * 0.24))
|
||||||
.foregroundColor(.primary)
|
.foregroundStyle(.primary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,45 +21,62 @@ struct BlinkSetupView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Button(action: {
|
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)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
Text("We blink much less when focusing on screens. Regular blink reminders help prevent dry eyes.")
|
Text(
|
||||||
.font(.headline)
|
"We blink much less when focusing on screens. Regular blink reminders help prevent dry eyes."
|
||||||
.foregroundColor(.white)
|
)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(
|
||||||
|
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
Toggle("Enable Blink Reminders", isOn: $settingsManager.settings.blinkTimer.enabled)
|
Toggle(
|
||||||
.font(.headline)
|
"Enable Blink Reminders", isOn: $settingsManager.settings.blinkTimer.enabled
|
||||||
|
)
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
if settingsManager.settings.blinkTimer.enabled {
|
if settingsManager.settings.blinkTimer.enabled {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Remind me every:")
|
Text("Remind me every:")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Slider(
|
Slider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
get: { Double(settingsManager.settings.blinkTimer.intervalSeconds / 60) },
|
get: {
|
||||||
set: { settingsManager.settings.blinkTimer.intervalSeconds = Int($0) * 60 }
|
Double(
|
||||||
|
settingsManager.settings.blinkTimer.intervalSeconds
|
||||||
|
/ 60)
|
||||||
|
},
|
||||||
|
set: {
|
||||||
|
settingsManager.settings.blinkTimer.intervalSeconds =
|
||||||
|
Int($0) * 60
|
||||||
|
}
|
||||||
),
|
),
|
||||||
in: 1...20,
|
in: 1...20,
|
||||||
step: 1
|
step: 1
|
||||||
)
|
)
|
||||||
|
|
||||||
Text("\(settingsManager.settings.blinkTimer.intervalSeconds / 60) min")
|
Text(
|
||||||
.frame(width: 60, alignment: .trailing)
|
"\(settingsManager.settings.blinkTimer.intervalSeconds / 60) min"
|
||||||
.monospacedDigit()
|
)
|
||||||
|
.frame(width: 60, alignment: .trailing)
|
||||||
|
.monospacedDigit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,29 +85,33 @@ struct BlinkSetupView: View {
|
|||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
||||||
|
|
||||||
if settingsManager.settings.blinkTimer.enabled {
|
if settingsManager.settings.blinkTimer.enabled {
|
||||||
Text("You will be subtly reminded every \(settingsManager.settings.blinkTimer.intervalSeconds / 60) minutes to blink")
|
Text(
|
||||||
.font(.subheadline)
|
"You will be subtly reminded every \(settingsManager.settings.blinkTimer.intervalSeconds / 60) minutes to blink"
|
||||||
.foregroundColor(.secondary)
|
)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
} else {
|
} else {
|
||||||
Text("Blink reminders are currently disabled.")
|
Text("Blink reminders are currently disabled.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: showPreviewWindow) {
|
Button(action: showPreviewWindow) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "eye")
|
Image(systemName: "eye")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
Text("Preview Reminder")
|
Text("Preview Reminder")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.contentShape(RoundedRectangle(cornerRadius: 10))
|
.contentShape(RoundedRectangle(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10))
|
.glassEffectIfAvailable(
|
||||||
|
GlassStyle.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -104,7 +125,9 @@ struct BlinkSetupView: View {
|
|||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = NSScreen.main else { return }
|
||||||
previewWindowController = PreviewWindowHelper.showPreview(
|
previewWindowController = PreviewWindowHelper.showPreview(
|
||||||
on: screen,
|
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()
|
previewWindowController?.window?.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ struct CompletionView: View {
|
|||||||
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.font(.system(size: 80))
|
.font(.system(size: 80))
|
||||||
.foregroundColor(.green)
|
.foregroundStyle(.green)
|
||||||
|
|
||||||
Text("You're All Set!")
|
Text("You're All Set!")
|
||||||
.font(.system(size: 36, weight: .bold))
|
.font(.system(size: 36, weight: .bold))
|
||||||
|
|
||||||
Text("Gaze will now help you take care of your eyes and posture")
|
Text("Gaze will now help you take care of your eyes and posture")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ struct CompletionView: View {
|
|||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Image(systemName: "menubar.rectangle")
|
Image(systemName: "menubar.rectangle")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
Text("Gaze will appear in your menu bar")
|
Text("Gaze will appear in your menu bar")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@@ -41,7 +41,7 @@ struct CompletionView: View {
|
|||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Image(systemName: "clock")
|
Image(systemName: "clock")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
Text("Timers will start automatically")
|
Text("Timers will start automatically")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@@ -50,7 +50,7 @@ struct CompletionView: View {
|
|||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Image(systemName: "gearshape")
|
Image(systemName: "gearshape")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
Text("Adjust settings anytime from the menu bar")
|
Text("Adjust settings anytime from the menu bar")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@@ -59,7 +59,7 @@ struct CompletionView: View {
|
|||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Image(systemName: "plus.circle")
|
Image(systemName: "plus.circle")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
Text("Create custom timers in Settings for additional reminders")
|
Text("Create custom timers in Settings for additional reminders")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import SwiftUI
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct EnforceModeSetupView: View {
|
struct EnforceModeSetupView: View {
|
||||||
@Bindable var settingsManager: SettingsManager
|
@Bindable var settingsManager: SettingsManager
|
||||||
@@ -33,7 +33,7 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Text("Use your camera to ensure you take breaks")
|
Text("Use your camera to ensure you take breaks")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
@@ -43,7 +43,7 @@ struct EnforceModeSetupView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("Camera activates 3 seconds before lookaway reminders")
|
Text("Camera activates 3 seconds before lookaway reminders")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle(
|
Toggle(
|
||||||
@@ -149,7 +149,7 @@ struct EnforceModeSetupView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "target")
|
Image(systemName: "target")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
Text("Eye Tracking Calibration")
|
Text("Eye Tracking Calibration")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text(calibrationManager.getCalibrationSummary())
|
Text(calibrationManager.getCalibrationSummary())
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
if calibrationManager.needsRecalibration() {
|
if calibrationManager.needsRecalibration() {
|
||||||
Label(
|
Label(
|
||||||
@@ -166,17 +166,17 @@ struct EnforceModeSetupView: View {
|
|||||||
systemImage: "exclamationmark.triangle.fill"
|
systemImage: "exclamationmark.triangle.fill"
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.orange)
|
.foregroundStyle(.orange)
|
||||||
} else {
|
} else {
|
||||||
Label("Calibration active and valid", systemImage: "checkmark.circle.fill")
|
Label("Calibration active and valid", systemImage: "checkmark.circle.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.green)
|
.foregroundStyle(.green)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("Not calibrated - using default thresholds")
|
Text("Not calibrated - using default thresholds")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@@ -258,7 +258,7 @@ struct EnforceModeSetupView: View {
|
|||||||
/*? "✓ Break compliance detected" : "⚠️ Please look away from screen"*/
|
/*? "✓ Break compliance detected" : "⚠️ Please look away from screen"*/
|
||||||
/*)*/
|
/*)*/
|
||||||
/*.font(.caption)*/
|
/*.font(.caption)*/
|
||||||
/*.foregroundColor(lookingAway ? .green : .orange)*/
|
/*.foregroundStyle(lookingAway ? .green : .orange)*/
|
||||||
/*.frame(maxWidth: .infinity, alignment: .center)*/
|
/*.frame(maxWidth: .infinity, alignment: .center)*/
|
||||||
/*.padding(.top, 4)*/
|
/*.padding(.top, 4)*/
|
||||||
/*}*/
|
/*}*/
|
||||||
@@ -277,15 +277,15 @@ struct EnforceModeSetupView: View {
|
|||||||
if cameraService.isCameraAuthorized {
|
if cameraService.isCameraAuthorized {
|
||||||
Label("Authorized", systemImage: "checkmark.circle.fill")
|
Label("Authorized", systemImage: "checkmark.circle.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.green)
|
.foregroundStyle(.green)
|
||||||
} else if let error = cameraService.cameraError {
|
} else if let error = cameraService.cameraError {
|
||||||
Label(error.localizedDescription, systemImage: "exclamationmark.triangle.fill")
|
Label(error.localizedDescription, systemImage: "exclamationmark.triangle.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.orange)
|
.foregroundStyle(.orange)
|
||||||
} else {
|
} else {
|
||||||
Label("Not authorized", systemImage: "xmark.circle.fill")
|
Label("Not authorized", systemImage: "xmark.circle.fill")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,14 +337,14 @@ struct EnforceModeSetupView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "timer")
|
Image(systemName: "timer")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.orange)
|
.foregroundStyle(.orange)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("Camera Ready")
|
Text("Camera Ready")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("Will activate 3 seconds before lookaway reminder")
|
Text("Will activate 3 seconds before lookaway reminder")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -357,11 +357,11 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(isActive ? .green : .secondary)
|
.foregroundStyle(isActive ? .green : .secondary)
|
||||||
|
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
@@ -372,7 +372,7 @@ struct EnforceModeSetupView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "lock.shield.fill")
|
Image(systemName: "lock.shield.fill")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
Text("Privacy Information")
|
Text("Privacy Information")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
@@ -381,11 +381,10 @@ struct EnforceModeSetupView: View {
|
|||||||
privacyBullet("All processing happens on-device")
|
privacyBullet("All processing happens on-device")
|
||||||
privacyBullet("No images are stored or transmitted")
|
privacyBullet("No images are stored or transmitted")
|
||||||
privacyBullet("Camera only active during lookaway reminders (3 second window)")
|
privacyBullet("Camera only active during lookaway reminders (3 second window)")
|
||||||
privacyBullet("Eyes closed does not affect countdown")
|
privacyBullet("You can always force quit with cmd+q")
|
||||||
privacyBullet("You can disable at any time")
|
|
||||||
}
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(
|
.glassEffectIfAvailable(
|
||||||
@@ -396,7 +395,7 @@ struct EnforceModeSetupView: View {
|
|||||||
HStack(alignment: .top, spacing: 8) {
|
HStack(alignment: .top, spacing: 8) {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,7 +441,7 @@ struct EnforceModeSetupView: View {
|
|||||||
systemName: eyeTrackingService.enableDebugLogging
|
systemName: eyeTrackingService.enableDebugLogging
|
||||||
? "ant.circle.fill" : "ant.circle"
|
? "ant.circle.fill" : "ant.circle"
|
||||||
)
|
)
|
||||||
.foregroundColor(eyeTrackingService.enableDebugLogging ? .orange : .secondary)
|
.foregroundStyle(eyeTrackingService.enableDebugLogging ? .orange : .secondary)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.help("Toggle console debug logging")
|
.help("Toggle console debug logging")
|
||||||
@@ -461,7 +460,7 @@ struct EnforceModeSetupView: View {
|
|||||||
Text("Live Values:")
|
Text("Live Values:")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
if let leftRatio = eyeTrackingService.debugLeftPupilRatio,
|
if let leftRatio = eyeTrackingService.debugLeftPupilRatio,
|
||||||
let rightRatio = eyeTrackingService.debugRightPupilRatio
|
let rightRatio = eyeTrackingService.debugRightPupilRatio
|
||||||
@@ -470,23 +469,23 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Left Pupil: \(String(format: "%.3f", leftRatio))")
|
Text("Left Pupil: \(String(format: "%.3f", leftRatio))")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(
|
.foregroundStyle(
|
||||||
!EyeTrackingConstants.minPupilEnabled
|
!EyeTrackingConstants.minPupilEnabled
|
||||||
&& !EyeTrackingConstants.maxPupilEnabled
|
&& !EyeTrackingConstants.maxPupilEnabled
|
||||||
? .secondary
|
? .secondary
|
||||||
: (leftRatio < EyeTrackingConstants.minPupilRatio
|
: (leftRatio < EyeTrackingConstants.minPupilRatio
|
||||||
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
|
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
|
||||||
? .orange : .green
|
? Color.orange : Color.green
|
||||||
)
|
)
|
||||||
Text("Right Pupil: \(String(format: "%.3f", rightRatio))")
|
Text("Right Pupil: \(String(format: "%.3f", rightRatio))")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(
|
.foregroundStyle(
|
||||||
!EyeTrackingConstants.minPupilEnabled
|
!EyeTrackingConstants.minPupilEnabled
|
||||||
&& !EyeTrackingConstants.maxPupilEnabled
|
&& !EyeTrackingConstants.maxPupilEnabled
|
||||||
? .secondary
|
? .secondary
|
||||||
: (rightRatio < EyeTrackingConstants.minPupilRatio
|
: (rightRatio < EyeTrackingConstants.minPupilRatio
|
||||||
|| rightRatio > EyeTrackingConstants.maxPupilRatio)
|
|| 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))"
|
"Range: \(String(format: "%.2f", EyeTrackingConstants.minPupilRatio)) - \(String(format: "%.2f", EyeTrackingConstants.maxPupilRatio))"
|
||||||
)
|
)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
let bothEyesOut =
|
let bothEyesOut =
|
||||||
(leftRatio < EyeTrackingConstants.minPupilRatio
|
(leftRatio < EyeTrackingConstants.minPupilRatio
|
||||||
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
|
|| leftRatio > EyeTrackingConstants.maxPupilRatio)
|
||||||
@@ -505,13 +504,13 @@ struct EnforceModeSetupView: View {
|
|||||||
|| rightRatio > EyeTrackingConstants.maxPupilRatio)
|
|| rightRatio > EyeTrackingConstants.maxPupilRatio)
|
||||||
Text(bothEyesOut ? "Both Out ⚠️" : "In Range ✓")
|
Text(bothEyesOut ? "Both Out ⚠️" : "In Range ✓")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(bothEyesOut ? .orange : .green)
|
.foregroundStyle(bothEyesOut ? .orange : .green)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("Pupil data unavailable")
|
Text("Pupil data unavailable")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let yaw = eyeTrackingService.debugYaw,
|
if let yaw = eyeTrackingService.debugYaw,
|
||||||
@@ -521,21 +520,21 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Yaw: \(String(format: "%.3f", yaw))")
|
Text("Yaw: \(String(format: "%.3f", yaw))")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(
|
.foregroundStyle(
|
||||||
!EyeTrackingConstants.yawEnabled
|
!EyeTrackingConstants.yawEnabled
|
||||||
? .secondary
|
? .secondary
|
||||||
: abs(yaw) > EyeTrackingConstants.yawThreshold
|
: abs(yaw) > EyeTrackingConstants.yawThreshold
|
||||||
? .orange : .green
|
? Color.orange : Color.green
|
||||||
)
|
)
|
||||||
Text("Pitch: \(String(format: "%.3f", pitch))")
|
Text("Pitch: \(String(format: "%.3f", pitch))")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(
|
.foregroundStyle(
|
||||||
!EyeTrackingConstants.pitchUpEnabled
|
!EyeTrackingConstants.pitchUpEnabled
|
||||||
&& !EyeTrackingConstants.pitchDownEnabled
|
&& !EyeTrackingConstants.pitchDownEnabled
|
||||||
? .secondary
|
? .secondary
|
||||||
: (pitch > EyeTrackingConstants.pitchUpThreshold
|
: (pitch > EyeTrackingConstants.pitchUpThreshold
|
||||||
|| pitch < EyeTrackingConstants.pitchDownThreshold)
|
|| pitch < EyeTrackingConstants.pitchDownThreshold)
|
||||||
? .orange : .green
|
? Color.orange : Color.green
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,12 +545,12 @@ struct EnforceModeSetupView: View {
|
|||||||
"Yaw Max: \(String(format: "%.2f", EyeTrackingConstants.yawThreshold))"
|
"Yaw Max: \(String(format: "%.2f", EyeTrackingConstants.yawThreshold))"
|
||||||
)
|
)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text(
|
Text(
|
||||||
"Pitch: \(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) to \(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold))"
|
"Pitch: \(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) to \(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold))"
|
||||||
)
|
)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,48 +564,54 @@ struct EnforceModeSetupView: View {
|
|||||||
Text("Current Threshold Values:")
|
Text("Current Threshold Values:")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Yaw Threshold:")
|
Text("Yaw Threshold:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.2f", EyeTrackingConstants.yawThreshold)) rad")
|
Text("\(String(format: "%.2f", EyeTrackingConstants.yawThreshold)) rad")
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Pitch Up Threshold:")
|
Text("Pitch Up Threshold:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold)) rad")
|
Text(
|
||||||
.foregroundColor(.secondary)
|
"\(String(format: "%.2f", EyeTrackingConstants.pitchUpThreshold)) rad"
|
||||||
|
)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Pitch Down Threshold:")
|
Text("Pitch Down Threshold:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) rad")
|
Text(
|
||||||
.foregroundColor(.secondary)
|
"\(String(format: "%.2f", EyeTrackingConstants.pitchDownThreshold)) rad"
|
||||||
|
)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Min Pupil Ratio:")
|
Text("Min Pupil Ratio:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.2f", EyeTrackingConstants.minPupilRatio))")
|
Text("\(String(format: "%.2f", EyeTrackingConstants.minPupilRatio))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Max Pupil Ratio:")
|
Text("Max Pupil Ratio:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.2f", EyeTrackingConstants.maxPupilRatio))")
|
Text("\(String(format: "%.2f", EyeTrackingConstants.maxPupilRatio))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Eye Closed Threshold:")
|
Text("Eye Closed Threshold:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(format: "%.3f", EyeTrackingConstants.eyeClosedThreshold))")
|
Text(
|
||||||
.foregroundColor(.secondary)
|
"\(String(format: "%.3f", EyeTrackingConstants.eyeClosedThreshold))"
|
||||||
|
)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
@@ -622,7 +627,7 @@ struct EnforceModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Debug Eye Tracking Data")
|
Text("Debug Eye Tracking Data")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text("Face Detected: \(eyeTrackingService.faceDetected ? "Yes" : "No")")
|
Text("Face Detected: \(eyeTrackingService.faceDetected ? "Yes" : "No")")
|
||||||
@@ -643,7 +648,7 @@ struct EnforceModeSetupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
||||||
|
|||||||
@@ -14,26 +14,28 @@ struct GeneralSetupView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
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()
|
Spacer()
|
||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Text("Configure app preferences and support the project")
|
Text("Configure app preferences and support the project")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
launchAtLoginToggle
|
launchAtLoginToggle
|
||||||
|
|
||||||
#if !APPSTORE
|
#if !APPSTORE
|
||||||
softwareUpdatesSection
|
softwareUpdatesSection
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
subtleReminderSizeSection
|
subtleReminderSizeSection
|
||||||
|
|
||||||
#if !APPSTORE
|
#if !APPSTORE
|
||||||
supportSection
|
supportSection
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ struct GeneralSetupView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("Start Gaze automatically when you log in")
|
Text("Start Gaze automatically when you log in")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle("", isOn: $settingsManager.settings.launchAtLogin)
|
Toggle("", isOn: $settingsManager.settings.launchAtLogin)
|
||||||
@@ -65,42 +67,45 @@ struct GeneralSetupView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !APPSTORE
|
#if !APPSTORE
|
||||||
private var softwareUpdatesSection: some View {
|
private var softwareUpdatesSection: some View {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("Software Updates")
|
Text("Software Updates")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
||||||
if let lastCheck = updateManager.lastUpdateCheckDate {
|
if let lastCheck = updateManager.lastUpdateCheckDate {
|
||||||
Text("Last checked: \(lastCheck, style: .relative)")
|
Text("Last checked: \(lastCheck, style: .relative)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.italic()
|
.italic()
|
||||||
} else {
|
} else {
|
||||||
Text("Never checked for updates")
|
Text("Never checked for updates")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.italic()
|
.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")
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
Spacer()
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private var subtleReminderSizeSection: some View {
|
private var subtleReminderSizeSection: some View {
|
||||||
@@ -110,20 +115,28 @@ struct GeneralSetupView: View {
|
|||||||
|
|
||||||
Text("Adjust the size of blink and posture reminders")
|
Text("Adjust the size of blink and posture reminders")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
ForEach(ReminderSize.allCases, id: \.self) { size in
|
ForEach(ReminderSize.allCases, id: \.self) { size in
|
||||||
Button(action: { settingsManager.settings.subtleReminderSize = size }) {
|
Button(action: { settingsManager.settings.subtleReminderSize = size }) {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Circle()
|
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))
|
.frame(width: iconSize(for: size), height: iconSize(for: size))
|
||||||
|
|
||||||
Text(size.displayName)
|
Text(size.displayName)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(settingsManager.settings.subtleReminderSize == size ? .semibold : .regular)
|
.fontWeight(
|
||||||
.foregroundColor(settingsManager.settings.subtleReminderSize == size ? .primary : .secondary)
|
settingsManager.settings.subtleReminderSize == size
|
||||||
|
? .semibold : .regular
|
||||||
|
)
|
||||||
|
.foregroundStyle(
|
||||||
|
settingsManager.settings.subtleReminderSize == size
|
||||||
|
? .primary : .secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, minHeight: 60)
|
.frame(maxWidth: .infinity, minHeight: 60)
|
||||||
.padding(.vertical, 12)
|
.padding(.vertical, 12)
|
||||||
@@ -142,31 +155,31 @@ struct GeneralSetupView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !APPSTORE
|
#if !APPSTORE
|
||||||
private var supportSection: some View {
|
private var supportSection: some View {
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
Text("Support & Contribute")
|
Text("Support & Contribute")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
ExternalLinkButton(
|
ExternalLinkButton(
|
||||||
icon: "chevron.left.forwardslash.chevron.right",
|
icon: "chevron.left.forwardslash.chevron.right",
|
||||||
title: "View on GitHub",
|
title: "View on GitHub",
|
||||||
subtitle: "Star the repo, report issues, contribute",
|
subtitle: "Star the repo, report issues, contribute",
|
||||||
url: "https://github.com/mikefreno/Gaze",
|
url: "https://github.com/mikefreno/Gaze",
|
||||||
tint: nil
|
tint: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
ExternalLinkButton(
|
ExternalLinkButton(
|
||||||
icon: "cup.and.saucer.fill",
|
icon: "cup.and.saucer.fill",
|
||||||
iconColor: .brown,
|
iconColor: .brown,
|
||||||
title: "Buy Me a Coffee",
|
title: "Buy Me a Coffee",
|
||||||
subtitle: "Support development of Gaze",
|
subtitle: "Support development of Gaze",
|
||||||
url: "https://buymeacoffee.com/mikefreno",
|
url: "https://buymeacoffee.com/mikefreno",
|
||||||
tint: .orange
|
tint: .orange
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private func applyLaunchAtLoginSetting(enabled: Bool) {
|
private func applyLaunchAtLoginSetting(enabled: Bool) {
|
||||||
@@ -205,14 +218,14 @@ struct ExternalLinkButton: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(iconColor)
|
.foregroundStyle(iconColor)
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Text(subtitle)
|
Text(subtitle)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Image(systemName: "arrow.up.right")
|
Image(systemName: "arrow.up.right")
|
||||||
@@ -224,7 +237,8 @@ struct ExternalLinkButton: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.glassEffectIfAvailable(
|
.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)
|
in: .rect(cornerRadius: 10)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,21 +21,27 @@ struct PostureSetupView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Button(action: {
|
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)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
Text("Regular posture checks help prevent back and neck pain from prolonged sitting")
|
Text(
|
||||||
.font(.headline)
|
"Regular posture checks help prevent back and neck pain from prolonged sitting"
|
||||||
.foregroundColor(.white)
|
)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(
|
||||||
|
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
|
||||||
|
|
||||||
SliderSection(
|
SliderSection(
|
||||||
intervalSettings: Binding(
|
intervalSettings: Binding(
|
||||||
@@ -46,7 +52,8 @@ struct PostureSetupView: View {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
settingsManager.settings.postureTimer.intervalSeconds = (newValue.val ?? 30) * 60
|
settingsManager.settings.postureTimer.intervalSeconds =
|
||||||
|
(newValue.val ?? 30) * 60
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
countdownSettings: nil,
|
countdownSettings: nil,
|
||||||
@@ -67,7 +74,9 @@ struct PostureSetupView: View {
|
|||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = NSScreen.main else { return }
|
||||||
previewWindowController = PreviewWindowHelper.showPreview(
|
previewWindowController = PreviewWindowHelper.showPreview(
|
||||||
on: screen,
|
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()
|
previewWindowController?.window?.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct SmartModeSetupView: View {
|
|||||||
|
|
||||||
Text("Automatically manage timers based on your activity")
|
Text("Automatically manage timers based on your activity")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.padding(.bottom, 30)
|
.padding(.bottom, 30)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -42,18 +42,21 @@ struct SmartModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
||||||
.foregroundColor(.blue)
|
.foregroundStyle(.blue)
|
||||||
Text("Auto-pause on Fullscreen")
|
Text("Auto-pause on Fullscreen")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
Text("Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)")
|
Text(
|
||||||
.font(.caption)
|
"Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)"
|
||||||
.foregroundColor(.secondary)
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnFullscreen)
|
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnFullscreen)
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.onChange(of: settingsManager.settings.smartMode.autoPauseOnFullscreen) { _, newValue in
|
.onChange(of: settingsManager.settings.smartMode.autoPauseOnFullscreen) {
|
||||||
|
_, newValue in
|
||||||
if newValue {
|
if newValue {
|
||||||
permissionManager.requestAuthorizationIfNeeded()
|
permissionManager.requestAuthorizationIfNeeded()
|
||||||
}
|
}
|
||||||
@@ -61,7 +64,8 @@ struct SmartModeSetupView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settingsManager.settings.smartMode.autoPauseOnFullscreen,
|
if settingsManager.settings.smartMode.autoPauseOnFullscreen,
|
||||||
permissionManager.authorizationStatus != .authorized {
|
permissionManager.authorizationStatus != .authorized
|
||||||
|
{
|
||||||
permissionWarningView
|
permissionWarningView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +85,7 @@ struct SmartModeSetupView: View {
|
|||||||
|
|
||||||
Text("macOS requires Screen Recording permission to detect other apps in fullscreen.")
|
Text("macOS requires Screen Recording permission to detect other apps in fullscreen.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button("Grant Access") {
|
Button("Grant Access") {
|
||||||
@@ -107,13 +111,13 @@ struct SmartModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "moon.zzz.fill")
|
Image(systemName: "moon.zzz.fill")
|
||||||
.foregroundColor(.indigo)
|
.foregroundStyle(.indigo)
|
||||||
Text("Auto-pause on Idle")
|
Text("Auto-pause on Idle")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
Text("Timers will pause when you're inactive for more than the threshold below")
|
Text("Timers will pause when you're inactive for more than the threshold below")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnIdle)
|
Toggle("", isOn: $settingsManager.settings.smartMode.autoPauseOnIdle)
|
||||||
@@ -139,13 +143,15 @@ struct SmartModeSetupView: View {
|
|||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "chart.line.uptrend.xyaxis")
|
Image(systemName: "chart.line.uptrend.xyaxis")
|
||||||
.foregroundColor(.green)
|
.foregroundStyle(.green)
|
||||||
Text("Track Usage Statistics")
|
Text("Track Usage Statistics")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
Text("Monitor active and idle time, with automatic reset after the specified duration")
|
Text(
|
||||||
.font(.caption)
|
"Monitor active and idle time, with automatic reset after the specified duration"
|
||||||
.foregroundColor(.secondary)
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle("", isOn: $settingsManager.settings.smartMode.trackUsage)
|
Toggle("", isOn: $settingsManager.settings.smartMode.trackUsage)
|
||||||
@@ -182,7 +188,7 @@ struct ThresholdSlider: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text("\(value) \(unit)")
|
Text("\(value) \(unit)")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider(
|
Slider(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct UserTimersView: View {
|
|||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Image(systemName: "clock.badge.checkmark")
|
Image(systemName: "clock.badge.checkmark")
|
||||||
.font(.system(size: 60))
|
.font(.system(size: 60))
|
||||||
.foregroundColor(.purple)
|
.foregroundStyle(.purple)
|
||||||
Text("Custom Timers")
|
Text("Custom Timers")
|
||||||
.font(.system(size: 28, weight: .bold))
|
.font(.system(size: 28, weight: .bold))
|
||||||
}
|
}
|
||||||
@@ -28,14 +28,14 @@ struct UserTimersView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Text("Create your own reminder schedules")
|
Text("Create your own reminder schedules")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
Text("Add up to 3 custom timers with your own intervals and messages")
|
Text("Add up to 3 custom timers with your own intervals and messages")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(
|
.glassEffectIfAvailable(
|
||||||
@@ -66,13 +66,13 @@ struct UserTimersView: View {
|
|||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
Image(systemName: "clock.badge.questionmark")
|
Image(systemName: "clock.badge.questionmark")
|
||||||
.font(.system(size: 40))
|
.font(.system(size: 40))
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text("No custom timers yet")
|
Text("No custom timers yet")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text("Click 'Add Timer' to create your first custom reminder")
|
Text("Click 'Add Timer' to create your first custom reminder")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(40)
|
.padding(40)
|
||||||
@@ -153,7 +153,7 @@ struct UserTimerRow: View {
|
|||||||
.frame(width: 12, height: 12)
|
.frame(width: 12, height: 12)
|
||||||
|
|
||||||
Image(systemName: timer.type == .subtle ? "eye.circle" : "rectangle.on.rectangle")
|
Image(systemName: timer.type == .subtle ? "eye.circle" : "rectangle.on.rectangle")
|
||||||
.foregroundColor(timer.color)
|
.foregroundStyle(timer.color)
|
||||||
.frame(width: 24)
|
.frame(width: 24)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
@@ -165,7 +165,7 @@ struct UserTimerRow: View {
|
|||||||
"\(timer.type.displayName) • \(timer.timeOnScreenSeconds)s on screen • \(timer.intervalMinutes) min interval"
|
"\(timer.type.displayName) • \(timer.timeOnScreenSeconds)s on screen • \(timer.intervalMinutes) min interval"
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -179,14 +179,14 @@ struct UserTimerRow: View {
|
|||||||
Button(action: onEdit) {
|
Button(action: onEdit) {
|
||||||
Image(systemName: "pencil.circle.fill")
|
Image(systemName: "pencil.circle.fill")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
Button(action: { showingDeleteConfirmation = true }) {
|
Button(action: { showingDeleteConfirmation = true }) {
|
||||||
Image(systemName: "trash.circle.fill")
|
Image(systemName: "trash.circle.fill")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.confirmationDialog("Delete Timer", isPresented: $showingDeleteConfirmation) {
|
.confirmationDialog("Delete Timer", isPresented: $showingDeleteConfirmation) {
|
||||||
@@ -264,7 +264,7 @@ struct UserTimerEditSheet: View {
|
|||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
Text("Example: \"Stretch Break\", \"Eye Rest\", \"Water Break\"")
|
Text("Example: \"Stretch Break\", \"Eye Rest\", \"Water Break\"")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
@@ -318,7 +318,7 @@ struct UserTimerEditSheet: View {
|
|||||||
: "Full screen reminder with animation"
|
: "Full screen reminder with animation"
|
||||||
)
|
)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if type == .overlay {
|
if type == .overlay {
|
||||||
@@ -359,7 +359,7 @@ struct UserTimerEditSheet: View {
|
|||||||
}
|
}
|
||||||
Text("How often this reminder will appear (in minutes)")
|
Text("How often this reminder will appear (in minutes)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
@@ -369,7 +369,7 @@ struct UserTimerEditSheet: View {
|
|||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
Text("Leave blank to show a default timer notification")
|
Text("Leave blank to show a default timer notification")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ struct WelcomeView: View {
|
|||||||
|
|
||||||
Image(systemName: "eye.fill")
|
Image(systemName: "eye.fill")
|
||||||
.font(.system(size: 80))
|
.font(.system(size: 80))
|
||||||
.foregroundColor(.accentColor)
|
.foregroundStyle(Color.accentColor)
|
||||||
|
|
||||||
Text("Welcome to Gaze")
|
Text("Welcome to Gaze")
|
||||||
.font(.system(size: 36, weight: .bold))
|
.font(.system(size: 36, weight: .bold))
|
||||||
|
|
||||||
Text("Take care of your eyes and posture")
|
Text("Take care of your eyes and posture")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
FeatureRow(
|
FeatureRow(
|
||||||
@@ -66,7 +66,7 @@ struct FeatureRow: View {
|
|||||||
HStack(alignment: .top, spacing: 16) {
|
HStack(alignment: .top, spacing: 16) {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(iconColor)
|
.foregroundStyle(iconColor)
|
||||||
.frame(width: 30)
|
.frame(width: 30)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
@@ -74,7 +74,7 @@ struct FeatureRow: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text(description)
|
Text(description)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user