f292b3fe3a
ci / rust (push) Has been cancelled
Three more tvOS-isms, all the same lesson — let the focus engine own the chrome: - Host cards drew their own material platter + accent ring INSIDE the .card button style, muting the native grow/tilt focus motion. On tvOS the card style now owns the platter outright (material/ring stay on the pointer platforms), and the grid gets 48 pt spacing so the focused card swells without overlapping siblings. - Add Host and Settings no longer sit in the hosts row: they're a compact button row below the grid (and the empty state gains a Settings button, since tvOS has no toolbar). - The Add Host and pairing dialogs drop Form entirely on tvOS — list rows added a full-width focus fill plus a row platter behind every field's own pill (the "second outer pill"). As standalone fields in a centered dialog over the dimmed home, each input is exactly one pill with vertically centered text. Verified by screenshot in the Apple TV simulator (home grid + Add Host dialog). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.1 KiB
Swift
85 lines
3.1 KiB
Swift
// "+" sheet: name (optional) + address + port → a card in the hosts grid. The first
|
|
// actual connection runs the trust-on-first-use fingerprint prompt.
|
|
|
|
import SwiftUI
|
|
|
|
struct AddHostSheet: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var name = ""
|
|
@State private var address = ""
|
|
@State private var port = 9777
|
|
|
|
let onAdd: (StoredHost) -> Void
|
|
|
|
var body: some View {
|
|
#if os(tvOS)
|
|
// No Form here: tvOS list rows add a full-width focus fill + row platter
|
|
// behind the field's own pill. Standalone fields have exactly one pill.
|
|
VStack(spacing: 28) {
|
|
Text("Add Host")
|
|
.font(.title3.weight(.semibold))
|
|
TextField("Name", text: $name, prompt: Text("Optional — e.g. Living Room"))
|
|
.labelsHidden()
|
|
TextField("Address", text: $address, prompt: Text("IP or hostname"))
|
|
.labelsHidden()
|
|
TextField("Port", value: $port, format: .number.grouping(.never))
|
|
.labelsHidden()
|
|
HStack(spacing: 32) {
|
|
Button("Cancel", role: .cancel) { dismiss() }
|
|
Button("Add Host") { add() }
|
|
.disabled(address.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
}
|
|
.padding(.top, 12)
|
|
}
|
|
.frame(maxWidth: 1000)
|
|
.padding(60)
|
|
#else
|
|
VStack(spacing: 0) {
|
|
Form {
|
|
TextField("Name", text: $name, prompt: Text("Optional — e.g. Living Room"))
|
|
TextField("Address", text: $address, prompt: Text("IP or hostname"))
|
|
TextField("Port", value: $port, format: .number.grouping(.never))
|
|
#if os(tvOS)
|
|
// tvOS floats the label above a non-empty field INSIDE the pill,
|
|
// shoving the value off-center — the field is always prefilled
|
|
// here, so drop the label there.
|
|
.labelsHidden()
|
|
#endif
|
|
}
|
|
#if !os(tvOS)
|
|
.formStyle(.grouped)
|
|
#endif
|
|
HStack {
|
|
Button("Cancel", role: .cancel) { dismiss() }
|
|
#if !os(tvOS)
|
|
.keyboardShortcut(.cancelAction)
|
|
#endif
|
|
Spacer()
|
|
Button("Add Host") { add() }
|
|
.buttonStyle(.borderedProminent)
|
|
#if !os(tvOS)
|
|
.keyboardShortcut(.defaultAction)
|
|
#endif
|
|
.disabled(address.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
}
|
|
#if os(iOS)
|
|
.controlSize(.large)
|
|
#endif
|
|
.padding(16)
|
|
}
|
|
#if os(macOS)
|
|
.frame(width: 380)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
private func add() {
|
|
onAdd(StoredHost(
|
|
name: name.trimmingCharacters(in: .whitespaces),
|
|
address: address.trimmingCharacters(in: .whitespaces),
|
|
port: UInt16(clamping: port)))
|
|
dismiss()
|
|
}
|
|
}
|