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 hotkeyView( binding: Binding, shortcutName: KeyboardShortcuts.Name, onConfigured: @escaping (Bool) -> Void ) -> some View { VStack(spacing: 16) { HStack(spacing: 12) { Spacer() Text("Shortcut:") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white.opacity(0.8)) Menu { ForEach(HotkeyManager.HotkeyOption.allCases, id: \.self) { option in if option != .none && option != .custom { // Exclude 'None' and 'Custom' from the list Button(action: { binding.wrappedValue = option onConfigured(option.isModifierKey) }) { HStack { Text(option.displayName) if binding.wrappedValue == option { Spacer() Image(systemName: "checkmark") } } } } } } label: { HStack(spacing: 8) { Text(binding.wrappedValue.displayName) .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() } if binding.wrappedValue == .custom { KeyboardShortcuts.Recorder(for: shortcutName) { newShortcut in onConfigured(newShortcut != nil) } .controlSize(.large) } } .padding() .background(Color.white.opacity(0.05)) .cornerRadius(12) .onChange(of: binding.wrappedValue) { newValue in onConfigured(newValue != .none) } } }