Files
ten31-transcripts/Ten31Transcripts/UI/MenuBarView.swift
T
Grant Gilliam b2ae3a62b9 Phase 0: menu-bar scaffold, permissions, backend health check
Native SwiftUI menu-bar app (LSUIElement, macOS 13+), generated from project.yml
via XcodeGen. Includes:
- PermissionsManager (Microphone / Screen Recording / Accessibility) + UI
- SparkControlHealth: GET /api/status over self-signed TLS (InsecureTrustDelegate)
- AppSettings persistence (host, TLS-skip, output folder, adapter toggles)
- Menu-bar panel + Settings, app sandbox & hardened runtime off (LAN tool)
2026-06-05 19:33:53 -05:00

117 lines
3.7 KiB
Swift

import SwiftUI
import AppKit
/// The menu-bar panel: permission statuses, backend health, and a link to
/// Settings. Shown when the user clicks the status-bar item.
struct MenuBarView: View {
@EnvironmentObject private var settings: AppSettings
@EnvironmentObject private var permissions: PermissionsManager
@EnvironmentObject private var health: SparkControlHealth
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 12) {
header
Divider()
permissionsSection
Divider()
backendSection
Divider()
footer
}
.padding(14)
.frame(width: 320)
}
.onAppear { permissions.refresh() }
.task { await refreshHealth() }
}
private var header: some View {
VStack(alignment: .leading, spacing: 2) {
Text("Ten31 Transcripts").font(.headline)
Text("Phase 0 · setup & status")
.font(.caption)
.foregroundStyle(.secondary)
}
}
private var permissionsSection: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Permissions").font(.subheadline).bold()
PermissionRow(
title: "Microphone",
state: permissions.microphone,
onGrant: permissions.requestMicrophone,
onOpenSettings: { permissions.openSettings(.microphone) }
)
PermissionRow(
title: "Screen Recording",
state: permissions.screenRecording,
onGrant: permissions.requestScreenRecording,
onOpenSettings: { permissions.openSettings(.screenRecording) }
)
PermissionRow(
title: "Accessibility",
state: permissions.accessibility,
onGrant: permissions.requestAccessibility,
onOpenSettings: { permissions.openSettings(.accessibility) }
)
}
}
private var backendSection: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Backend").font(.subheadline).bold()
Spacer()
Button("Check") { Task { await refreshHealth() } }
.disabled(health.status == .checking)
}
Text(settings.backendBaseURL)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
HStack(spacing: 8) {
StatusDot(color: healthColor)
Text(healthText).font(.caption)
}
}
}
private var footer: some View {
HStack {
NavigationLink("Settings…") {
SettingsView()
}
Spacer()
Button("Quit") { NSApplication.shared.terminate(nil) }
}
}
private func refreshHealth() async {
await health.check(
baseURL: settings.backendBaseURL,
skipTLS: settings.skipTLSVerification
)
}
private var healthColor: Color {
switch health.status {
case .online: return .green
case .offline: return .red
case .checking: return .orange
case .unknown: return .gray
}
}
private var healthText: String {
switch health.status {
case .unknown: return "Not checked yet"
case .checking: return "Checking…"
case .online(let detail): return "Online · \(detail)"
case .offline(let error): return "Offline · \(error)"
}
}
}