pi-remote-ios/Sources/UI/Sessions/SessionSwitcher.swift

95 lines
3.6 KiB
Swift

// Sources/UI/Sessions/SessionSwitcher.swift
// T-2.6: Sheet UI for listing, selecting, and creating sessions.
import SwiftUI
struct SessionSwitcher: View {
@ObservedObject var registry: SessionRegistry
let credential: SidecarCredential
let onSelect: (SessionInfo) -> Void
@Environment(\.dismiss) private var dismiss
@State private var showNewSessionAlert = false
@State private var newSessionName = ""
@State private var spawnError: String? = nil
var body: some View {
NavigationStack {
Group {
if registry.isLoading {
ProgressView("Loading sessions…")
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
List(registry.sessions) { session in
Button {
onSelect(session)
dismiss()
} label: {
SessionRow(session: session)
}
.foregroundStyle(.primary)
}
.overlay {
if registry.sessions.isEmpty {
ContentUnavailableView(
"No Sessions",
systemImage: "terminal",
description: Text("Tap + to create a session.")
)
}
}
}
}
.navigationTitle("Sessions")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") { dismiss() }
}
ToolbarItem(placement: .primaryAction) {
Button {
newSessionName = ""
spawnError = nil
showNewSessionAlert = true
} label: {
Image(systemName: "plus")
}
.accessibilityLabel("New Session")
.accessibilityIdentifier("sessionswitcher.new")
}
}
.alert("New Session", isPresented: $showNewSessionAlert) {
TextField("Session name", text: $newSessionName)
.autocorrectionDisabled()
Button("Create") {
let name = newSessionName.trimmingCharacters(in: .whitespaces)
guard !name.isEmpty else { return }
Task {
do {
try await registry.spawnSession(name: name, credential: credential)
await registry.refresh(credential: credential)
} catch {
spawnError = error.localizedDescription
}
}
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Enter a name for the new session.")
}
.safeAreaInset(edge: .bottom) {
if let err = registry.error ?? spawnError {
Text(err)
.font(.caption)
.foregroundStyle(.red)
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(uiColor: .secondarySystemBackground))
}
}
}
.task { await registry.refresh(credential: credential) }
}
}