mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-06-29 05:39:31 +00:00
239 lines
8.6 KiB
Swift
239 lines
8.6 KiB
Swift
import Combine
|
|
import SwiftUI
|
|
|
|
/// General settings tab for basic app preferences
|
|
struct GeneralSettingsView: View {
|
|
@AppStorage("autostart")
|
|
private var autostart = false
|
|
@AppStorage("showNotifications")
|
|
private var showNotifications = true
|
|
@AppStorage("updateChannel")
|
|
private var updateChannelRaw = UpdateChannel.stable.rawValue
|
|
|
|
@State private var isCheckingForUpdates = false
|
|
|
|
private let startupManager = StartupManager()
|
|
|
|
var updateChannel: UpdateChannel {
|
|
UpdateChannel(rawValue: updateChannelRaw) ?? .stable
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section {
|
|
// Launch at Login
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Toggle("Launch at Login", isOn: launchAtLoginBinding)
|
|
Text("Automatically start VibeTunnel when you log into your Mac.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
} header: {
|
|
Text("Application")
|
|
.font(.headline)
|
|
}
|
|
|
|
Section {
|
|
// Update Channel
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack {
|
|
Text("Update Channel")
|
|
Spacer()
|
|
Picker("", selection: updateChannelBinding) {
|
|
ForEach(UpdateChannel.allCases) { channel in
|
|
Text(channel.displayName).tag(channel)
|
|
}
|
|
}
|
|
.pickerStyle(.menu)
|
|
.labelsHidden()
|
|
}
|
|
Text(updateChannel.description)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
// Check for Updates
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("Check for Updates")
|
|
Text("Check for new versions of VibeTunnel.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Button("Check Now") {
|
|
checkForUpdates()
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.disabled(isCheckingForUpdates)
|
|
}
|
|
} header: {
|
|
Text("Updates")
|
|
.font(.headline)
|
|
}
|
|
|
|
// Permissions Section
|
|
PermissionsSection()
|
|
}
|
|
.formStyle(.grouped)
|
|
.scrollContentBackground(.hidden)
|
|
.navigationTitle("General Settings")
|
|
}
|
|
.task {
|
|
// Sync launch at login status
|
|
autostart = startupManager.isLaunchAtLoginEnabled
|
|
}
|
|
}
|
|
|
|
private var launchAtLoginBinding: Binding<Bool> {
|
|
Binding(
|
|
get: { autostart },
|
|
set: { newValue in
|
|
autostart = newValue
|
|
startupManager.setLaunchAtLogin(enabled: newValue)
|
|
}
|
|
)
|
|
}
|
|
|
|
private var updateChannelBinding: Binding<UpdateChannel> {
|
|
Binding(
|
|
get: { updateChannel },
|
|
set: { newValue in
|
|
updateChannelRaw = newValue.rawValue
|
|
// Notify the updater manager about the channel change
|
|
NotificationCenter.default.post(
|
|
name: Notification.Name("UpdateChannelChanged"),
|
|
object: nil,
|
|
userInfo: ["channel": newValue]
|
|
)
|
|
}
|
|
)
|
|
}
|
|
|
|
private func checkForUpdates() {
|
|
isCheckingForUpdates = true
|
|
NotificationCenter.default.post(name: Notification.Name("checkForUpdates"), object: nil)
|
|
|
|
// Reset after a delay
|
|
Task {
|
|
try? await Task.sleep(for: .seconds(2))
|
|
isCheckingForUpdates = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.checkPermissionStatus() {
|
|
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 to enter terminal startup commands.")
|
|
.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: {
|
|
if appleScriptManager.checkPermissionStatus() && hasAccessibilityPermission {
|
|
Text(
|
|
"All permissions granted. New sessions will spawn new terminal windows."
|
|
)
|
|
.font(.caption)
|
|
.frame(maxWidth: .infinity)
|
|
.multilineTextAlignment(.center)
|
|
.foregroundColor(.green)
|
|
} else {
|
|
Text(
|
|
"Terminals can be controlled without permissions, however new sessions won't load."
|
|
)
|
|
.font(.caption)
|
|
.frame(maxWidth: .infinity)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
}
|
|
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
|
// Force a re-render to check accessibility permission
|
|
accessibilityUpdateTrigger += 1
|
|
}
|
|
.task {
|
|
// Perform a silent check that won't trigger dialog
|
|
_ = await appleScriptManager.silentPermissionCheck()
|
|
}
|
|
}
|
|
}
|