vibetunnel/mac/VibeTunnel/Presentation/Views/Welcome/RequestPermissionsPageView.swift
2025-06-22 11:53:00 +02:00

136 lines
5.3 KiB
Swift

import SwiftUI
/// Third page requesting AppleScript automation and accessibility permissions.
///
/// This view guides users through granting necessary permissions for VibeTunnel
/// to function properly. It handles both AppleScript permissions for terminal
/// automation and accessibility permissions for sending commands.
///
/// ## Topics
///
/// ### Overview
/// The permissions page includes:
/// - AppleScript permission request and status
/// - Accessibility permission request and status
/// - Terminal application selector
/// - Real-time permission status updates
///
/// ### Requirements
/// - ``SystemPermissionManager`` for all system permissions
/// - Terminal selection stored in UserDefaults
struct RequestPermissionsPageView: View {
@State private var permissionManager = SystemPermissionManager.shared
@State private var permissionUpdateTrigger = 0
// IMPORTANT: These computed properties ensure the UI always shows current permission state.
// The permissionUpdateTrigger dependency forces SwiftUI to re-evaluate these properties
// when permissions change. Without this, the UI would not update when permissions are
// granted in System Settings while this view is visible.
//
// We use computed properties instead of @State to avoid UI flashing - the initial
// permission check in .task happens before the first render, ensuring correct state
// from the start.
private var hasAppleScriptPermission: Bool {
_ = permissionUpdateTrigger
return permissionManager.hasPermission(.appleScript)
}
private var hasAccessibilityPermission: Bool {
_ = permissionUpdateTrigger
return permissionManager.hasPermission(.accessibility)
}
var body: some View {
VStack(spacing: 30) {
// App icon
Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
.resizable()
.frame(width: 156, height: 156)
.shadow(radius: 10)
VStack(spacing: 16) {
Text("Request Permissions")
.font(.largeTitle)
.fontWeight(.semibold)
Text(
"VibeTunnel needs AppleScript to start new terminal sessions\nand accessibility to send commands."
)
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.frame(maxWidth: 480)
.fixedSize(horizontal: false, vertical: true)
// Permissions buttons
VStack(spacing: 16) {
// Automation permission
if hasAppleScriptPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Automation permission granted")
.foregroundColor(.secondary)
}
.font(.body)
.frame(maxWidth: 250)
.frame(height: 32)
} else {
Button("Grant Automation Permission") {
permissionManager.requestPermission(.appleScript)
}
.buttonStyle(.borderedProminent)
.controlSize(.regular)
.frame(width: 250, height: 32)
}
// Accessibility permission
if hasAccessibilityPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Accessibility permission granted")
.foregroundColor(.secondary)
}
.font(.body)
.frame(maxWidth: 250)
.frame(height: 32)
} else {
Button("Grant Accessibility Permission") {
permissionManager.requestPermission(.accessibility)
}
.buttonStyle(.bordered)
.controlSize(.regular)
.frame(width: 250, height: 32)
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.task {
// Check permissions before first render to avoid UI flashing
await permissionManager.checkAllPermissions()
// Register for continuous monitoring
permissionManager.registerForMonitoring()
}
.onDisappear {
permissionManager.unregisterFromMonitoring()
}
.onReceive(NotificationCenter.default.publisher(for: .permissionsUpdated)) { _ in
// Increment trigger to force computed property re-evaluation
permissionUpdateTrigger += 1
}
}
}
// MARK: - Preview
struct RequestPermissionsPageView_Previews: PreviewProvider {
static var previews: some View {
RequestPermissionsPageView()
.frame(width: 640, height: 480)
.background(Color(NSColor.windowBackgroundColor))
}
}