import SwiftUI /// Shared native chat history and composer. History stays visible while the /// signaling stream reconnects; only sending depends on current admission. @MainActor public struct ChatView: View { @ObservedObject private var call: Call @StateObject private var draft: ChatDraft public init(call: Call, draft: ChatDraft? = nil) { self.call = call _draft = StateObject(wrappedValue: draft ?? ChatDraft()) } public var body: some View { VStack(alignment: .leading, spacing: 12) { ScrollViewReader { proxy in ScrollView { VStack(alignment: .leading, spacing: 8) { if call.chat.isEmpty { Text("Messages sent during this call appear here.") .foregroundStyle(.secondary) } ForEach(Array(call.chat.enumerated()), id: \.offset) { _, line in Text(line).textSelection(.enabled) .padding(10).frame(maxWidth: .infinity, alignment: .leading) .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 10)) } Color.clear.frame(height: 1).id("chat-bottom") }.frame(maxWidth: .infinity, alignment: .leading) } .accessibilityIdentifier("chatHistory") .defaultScrollAnchor(.bottom) .onAppear { proxy.scrollTo("chat-bottom", anchor: .bottom) } .onChange(of: call.chat) { _, _ in proxy.scrollTo("chat-bottom", anchor: .bottom) } } if call.reconnecting { Text("Reconnecting. You can send again when the call reconnects.") .font(.footnote).foregroundStyle(.secondary) } HStack { TextField("Message to everyone", text: $draft.text) .textFieldStyle(.roundedBorder) .accessibilityIdentifier("chatComposer") .onSubmit(send) Button("Send", systemImage: "arrow.up.circle.fill", action: send) .buttonStyle(.borderedProminent) .disabled(draft.text.isEmpty || call.state?.phase != "admitted") } } } private func send() { guard call.state?.phase == "admitted" else { return } if call.sendChat(draft.text) { draft.text = "" } } }