import SwiftUI import KeyboardShortcuts struct MetricsSetupView: View { @EnvironmentObject private var whisperState: WhisperState @State private var isAccessibilityEnabled = AXIsProcessTrusted() @State private var isScreenRecordingEnabled = CGPreflightScreenCaptureAccess() var body: some View { GeometryReader { geometry in ScrollView { VStack(spacing: geometry.size.height * 0.05) { // Header VStack(spacing: geometry.size.height * 0.02) { AppIconView(size: min(90, geometry.size.width * 0.15), cornerRadius: 22) VStack(spacing: geometry.size.height * 0.01) { Text("Welcome to VoiceInk") .font(.system(size: min(32, geometry.size.width * 0.05), weight: .bold, design: .rounded)) .multilineTextAlignment(.center) Text("Complete the setup to get started") .font(.system(size: min(16, geometry.size.width * 0.025))) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } .padding(.top, geometry.size.height * 0.03) // Setup Steps VStack(spacing: geometry.size.height * 0.02) { ForEach(0..<4) { index in setupStep(for: index, geometry: geometry) } } .padding(.horizontal, geometry.size.width * 0.03) Spacer(minLength: geometry.size.height * 0.02) // Action Button actionButton .frame(maxWidth: min(600, geometry.size.width * 0.8)) // Help Text helpText .padding(.bottom, geometry.size.height * 0.03) } .padding(.horizontal, geometry.size.width * 0.05) .frame(minHeight: geometry.size.height) .background { Color(.controlBackgroundColor) } } } .frame(minWidth: 500, minHeight: 400) } private func setupStep(for index: Int, geometry: GeometryProxy) -> some View { let isCompleted: Bool let icon: String let title: String let description: String switch index { case 0: isCompleted = KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) != nil icon = "command" title = "Set Keyboard Shortcut" description = "Set up a keyboard shortcut to use VoiceInk anywhere" case 1: isCompleted = isAccessibilityEnabled icon = "hand.raised" title = "Enable Accessibility" description = "Allow VoiceInk to paste transcribed text directly at your cursor position" case 2: isCompleted = isScreenRecordingEnabled icon = "video" title = "Enable Screen Recording" description = "Allow VoiceInk to understand context from your screen for transcript Enhancement" default: isCompleted = whisperState.currentModel != nil icon = "arrow.down" title = "Download Model" description = "Choose and download an AI model" } return HStack(spacing: geometry.size.width * 0.03) { // Status Icon ZStack { Circle() .fill(isCompleted ? Color(nsColor: .controlAccentColor).opacity(0.15) : Color(nsColor: .systemRed).opacity(0.15)) .frame(width: min(44, geometry.size.width * 0.08), height: min(44, geometry.size.width * 0.08)) Image(systemName: "\(icon).circle") .font(.system(size: min(24, geometry.size.width * 0.04), weight: .medium)) .foregroundColor(isCompleted ? Color(nsColor: .controlAccentColor) : Color(nsColor: .systemRed)) .symbolRenderingMode(.hierarchical) } // Text VStack(alignment: .leading, spacing: 4) { Text(title) .font(.system(size: min(16, geometry.size.width * 0.025), weight: .semibold)) Text(description) .font(.system(size: min(14, geometry.size.width * 0.022))) .foregroundColor(.secondary) } Spacer() // Status indicator if isCompleted { Image(systemName: "checkmark.seal.fill") .font(.system(size: min(26, geometry.size.width * 0.045), weight: .semibold)) .foregroundColor(Color.green.opacity(0.95)) .symbolRenderingMode(.hierarchical) } else { Circle() .stroke(Color(nsColor: .systemRed), lineWidth: 2) .frame(width: min(24, geometry.size.width * 0.04), height: min(24, geometry.size.width * 0.04)) } } .padding(.horizontal, geometry.size.width * 0.03) .padding(.vertical, geometry.size.height * 0.02) .background( RoundedRectangle(cornerRadius: 16) .fill(Color(.windowBackgroundColor)) .shadow( color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4 ) ) } private var actionButton: some View { Button(action: { if isShortcutAndAccessibilityGranted { openModelManagement() } else { // Handle different permission requests based on which one is missing if KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) == nil { openSettings() } else if !AXIsProcessTrusted() { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { NSWorkspace.shared.open(url) } } else if !CGPreflightScreenCaptureAccess() { CGRequestScreenCaptureAccess() // After requesting, open system preferences as fallback if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") { NSWorkspace.shared.open(url) } } } }) { HStack(spacing: 8) { Text(isShortcutAndAccessibilityGranted ? "Download Model" : getActionButtonTitle()) Image(systemName: "arrow.right") .font(.system(size: 14, weight: .semibold)) } .font(.headline) .frame(maxWidth: .infinity) .frame(height: 50) .background( LinearGradient( colors: [ Color(nsColor: .controlAccentColor), Color(nsColor: .controlAccentColor).opacity(0.8) ], startPoint: .leading, endPoint: .trailing ) ) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: 14)) } .buttonStyle(.plain) .shadow( color: Color(nsColor: .controlAccentColor).opacity(0.3), radius: 10, y: 5 ) } private func getActionButtonTitle() -> String { if KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) == nil { return "Configure Shortcut" } else if !AXIsProcessTrusted() { return "Enable Accessibility" } else if !CGPreflightScreenCaptureAccess() { return "Enable Screen Recording" } return "Open Settings" } private var helpText: some View { Text("Need help? Check the Help menu for support options") .font(.system(size: 13)) .foregroundColor(.secondary) } private var isShortcutAndAccessibilityGranted: Bool { KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) != nil && AXIsProcessTrusted() && CGPreflightScreenCaptureAccess() } private func openSettings() { NotificationCenter.default.post( name: .navigateToDestination, object: nil, userInfo: ["destination": "Settings"] ) } private func openModelManagement() { NotificationCenter.default.post( name: .navigateToDestination, object: nil, userInfo: ["destination": "AI Models"] ) } }