Add .task { store.connect() } so the app connects on open instead of requiring
a manual Connect tap (macOS already did this) — the defaults are production-ready
(public relay + account-derived room). This is also what surfaced the stale
"ttps://" typo bug: a corrupted saved server URL silently failed connect with no
visible error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
3.3 KiB
Swift
98 lines
3.3 KiB
Swift
import SwiftUI
|
|
|
|
struct RoomView: View {
|
|
@Bindable var store: TetherStore
|
|
@State private var draft = ""
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 0) {
|
|
connectionBar
|
|
Divider()
|
|
feedList
|
|
Divider()
|
|
composer
|
|
}
|
|
.navigationTitle("tether")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.task { store.connect() } // auto-connect on launch (defaults are production-ready)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Circle()
|
|
.fill(store.connected ? .green : .secondary)
|
|
.frame(width: 10, height: 10)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var connectionBar: some View {
|
|
VStack(spacing: 8) {
|
|
TextField("https://tether.pecord.io", text: $store.serverURL)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.keyboardType(.URL)
|
|
HStack {
|
|
TextField("room id (8 hex)", text: $store.room)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
Button(store.connected ? "Disconnect" : "Connect") {
|
|
store.connected ? store.disconnect() : store.connect()
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
if let err = store.lastError {
|
|
Text(err).font(.caption).foregroundStyle(.red)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
.textFieldStyle(.roundedBorder)
|
|
.padding()
|
|
}
|
|
|
|
private var feedList: some View {
|
|
List(store.feed) { msg in
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(msg.text ?? "")
|
|
.lineLimit(4)
|
|
HStack {
|
|
Text(msg.source ?? "?").font(.caption2)
|
|
Spacer()
|
|
Text(msg.date, style: .time).font(.caption2)
|
|
}
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.contentShape(Rectangle())
|
|
.onTapGesture { store.copyToPasteboard(msg) }
|
|
.swipeActions {
|
|
Button("Copy") { store.copyToPasteboard(msg) }.tint(.blue)
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.overlay {
|
|
if store.feed.isEmpty {
|
|
ContentUnavailableView("No messages yet",
|
|
systemImage: "doc.on.clipboard",
|
|
description: Text("Connect, then send from any device in the room."))
|
|
}
|
|
}
|
|
}
|
|
|
|
private var composer: some View {
|
|
HStack {
|
|
TextField("Type or paste…", text: $draft, axis: .vertical)
|
|
.textFieldStyle(.roundedBorder)
|
|
.lineLimit(1...4)
|
|
Button {
|
|
if draft.isEmpty { store.sendClipboard() }
|
|
else { store.send(draft); draft = "" }
|
|
} label: {
|
|
Image(systemName: draft.isEmpty ? "doc.on.clipboard" : "paperplane.fill")
|
|
.font(.title2)
|
|
}
|
|
.disabled(!store.connected)
|
|
}
|
|
.padding()
|
|
}
|
|
}
|