macos: Nearby-devices picker (AirDrop-style) over the LAN
Add a horizontal strip of discovered tether devices to the window: the store polls engine.nearby() every 2s while connected and renders each as a chip with its friendly name + platform icon. Tapping a device calls pair() — sets room to that device's room and reconnects, so you join it without typing a room id. Devices already in your room show "in your room" (disabled). Verified live on macOS: two named LAN devices appear as "tap to join" chips once the app has Local Network permission. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,10 @@ struct ContentView: View {
|
||||
VStack(spacing: 0) {
|
||||
toolbar
|
||||
Divider()
|
||||
if store.connected && !store.nearby.isEmpty {
|
||||
nearbyStrip
|
||||
Divider()
|
||||
}
|
||||
feedArea
|
||||
Divider()
|
||||
composer
|
||||
@@ -16,6 +20,40 @@ struct ContentView: View {
|
||||
.task { store.connectIfSaved() }
|
||||
}
|
||||
|
||||
// MARK: - Nearby devices (AirDrop-style picker)
|
||||
|
||||
private var nearbyStrip: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
Label("Nearby", systemImage: "dot.radiowaves.left.and.right")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
.padding(.trailing, 4)
|
||||
ForEach(store.nearby, id: \.id) { peer in
|
||||
let here = peer.room == store.room
|
||||
Button {
|
||||
store.pair(with: peer)
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: sourceIcon(peer.source))
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(peer.name).font(.caption).lineLimit(1)
|
||||
Text(here ? "in your room" : "tap to join")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(here ? .green : .secondary)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10).padding(.vertical, 6)
|
||||
.background(Color.primary.opacity(0.06), in: Capsule())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(here)
|
||||
.help(here ? "Already in this room" : "Join \(peer.name)'s room")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12).padding(.vertical, 6)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toolbar
|
||||
|
||||
private var toolbar: some View {
|
||||
|
||||
@@ -19,6 +19,7 @@ final class MacTetherStore {
|
||||
var serverURL: String = UserDefaults.standard.string(forKey: "serverURL") ?? "https://tether.pecord.io"
|
||||
var room: String = RoomIdentity.derive()
|
||||
var feed: [FeedItem] = []
|
||||
var nearby: [Peer] = [] // tether devices on the LAN, for the picker
|
||||
var connected = false
|
||||
var lastError: String?
|
||||
var hasAttemptedConnect = false
|
||||
@@ -27,6 +28,7 @@ final class MacTetherStore {
|
||||
private var engine: Engine?
|
||||
private var bridge: EngineBridge?
|
||||
private var pollTask: Task<Void, Never>?
|
||||
private var nearbyTask: Task<Void, Never>?
|
||||
private var lastChangeCount = NSPasteboard.general.changeCount
|
||||
|
||||
func connectIfSaved() {
|
||||
@@ -53,6 +55,15 @@ final class MacTetherStore {
|
||||
connected = true
|
||||
lastError = nil
|
||||
|
||||
// Poll the engine's mDNS-discovered device list for the picker.
|
||||
nearbyTask = Task { [weak self] in
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(2))
|
||||
guard let self, let engine = self.engine else { continue }
|
||||
self.nearby = engine.nearby().sorted { $0.name < $1.name }
|
||||
}
|
||||
}
|
||||
|
||||
// Send: poll NSPasteboard for local changes → push to room.
|
||||
lastChangeCount = NSPasteboard.general.changeCount
|
||||
pollTask = Task { [weak self] in
|
||||
@@ -75,9 +86,18 @@ final class MacTetherStore {
|
||||
bridge = nil
|
||||
pollTask?.cancel()
|
||||
pollTask = nil
|
||||
nearbyTask?.cancel()
|
||||
nearbyTask = nil
|
||||
nearby = []
|
||||
connected = false
|
||||
}
|
||||
|
||||
/// Join a discovered device's room — the "pair" action from the picker.
|
||||
func pair(with peer: Peer) {
|
||||
room = peer.room
|
||||
connect()
|
||||
}
|
||||
|
||||
func send(_ text: String) {
|
||||
engine?.send(text: text)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user