75 lines
2.3 KiB
Swift
75 lines
2.3 KiB
Swift
|
|
import SwiftUI
|
||
|
|
|
||
|
|
/// A single anonymous encounter in the feed.
|
||
|
|
///
|
||
|
|
/// Until mutual consent, the other person is a **silhouette** — a shape, not
|
||
|
|
/// a profile. This visual language is the privacy promise made visible: you
|
||
|
|
/// know a human was here, but you don't know who until they choose to be seen.
|
||
|
|
struct SilhouetteRow: View {
|
||
|
|
let encounter: Encounter
|
||
|
|
/// Called when the user taps "Wave" on a silhouette.
|
||
|
|
var onWave: () -> Void
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
HStack(spacing: 16) {
|
||
|
|
// Silhouette avatar — deliberately anonymous.
|
||
|
|
ZStack {
|
||
|
|
Circle()
|
||
|
|
.fill(Color.gray.opacity(0.25))
|
||
|
|
.frame(width: 48, height: 48)
|
||
|
|
Image(systemName: "person.fill")
|
||
|
|
.foregroundColor(.gray)
|
||
|
|
}
|
||
|
|
|
||
|
|
VStack(alignment: .leading, spacing: 4) {
|
||
|
|
Text(title)
|
||
|
|
.font(.headline)
|
||
|
|
Text(subtitle)
|
||
|
|
.font(.caption)
|
||
|
|
.foregroundColor(.secondary)
|
||
|
|
}
|
||
|
|
|
||
|
|
Spacer()
|
||
|
|
|
||
|
|
// Action depends on status.
|
||
|
|
switch encounter.status {
|
||
|
|
case .silhouette:
|
||
|
|
Button("Wave", action: onWave)
|
||
|
|
.buttonStyle(.borderedProminent)
|
||
|
|
.tint(.green)
|
||
|
|
case .waved:
|
||
|
|
Text("Waved ✓")
|
||
|
|
.font(.caption)
|
||
|
|
.foregroundColor(.green)
|
||
|
|
case .mutual:
|
||
|
|
Image(systemName: "checkmark.circle.fill")
|
||
|
|
.foregroundColor(.green)
|
||
|
|
case .declined:
|
||
|
|
Image(systemName: "xmark.circle.fill")
|
||
|
|
.foregroundColor(.red)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.padding()
|
||
|
|
.background(
|
||
|
|
RoundedRectangle(cornerRadius: 16)
|
||
|
|
.fill(Color(.secondarySystemBackground))
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
private var title: String {
|
||
|
|
switch encounter.status {
|
||
|
|
case .mutual:
|
||
|
|
return "Connected"
|
||
|
|
default:
|
||
|
|
return "Someone nearby"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private var subtitle: String {
|
||
|
|
var parts: [String] = [encounter.tier.title]
|
||
|
|
if let place = encounter.placeHint { parts.append(place) }
|
||
|
|
parts.append(encounter.timestamp.formatted(.relative(presentation: .named)))
|
||
|
|
return parts.joined(separator: " · ")
|
||
|
|
}
|
||
|
|
}
|