import SwiftUI import AVFoundation import AppKit import KeyboardShortcuts struct OnboardingPermission: Identifiable { let id = UUID() let title: String let description: String let icon: String let type: PermissionType enum PermissionType { case microphone case audioDeviceSelection case accessibility case screenRecording case keyboardShortcut var systemName: String { switch self { case .microphone: return "mic" case .audioDeviceSelection: return "headphones" case .accessibility: return "accessibility" case .screenRecording: return "rectangle.inset.filled.and.person.filled" case .keyboardShortcut: return "keyboard" } } } } struct OnboardingPermissionsView: View { @Binding var hasCompletedOnboarding: Bool @EnvironmentObject private var hotkeyManager: HotkeyManager @ObservedObject private var audioDeviceManager = AudioDeviceManager.shared @State private var currentPermissionIndex = 0 @State private var permissionStates: [Bool] = [false, false, false, false, false] @State private var showAnimation = false @State private var scale: CGFloat = 0.8 @State private var opacity: CGFloat = 0 @State private var showModelDownload = false private let permissions: [OnboardingPermission] = [ OnboardingPermission( title: "Microphone Access", description: "Enable your microphone to start speaking and converting your voice to text instantly.", icon: "waveform", type: .microphone ), OnboardingPermission( title: "Microphone Selection", description: "Select the audio input device you want to use with VoiceInk.", icon: "headphones", type: .audioDeviceSelection ), OnboardingPermission( title: "Accessibility Access", description: "Allow VoiceInk to help you type anywhere in your Mac.", icon: "accessibility", type: .accessibility ), OnboardingPermission( title: "Screen Recording", description: "This helps to improve the accuracy of transcription.", icon: "rectangle.inset.filled.and.person.filled", type: .screenRecording ), OnboardingPermission( title: "Keyboard Shortcut", description: "Set up a keyboard shortcut to quickly access VoiceInk from anywhere.", icon: "keyboard", type: .keyboardShortcut ) ] var body: some View { ZStack { GeometryReader { geometry in ZStack { // Reusable background OnboardingBackgroundView() VStack(spacing: 40) { // Progress indicator HStack(spacing: 8) { ForEach(0.. String { switch permissions[currentPermissionIndex].type { case .keyboardShortcut: return permissionStates[currentPermissionIndex] ? "Continue" : "Set Shortcut" case .audioDeviceSelection: return "Continue" default: return permissionStates[currentPermissionIndex] ? "Continue" : "Enable Access" } } @ViewBuilder private func styledPicker( label: String, selectedValue: T, displayValue: String, options: [T], optionDisplayName: @escaping (T) -> String, onSelection: @escaping (T) -> Void ) -> some View { VStack(spacing: 16) { HStack(spacing: 12) { Spacer() Text(label) .font(.system(size: 16, weight: .medium)) .foregroundColor(.white.opacity(0.8)) Menu { ForEach(options, id: \.self) { option in Button(action: { onSelection(option) }) { HStack { Text(optionDisplayName(option)) if selectedValue == option { Spacer() Image(systemName: "checkmark") } } } } } label: { HStack(spacing: 8) { Text(displayValue) .foregroundColor(.white) .font(.system(size: 16, weight: .medium)) Image(systemName: "chevron.up.chevron.down") .font(.system(size: 12)) .foregroundColor(.white.opacity(0.6)) } .padding(.horizontal, 16) .padding(.vertical, 12) .background(Color.white.opacity(0.1)) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.white.opacity(0.2), lineWidth: 1) ) } .menuStyle(.borderlessButton) Spacer() } } .padding() .background(Color.white.opacity(0.05)) .cornerRadius(12) } @ViewBuilder private func hotkeyView( binding: Binding, shortcutName: KeyboardShortcuts.Name, onConfigured: @escaping (Bool) -> Void ) -> some View { VStack(spacing: 16) { styledPicker( label: "Shortcut:", selectedValue: binding.wrappedValue, displayValue: binding.wrappedValue.displayName, options: HotkeyManager.HotkeyOption.allCases.filter { $0 != .none && $0 != .custom }, optionDisplayName: { $0.displayName }, onSelection: { option in binding.wrappedValue = option onConfigured(option.isModifierKey) } ) if binding.wrappedValue == .custom { KeyboardShortcuts.Recorder(for: shortcutName) { newShortcut in onConfigured(newShortcut != nil) } .controlSize(.large) } } .onChange(of: binding.wrappedValue) { newValue in onConfigured(newValue != .none) } } }