apps: show delivery receipts — "✓ Seen by <device>"
Both apps now poll engine.receipts() alongside nearby() and render a "Seen by <names>" bar above the composer when acks come in. Closes the loop the receipt engine opened: you can see your clipboard was received, instead of asking. Verified live on macOS: copying on the Mac with a peer in the room shows "✓ Seen by Rust LAN Peer". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,12 +14,30 @@ struct ContentView: View {
|
||||
}
|
||||
feedArea
|
||||
Divider()
|
||||
if !store.receipts.isEmpty {
|
||||
seenByBar
|
||||
Divider()
|
||||
}
|
||||
composer
|
||||
}
|
||||
.frame(minWidth: 480, minHeight: 400)
|
||||
.task { store.connectIfSaved() }
|
||||
}
|
||||
|
||||
// MARK: - Delivery receipts
|
||||
|
||||
private var seenByBar: some View {
|
||||
let names = Array(Set(store.receipts.map(\.name))).sorted()
|
||||
return HStack(spacing: 6) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green).font(.caption)
|
||||
Text("Seen by \(names.joined(separator: ", "))")
|
||||
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 14).padding(.vertical, 5)
|
||||
}
|
||||
|
||||
// MARK: - Nearby devices (AirDrop-style picker)
|
||||
|
||||
private var nearbyStrip: some View {
|
||||
|
||||
@@ -26,6 +26,7 @@ final class MacTetherStore {
|
||||
}()
|
||||
var feed: [FeedItem] = []
|
||||
var nearby: [Peer] = [] // tether devices on the LAN, for the picker
|
||||
var receipts: [Receipt] = [] // delivery acks — "seen by <device>"
|
||||
var connected = false
|
||||
var lastError: String?
|
||||
var hasAttemptedConnect = false
|
||||
@@ -68,6 +69,7 @@ final class MacTetherStore {
|
||||
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 }
|
||||
self.receipts = engine.receipts()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ struct RoomView: View {
|
||||
}
|
||||
feedList
|
||||
Divider()
|
||||
if !store.receipts.isEmpty {
|
||||
seenByBar
|
||||
Divider()
|
||||
}
|
||||
composer
|
||||
}
|
||||
.navigationTitle("tether")
|
||||
@@ -86,6 +90,18 @@ struct RoomView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var seenByBar: some View {
|
||||
let names = Array(Set(store.receipts.map(\.name))).sorted()
|
||||
return HStack(spacing: 6) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green).font(.caption)
|
||||
Text("Seen by \(names.joined(separator: ", "))")
|
||||
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 14).padding(.vertical, 5)
|
||||
}
|
||||
|
||||
private func sourceIcon(_ source: String) -> String {
|
||||
if source.contains("iphone") || source.contains("ios") { return "iphone" }
|
||||
if source.contains("ipad") { return "ipad" }
|
||||
|
||||
@@ -31,6 +31,7 @@ final class TetherStore {
|
||||
}()
|
||||
var feed: [FeedItem] = []
|
||||
var nearby: [Peer] = [] // tether devices on the LAN (empty on iOS w/o multicast entitlement)
|
||||
var receipts: [Receipt] = [] // delivery acks — "seen by <device>"
|
||||
var connected = false
|
||||
var lastError: String?
|
||||
|
||||
@@ -67,6 +68,7 @@ final class TetherStore {
|
||||
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 }
|
||||
self.receipts = engine.receipts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user