Shuffle settings around

This commit is contained in:
Peter Steinberger 2025-06-18 10:39:45 +02:00
parent 31f1d625b1
commit c16ccacf30
4 changed files with 151 additions and 171 deletions

View file

@ -6,6 +6,8 @@ struct AdvancedSettingsView: View {
private var debugMode = false
@AppStorage("cleanupOnStartup")
private var cleanupOnStartup = true
@AppStorage("showInDock")
private var showInDock = false
@State private var cliInstaller = CLIInstaller()
var body: some View {
@ -70,13 +72,21 @@ struct AdvancedSettingsView: View {
.font(.caption)
.foregroundStyle(.secondary)
}
} header: {
Text("Advanced")
.font(.headline)
}
// Debug section
Section {
// Show in Dock
VStack(alignment: .leading, spacing: 4) {
Toggle("Show in Dock", isOn: showInDockBinding)
VStack(alignment: .leading, spacing: 2) {
Text("Show VibeTunnel icon in the Dock.")
.font(.caption)
.foregroundStyle(.secondary)
Text("The dock icon is always displayed when the Settings dialog is visible.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
// Debug mode toggle
VStack(alignment: .leading, spacing: 4) {
Toggle("Debug mode", isOn: $debugMode)
Text("Enable additional logging and debugging features.")
@ -84,7 +94,7 @@ struct AdvancedSettingsView: View {
.foregroundStyle(.secondary)
}
} header: {
Text("Debug")
Text("Advanced")
.font(.headline)
}
}
@ -96,6 +106,17 @@ struct AdvancedSettingsView: View {
cliInstaller.checkInstallationStatus()
}
}
private var showInDockBinding: Binding<Bool> {
Binding(
get: { showInDock },
set: { newValue in
showInDock = newValue
// Don't change activation policy while settings window is open
// The change will be applied when the settings window closes
}
)
}
}
// MARK: - Terminal Preference Section

View file

@ -46,8 +46,6 @@ struct DashboardSettingsView: View {
var body: some View {
NavigationStack {
Form {
PermissionsSection()
SecuritySection(
passwordEnabled: $passwordEnabled,
password: $password,
@ -825,102 +823,3 @@ private struct NgrokErrorView: View {
}
}
// MARK: - Permissions Section
private struct PermissionsSection: View {
@StateObject private var appleScriptManager = AppleScriptPermissionManager.shared
@State private var accessibilityUpdateTrigger = 0
private var hasAccessibilityPermission: Bool {
// This will cause a re-read whenever accessibilityUpdateTrigger changes
_ = accessibilityUpdateTrigger
return AccessibilityPermissionManager.shared.hasPermission()
}
var body: some View {
Section {
VStack(alignment: .leading, spacing: 16) {
// Automation permission
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Terminal Automation")
.font(.body)
Text("Required to launch and control terminal applications")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if appleScriptManager.hasPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Granted")
.foregroundColor(.secondary)
}
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 2)
.frame(height: 22) // Match small button height
} else {
Button("Grant Permission") {
appleScriptManager.requestPermission()
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
Divider()
// Accessibility permission
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Accessibility")
.font(.body)
Text("Required for terminals that need keystroke input (Ghostty, Warp, Hyper)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if hasAccessibilityPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Granted")
.foregroundColor(.secondary)
}
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 2)
.frame(height: 22) // Match small button height
} else {
Button("Grant Permission") {
AccessibilityPermissionManager.shared.requestPermission()
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
}
} header: {
Text("Permissions")
.font(.headline)
} footer: {
Text("Terminal automation is required for all terminals. Accessibility is only needed for terminals that simulate keyboard input.")
.font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
.task {
_ = await appleScriptManager.checkPermission()
}
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
// Force a re-render to check accessibility permission
accessibilityUpdateTrigger += 1
}
}
}

View file

@ -32,15 +32,12 @@ struct DebugSettingsView: View {
var body: some View {
NavigationStack {
Form {
HTTPServerSection(
ServerSection(
isServerHealthy: isServerHealthy,
isServerRunning: isServerRunning,
serverPort: serverPort,
lastError: lastError,
toggleServer: toggleServer
)
ServerConfigurationSection(
toggleServer: toggleServer,
serverModeString: $serverModeString,
serverManager: serverManager
)
@ -269,18 +266,21 @@ struct DebugSettingsView: View {
}
}
// MARK: - HTTP Server Section
// MARK: - Server Section
private struct HTTPServerSection: View {
private struct ServerSection: View {
let isServerHealthy: Bool
let isServerRunning: Bool
let serverPort: Int
let lastError: String?
let toggleServer: (Bool) async -> Void
@Binding var serverModeString: String
let serverManager: ServerManager
var body: some View {
Section {
VStack(alignment: .leading, spacing: 12) {
// Server Status
HStack {
VStack(alignment: .leading, spacing: 4) {
HStack {
@ -323,29 +323,10 @@ private struct HTTPServerSection: View {
.font(.caption)
.foregroundStyle(.red)
}
}
.padding(.vertical, 4)
} header: {
Text("HTTP Server")
.font(.headline)
} footer: {
Text("The HTTP server provides REST API endpoints for terminal session management.")
.font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
}
}
// MARK: - Server Configuration Section
private struct ServerConfigurationSection: View {
@Binding var serverModeString: String
let serverManager: ServerManager
var body: some View {
Section {
VStack(alignment: .leading, spacing: 8) {
Divider()
// Server Mode Configuration
HStack {
Text("Server Mode")
Spacer()
@ -383,11 +364,12 @@ private struct ServerConfigurationSection: View {
}
}
}
.padding(.vertical, 4)
} header: {
Text("Server Configuration")
Text("HTTP Server")
.font(.headline)
} footer: {
Text("Choose between the built-in Swift Hummingbird server or the Rust tty-fwd binary.")
Text("The HTTP server provides REST API endpoints for terminal session management. Choose between the built-in Swift Hummingbird server or the Rust tty-fwd binary.")
.font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
@ -559,6 +541,7 @@ private struct DeveloperToolsSection: View {
showServerConsole()
}
.buttonStyle(.bordered)
.frame(width: 120)
}
Text("View real-time server logs from both Hummingbird and Rust servers")
.font(.caption)
@ -573,6 +556,7 @@ private struct DeveloperToolsSection: View {
openConsole()
}
.buttonStyle(.bordered)
.frame(width: 120)
}
Text("View all application logs in Console.app")
.font(.caption)
@ -587,6 +571,7 @@ private struct DeveloperToolsSection: View {
showApplicationSupport()
}
.buttonStyle(.bordered)
.frame(width: 120)
}
Text("Open the application support directory")
.font(.caption)
@ -601,6 +586,7 @@ private struct DeveloperToolsSection: View {
AppDelegate.showWelcomeScreen()
}
.buttonStyle(.bordered)
.frame(width: 120)
}
Text("Display the welcome screen again")
.font(.caption)
@ -616,6 +602,7 @@ private struct DeveloperToolsSection: View {
}
.buttonStyle(.borderedProminent)
.tint(.red)
.frame(width: 120)
}
Text("Remove all stored preferences and reset to defaults")
.font(.caption)

View file

@ -1,4 +1,5 @@
import SwiftUI
import Combine
/// General settings tab for basic app preferences
struct GeneralSettingsView: View {
@ -6,8 +7,6 @@ struct GeneralSettingsView: View {
private var autostart = false
@AppStorage("showNotifications")
private var showNotifications = true
@AppStorage("showInDock")
private var showInDock = false
@AppStorage("updateChannel")
private var updateChannelRaw = UpdateChannel.stable.rawValue
@ -76,23 +75,8 @@ struct GeneralSettingsView: View {
.font(.headline)
}
Section {
// Show in Dock
VStack(alignment: .leading, spacing: 4) {
Toggle("Show in Dock", isOn: showInDockBinding)
VStack(alignment: .leading, spacing: 2) {
Text("Show VibeTunnel icon in the Dock.")
.font(.caption)
.foregroundStyle(.secondary)
Text("The dock icon is always displayed when the Settings dialog is visible.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
} header: {
Text("Appearance")
.font(.headline)
}
// Permissions Section
PermissionsSection()
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
@ -114,17 +98,6 @@ struct GeneralSettingsView: View {
)
}
private var showInDockBinding: Binding<Bool> {
Binding(
get: { showInDock },
set: { newValue in
showInDock = newValue
// Don't change activation policy while settings window is open
// The change will be applied when the settings window closes
}
)
}
private var updateChannelBinding: Binding<UpdateChannel> {
Binding(
get: { updateChannel },
@ -151,3 +124,103 @@ struct GeneralSettingsView: View {
}
}
}
// MARK: - Permissions Section
private struct PermissionsSection: View {
@StateObject private var appleScriptManager = AppleScriptPermissionManager.shared
@State private var accessibilityUpdateTrigger = 0
private var hasAccessibilityPermission: Bool {
// This will cause a re-read whenever accessibilityUpdateTrigger changes
_ = accessibilityUpdateTrigger
return AccessibilityPermissionManager.shared.hasPermission()
}
var body: some View {
Section {
VStack(alignment: .leading, spacing: 16) {
// Automation permission
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Terminal Automation")
.font(.body)
Text("Required to launch and control terminal applications")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if appleScriptManager.hasPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Granted")
.foregroundColor(.secondary)
}
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 2)
.frame(height: 22) // Match small button height
} else {
Button("Grant Permission") {
appleScriptManager.requestPermission()
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
Divider()
// Accessibility permission
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Accessibility")
.font(.body)
Text("Required for terminals that need keystroke input (Ghostty, Warp, Hyper)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if hasAccessibilityPermission {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Granted")
.foregroundColor(.secondary)
}
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 2)
.frame(height: 22) // Match small button height
} else {
Button("Grant Permission") {
AccessibilityPermissionManager.shared.requestPermission()
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
}
} header: {
Text("Permissions")
.font(.headline)
} footer: {
Text("Terminal automation is required for all terminals. Accessibility is only needed for terminals that simulate keyboard input.")
.font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
.task {
_ = await appleScriptManager.checkPermission()
}
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
// Force a re-render to check accessibility permission
accessibilityUpdateTrigger += 1
}
}
}