fix(apple/tvOS): focus-native home grid, separated actions, Form-free dialogs
ci / rust (push) Has been cancelled
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>
This commit is contained in:
@@ -35,6 +35,46 @@ struct PairSheet: View {
|
||||
@State private var token = CeremonyToken()
|
||||
|
||||
var body: some View {
|
||||
#if os(tvOS)
|
||||
VStack(spacing: 24) {
|
||||
Label("Pair with \(host.displayName)", systemImage: "lock.shield")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundStyle(.tint)
|
||||
Text("The host prints the PIN when pairing is armed "
|
||||
+ "(--allow-pairing, \u{201C}PAIRING ARMED\u{201D} in its log). "
|
||||
+ "Pairing verifies both sides at once — no fingerprint comparison "
|
||||
+ "needed.")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
TextField("PIN", text: $pin, prompt: Text("Shown in the host's log"))
|
||||
.labelsHidden()
|
||||
TextField(
|
||||
"Client name", text: $clientName,
|
||||
prompt: Text("How the host lists this device"))
|
||||
.labelsHidden()
|
||||
if let errorText {
|
||||
Text(errorText)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
HStack(spacing: 32) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
token.cancelled = true
|
||||
dismiss()
|
||||
}
|
||||
if busy {
|
||||
ProgressView()
|
||||
}
|
||||
Button("Pair & Connect") { runCeremony() }
|
||||
.disabled(busy || pin.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
}
|
||||
.padding(.top, 12)
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
.padding(60)
|
||||
.onDisappear { token.cancelled = true }
|
||||
#else
|
||||
VStack(spacing: 0) {
|
||||
Form {
|
||||
Section {
|
||||
@@ -103,6 +143,7 @@ struct PairSheet: View {
|
||||
#endif
|
||||
.interactiveDismissDisabled(busy)
|
||||
.onDisappear { token.cancelled = true } // any other dismissal path
|
||||
#endif
|
||||
}
|
||||
|
||||
private func runCeremony() {
|
||||
|
||||
Reference in New Issue
Block a user