general: cleanup new page

This commit is contained in:
Michael Freno
2026-01-19 13:49:06 -05:00
parent 1fc1d00b1e
commit ac6287bc82
2 changed files with 353 additions and 144 deletions

View File

@@ -7,160 +7,369 @@
import SwiftUI import SwiftUI
struct OnboardingVisualEffectView: NSViewRepresentable {
let material: NSVisualEffectView.Material
let blendingMode: NSVisualEffectView.BlendingMode
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
view.material = material
view.blendingMode = blendingMode
view.state = .active
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
nsView.material = material
nsView.blendingMode = blendingMode
}
}
struct AdditionalModifiersView: View { struct AdditionalModifiersView: View {
@Bindable var settingsManager: SettingsManager @Bindable var settingsManager: SettingsManager
@State private var frontCardIndex: Int = 0
@State private var dragOffset: CGFloat = 0
@State private var isDragging: Bool = false
private let cardWidth: CGFloat = 480
private let cardHeight: CGFloat = 480
private let backCardOffset: CGFloat = 30
private let backCardScale: CGFloat = 0.92
var body: some View { var body: some View {
ZStack { VStack(spacing: 0) {
OnboardingVisualEffectView(material: .hudWindow, blendingMode: .behindWindow) SetupHeader(icon: "slider.horizontal.3", title: "Additional Options", color: .purple)
.ignoresSafeArea()
VStack(spacing: 0) { Text("Optional features to enhance your experience")
// Header .font(.title3)
HStack { .foregroundStyle(.secondary)
Text("Additional Modifiers") .multilineTextAlignment(.center)
.padding(.bottom, 20)
Spacer()
// Card stack
ZStack {
// Card 0 (Enforce Mode)
cardView(for: 0)
.zIndex(zIndex(for: 0))
.scaleEffect(scale(for: 0))
.offset(x: xOffset(for: 0), y: yOffset(for: 0))
.opacity(opacity(for: 0))
// Card 1 (Smart Mode)
cardView(for: 1)
.zIndex(zIndex(for: 1))
.scaleEffect(scale(for: 1))
.offset(x: xOffset(for: 1), y: yOffset(for: 1))
.opacity(opacity(for: 1))
}
.gesture(dragGesture)
Spacer()
// Navigation controls
HStack(spacing: 20) {
Button(action: { swapCards() }) {
Image(systemName: "chevron.left")
.font(.title2) .font(.title2)
.fontWeight(.semibold) .frame(width: 44, height: 44)
Spacer()
} }
.padding(.horizontal, 40) .buttonStyle(.plain)
.padding(.top, 20) .glassEffectIfAvailable(GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
.disabled(frontCardIndex == 0)
.opacity(frontCardIndex == 0 ? 0.4 : 1)
Spacer() // Page indicators with labels
HStack(spacing: 16) {
cardIndicator(index: 0, icon: "video.fill", label: "Enforce")
cardIndicator(index: 1, icon: "brain.fill", label: "Smart")
}
// Main content area with stacking effect Button(action: { swapCards() }) {
HStack(spacing: 0) { Image(systemName: "chevron.right")
// Smart Mode Card (stacked behind, 50% width, offset 10% from right) .font(.title2)
ZStack { .frame(width: 44, height: 44)
// Background card with shadow and rounded corners }
RoundedRectangle(cornerRadius: 16) .buttonStyle(.plain)
.fill(Color(NSColor.windowBackgroundColor).opacity(0.8)) .glassEffectIfAvailable(GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) .disabled(frontCardIndex == 1)
.frame(width: 500, height: 500) // 50% of 1000px width .opacity(frontCardIndex == 1 ? 0.4 : 1)
.offset(x: 100) // 10% offset from right (1000 * 0.1 = 100) }
.padding(.bottom, 10)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.background(.clear)
}
// Smart mode content // MARK: - Card Indicator
SmartModeSetupView(settingsManager: settingsManager)
.padding(20)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.zIndex(0)
// Enforce Mode Card (in front) @ViewBuilder
VStack(spacing: 24) { private func cardIndicator(index: Int, icon: String, label: String) -> some View {
SetupHeader(icon: "video.fill", title: "Enforce Mode", color: .accentColor) Button(action: {
if index != frontCardIndex {
swapCards()
}
}) {
HStack(spacing: 6) {
Image(systemName: icon)
.font(.caption)
Text(label)
.font(.caption)
.fontWeight(.medium)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.foregroundStyle(index == frontCardIndex ? .primary : .secondary)
}
.buttonStyle(.plain)
.glassEffectIfAvailable(
index == frontCardIndex
? GlassStyle.regular.tint(Color.accentColor.opacity(0.3))
: GlassStyle.regular,
in: .capsule
)
}
Text("Use your camera to ensure you take breaks") // MARK: - Card Transform Calculations
.font(.title3)
private func zIndex(for cardIndex: Int) -> Double {
let isFront = cardIndex == frontCardIndex
let dragProgress = abs(dragOffset) / 150
if isDragging && dragProgress > 0.3 {
return isFront ? 0 : 1
}
return isFront ? 1 : 0
}
private func scale(for cardIndex: Int) -> CGFloat {
let isFront = cardIndex == frontCardIndex
let dragProgress = min(abs(dragOffset) / 150, 1.0)
if isFront {
return 1.0 - (dragProgress * (1.0 - backCardScale))
} else {
return backCardScale + (dragProgress * (1.0 - backCardScale))
}
}
private func xOffset(for cardIndex: Int) -> CGFloat {
let isFront = cardIndex == frontCardIndex
let dragProgress = min(abs(dragOffset) / 150, 1.0)
let backPeekX = backCardOffset
if isFront {
return dragOffset + (dragProgress * backPeekX * (dragOffset > 0 ? -1 : 1))
} else {
return backPeekX * (1.0 - dragProgress)
}
}
private func yOffset(for cardIndex: Int) -> CGFloat {
let isFront = cardIndex == frontCardIndex
let dragProgress = min(abs(dragOffset) / 150, 1.0)
let backPeekY: CGFloat = 15
if isFront {
return dragProgress * backPeekY
} else {
return backPeekY * (1.0 - dragProgress)
}
}
private func opacity(for cardIndex: Int) -> CGFloat {
let isFront = cardIndex == frontCardIndex
let dragProgress = min(abs(dragOffset) / 150, 1.0)
if isFront {
return 1.0 - (dragProgress * 0.3)
} else {
return 0.7 + (dragProgress * 0.3)
}
}
// MARK: - Card Views
@ViewBuilder
private func cardView(for index: Int) -> some View {
ZStack {
RoundedRectangle(cornerRadius: 16)
.fill(Color(NSColor.windowBackgroundColor).opacity(0.8))
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4)
Group {
if index == 0 {
enforceModeContent
} else {
smartModeContent
}
}
.padding(20)
}
.frame(width: cardWidth, height: cardHeight)
}
private var enforceModeContent: some View {
VStack(spacing: 16) {
Image(systemName: "video.fill")
.font(.system(size: 40))
.foregroundStyle(Color.accentColor)
Text("Enforce Mode")
.font(.title2)
.fontWeight(.bold)
Text("Use your camera to ensure you take breaks")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Spacer()
VStack(spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Enable Enforce Mode")
.font(.headline)
Text("Camera activates before lookaway reminders")
.font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) }
.padding(.bottom, 10) Spacer()
Toggle("", isOn: $settingsManager.settings.enforcementMode)
.labelsHidden()
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
VStack(spacing: 20) { HStack {
HStack { VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 4) { Text("Camera Access")
Text("Enable Enforce Mode") .font(.headline)
.font(.headline)
Text("Camera activates 3 seconds before lookaway reminders")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Toggle(
"",
isOn: Binding(
get: {
settingsManager.settings.enforcementMode
},
set: { newValue in
print("🎛️ Toggle changed to: \(newValue)")
settingsManager.settings.enforcementMode = newValue
}
)
)
.labelsHidden()
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
// Camera access status display if CameraAccessService.shared.isCameraAuthorized {
HStack { Label("Authorized", systemImage: "checkmark.circle.fill")
VStack(alignment: .leading, spacing: 4) { .font(.caption)
Text("Camera Access") .foregroundStyle(.green)
.font(.headline) } else if let error = CameraAccessService.shared.cameraError {
Label(error.localizedDescription, systemImage: "exclamationmark.triangle.fill")
if CameraAccessService.shared.isCameraAuthorized { .font(.caption)
Label("Authorized", systemImage: "checkmark.circle.fill") .foregroundStyle(.orange)
.font(.caption) } else {
.foregroundStyle(.green) Label("Not authorized", systemImage: "xmark.circle.fill")
} else if let error = CameraAccessService.shared.cameraError { .font(.caption)
Label(error.localizedDescription, systemImage: "exclamationmark.triangle.fill") .foregroundStyle(.secondary)
.font(.caption)
.foregroundStyle(.orange)
} else {
Label("Not authorized", systemImage: "xmark.circle.fill")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
if !CameraAccessService.shared.isCameraAuthorized {
Button("Request Access") {
print("📷 Request Access button clicked")
Task { @MainActor in
do {
try await CameraAccessService.shared.requestCameraAccess()
print("✓ Camera access granted via button")
} catch {
print("⚠️ Camera access failed: \(error.localizedDescription)")
}
}
}
.buttonStyle(.bordered)
}
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
} }
} }
.frame(width: 500) // 50% width
.zIndex(1)
}
Spacer() Spacer()
if !CameraAccessService.shared.isCameraAuthorized {
Button("Request Access") {
Task { @MainActor in
do {
try await CameraAccessService.shared.requestCameraAccess()
} catch {
print("Camera access failed: \(error.localizedDescription)")
}
}
}
.buttonStyle(.bordered)
}
}
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
} }
Spacer()
}
}
private var smartModeContent: some View {
VStack(spacing: 16) {
Image(systemName: "brain.fill")
.font(.system(size: 40))
.foregroundStyle(.purple)
Text("Smart Mode")
.font(.title2)
.fontWeight(.bold)
Text("Automatically manage timers based on activity")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Spacer()
VStack(spacing: 12) {
smartModeToggle(
icon: "arrow.up.left.and.arrow.down.right",
iconColor: .blue,
title: "Auto-pause on Fullscreen",
subtitle: "Pause during videos, games, presentations",
isOn: $settingsManager.settings.smartMode.autoPauseOnFullscreen
)
smartModeToggle(
icon: "moon.zzz.fill",
iconColor: .indigo,
title: "Auto-pause on Idle",
subtitle: "Pause when you're inactive",
isOn: $settingsManager.settings.smartMode.autoPauseOnIdle
)
smartModeToggle(
icon: "chart.line.uptrend.xyaxis",
iconColor: .green,
title: "Track Usage Statistics",
subtitle: "Monitor active and idle time",
isOn: $settingsManager.settings.smartMode.trackUsage
)
}
Spacer()
}
}
@ViewBuilder
private func smartModeToggle(icon: String, iconColor: Color, title: String, subtitle: String, isOn: Binding<Bool>) -> some View {
HStack {
Image(systemName: icon)
.foregroundStyle(iconColor)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
Text(subtitle)
.font(.caption2)
.foregroundStyle(.secondary)
}
Spacer()
Toggle("", isOn: isOn)
.labelsHidden()
.controlSize(.small)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 10))
}
// MARK: - Gestures & Navigation
private var dragGesture: some Gesture {
DragGesture()
.onChanged { value in
isDragging = true
dragOffset = value.translation.width
}
.onEnded { value in
let threshold: CGFloat = 80
let shouldSwap = abs(value.translation.width) > threshold ||
abs(value.predictedEndTranslation.width) > 150
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
if shouldSwap {
frontCardIndex = 1 - frontCardIndex
}
dragOffset = 0
isDragging = false
}
}
}
private func swapCards() {
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
frontCardIndex = 1 - frontCardIndex
} }
.frame(
minWidth: 1000,
minHeight: {
#if APPSTORE
return 700
#else
return 1000
#endif
}()
)
} }
} }

View File

@@ -153,13 +153,13 @@ struct OnboardingContainerView: View {
.tag(4) .tag(4)
.tabItem { Image(systemName: "figure.stand") } .tabItem { Image(systemName: "figure.stand") }
GeneralSetupView(settingsManager: settingsManager, isOnboarding: true)
.tag(5)
.tabItem { Image(systemName: "gearshape.fill") }
AdditionalModifiersView(settingsManager: settingsManager) AdditionalModifiersView(settingsManager: settingsManager)
.tag(5)
.tabItem { Image(systemName: "slider.horizontal.3") }
GeneralSetupView(settingsManager: settingsManager, isOnboarding: true)
.tag(6) .tag(6)
.tabItem { Image(systemName: "plus.circle.fill") } .tabItem { Image(systemName: "gearshape.fill") }
CompletionView() CompletionView()
.tag(7) .tag(7)