Files
Nearfield_Friends/Proximity/Views/ChatView.swift

111 lines
3.8 KiB
Swift

import SwiftUI
/// The conversation screen for a revealed connection.
///
/// This is where two people who met in the real world actually talk. The UI
/// is deliberately calm and human no engagement-baiting, no streaks, no
/// algorithms. Just a conversation between two people who chose to meet.
struct ChatView: View {
@EnvironmentObject private var vm: ChatViewModel
@EnvironmentObject private var revealFlow: RevealFlow
@State private var input = ""
let connection: Connection
var body: some View {
VStack(spacing: 0) {
// Header
HStack(spacing: 12) {
Circle()
.fill(Color.green.opacity(0.2))
.frame(width: 40, height: 40)
.overlay(Text(String(connection.displayName.prefix(1))))
VStack(alignment: .leading, spacing: 2) {
Text(connection.displayName)
.font(.headline)
Text("Met in person · \(connection.createdAt, format: .dateTime.month().day())")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "lock.fill")
.font(.caption)
.foregroundColor(.green)
.accessibilityLabel("End-to-end encrypted")
}
.padding()
.background(Color(.secondarySystemBackground))
Divider()
// Messages
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 12) {
ForEach(vm.messages) { message in
MessageBubble(message: message, isMine: message.senderID == "me")
}
}
.padding()
}
.onChange(of: vm.messages.count) { _, _ in
if let last = vm.messages.last {
withAnimation {
proxy.scrollTo(last.id, anchor: .bottom)
}
}
}
}
// Input
HStack(spacing: 12) {
TextField("Say hello…", text: $input, axis: .vertical)
.lineLimit(1...4)
.padding(10)
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 18))
Button {
vm.draft = input
input = ""
Task { await vm.send() }
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: 28))
}
.disabled(input.trimmingCharacters(in: .whitespaces).isEmpty)
}
.padding()
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
// Use the E2E session key established at reveal time.
vm.start(with: connection, sessionKey: revealFlow.currentSessionKey())
}
}
}
/// A single message bubble.
struct MessageBubble: View {
@EnvironmentObject private var vm: ChatViewModel
let message: ChatMessage
let isMine: Bool
var body: some View {
HStack {
if isMine { Spacer() }
Text(vm.text(for: message))
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 18)
.fill(isMine ? Color.green : Color(.secondarySystemBackground))
)
.foregroundColor(isMine ? .white : .primary)
if !isMine { Spacer() }
}
}
}