feat: richer mac settings panes and template icon
parent
b66098ea20
commit
b557a73c3f
|
|
@ -357,8 +357,8 @@ struct ClawdisApp: App {
|
||||||
.menuBarExtraStyle(.menu)
|
.menuBarExtraStyle(.menu)
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
SettingsView(state: state)
|
SettingsRootView(state: state)
|
||||||
.frame(width: 420, height: 320)
|
.frame(minWidth: 520, minHeight: 420)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,10 +460,64 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate
|
||||||
|
|
||||||
// MARK: - Settings UI
|
// MARK: - Settings UI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsRootView: View {
|
||||||
@ObservedObject var state: AppState
|
@ObservedObject var state: AppState
|
||||||
@State private var permStatus: [Capability: Bool] = [:]
|
@State private var permStatus: [Capability: Bool] = [:]
|
||||||
@State private var loadingPerms = false
|
@State private var loadingPerms = false
|
||||||
|
@State private var selectedTab: SettingsTab = .general
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Picker("", selection: $selectedTab) {
|
||||||
|
ForEach(SettingsTab.allCases, id: \.self) { tab in
|
||||||
|
Text(tab.title).tag(tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
.padding([.top, .horizontal])
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Group {
|
||||||
|
switch selectedTab {
|
||||||
|
case .general:
|
||||||
|
GeneralSettings(state: state)
|
||||||
|
case .permissions:
|
||||||
|
PermissionsSettings(status: permStatus, refresh: refreshPerms, showOnboarding: { OnboardingController.shared.show() })
|
||||||
|
case .debug:
|
||||||
|
DebugSettings()
|
||||||
|
case .about:
|
||||||
|
AboutSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(16)
|
||||||
|
}
|
||||||
|
.task { await refreshPerms() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func refreshPerms() async {
|
||||||
|
guard !loadingPerms else { return }
|
||||||
|
loadingPerms = true
|
||||||
|
permStatus = await PermissionManager.status()
|
||||||
|
loadingPerms = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SettingsTab: CaseIterable {
|
||||||
|
case general, permissions, debug, about
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .general: return "General"
|
||||||
|
case .permissions: return "Permissions"
|
||||||
|
case .debug: return "Debug"
|
||||||
|
case .about: return "About"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GeneralSettings: View {
|
||||||
|
@ObservedObject var state: AppState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
|
@ -471,12 +525,8 @@ struct SettingsView: View {
|
||||||
Label("Complete onboarding to finish setup", systemImage: "sparkles")
|
Label("Complete onboarding to finish setup", systemImage: "sparkles")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
}
|
}
|
||||||
Toggle(isOn: $state.isPaused) {
|
Toggle(isOn: $state.isPaused) { Text("Pause Clawdis (disables notifications & privileged actions)") }
|
||||||
Text("Pause Clawdis (disables notifications & privileged actions)")
|
Toggle(isOn: $state.launchAtLogin) { Text("Launch at login") }
|
||||||
}
|
|
||||||
Toggle(isOn: $state.launchAtLogin) {
|
|
||||||
Text("Launch at login")
|
|
||||||
}
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Default sound")
|
Text("Default sound")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
@ -489,30 +539,72 @@ struct SettingsView: View {
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.frame(width: 140)
|
.frame(width: 140)
|
||||||
}
|
}
|
||||||
Divider()
|
|
||||||
Text("Permissions")
|
|
||||||
.font(.headline)
|
|
||||||
PermissionStatusList(status: permStatus, refresh: refreshPerms)
|
|
||||||
Button("Show Onboarding") {
|
|
||||||
OnboardingController.shared.show()
|
|
||||||
}
|
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Clawdis Companion")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding()
|
}
|
||||||
.task { await refreshPerms() }
|
}
|
||||||
|
|
||||||
|
struct PermissionsSettings: View {
|
||||||
|
let status: [Capability: Bool]
|
||||||
|
let refresh: () async -> Void
|
||||||
|
let showOnboarding: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("Allow these so Clawdis can notify and capture when needed.")
|
||||||
|
PermissionStatusList(status: status, refresh: refresh)
|
||||||
|
Button("Show onboarding") { showOnboarding() }
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebugSettings: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
LabeledContent("PID") { Text("\(ProcessInfo.processInfo.processIdentifier)") }
|
||||||
|
LabeledContent("Log file") {
|
||||||
|
Button("Open /tmp/clawdis.log") { NSWorkspace.shared.open(URL(fileURLWithPath: "/tmp/clawdis.log")) }
|
||||||
|
}
|
||||||
|
LabeledContent("Binary path") { Text(Bundle.main.bundlePath).font(.footnote) }
|
||||||
|
HStack {
|
||||||
|
Button("Restart app") { relaunch() }
|
||||||
|
Button("Reveal app in Finder") { revealApp() }
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
private func relaunch() {
|
||||||
private func refreshPerms() async {
|
let url = Bundle.main.bundleURL
|
||||||
guard !loadingPerms else { return }
|
let task = Process()
|
||||||
loadingPerms = true
|
task.launchPath = "/usr/bin/open"
|
||||||
permStatus = await PermissionManager.status()
|
task.arguments = [url.path]
|
||||||
loadingPerms = false
|
try? task.run()
|
||||||
|
task.waitUntilExit()
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func revealApp() {
|
||||||
|
let url = Bundle.main.bundleURL
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([url])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AboutSettings: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("Clawdis Companion")
|
||||||
|
.font(.title2.bold())
|
||||||
|
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev"
|
||||||
|
Text("Version \(version)")
|
||||||
|
Text("Menu bar helper for notifications, screenshots, and privileged actions.")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Divider()
|
||||||
|
Link("View repository", destination: URL(string: "https://github.com/steipete/warelay")!)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue