import SwiftUI import VoiceCatCore struct ChatView: View { @Bindable var session: SessionState @State private var composeText = "" @State private var scope: VoiceCatTextScope = .channel @State private var privateTargetId: UInt32 = 0 var body: some View { VStack(spacing: 0) { // Message list ScrollViewReader { proxy in ScrollView { LazyVStack(alignment: .leading, spacing: 8) { ForEach(session.messages) { msg in ChatBubble(message: msg) .id(msg.id) } } .padding() } .onChange(of: session.messages.count) { _, _ in if let last = session.messages.last { proxy.scrollTo(last.id, anchor: .bottom) } } } Divider() // Compose bar HStack(spacing: 8) { TextField("Message…", text: $composeText, axis: .vertical) .lineLimit(1...5) .textFieldStyle(.roundedBorder) .accessibilityLabel("Message text field") .onSubmit { sendMessage() } Button { sendMessage() } label: { Image(systemName: "arrow.up.circle.fill") .imageScale(.large) } .disabled(composeText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || session.currentChannelId == 0) .accessibilityLabel("Send message") } .padding(.horizontal) .padding(.vertical, 8) } .navigationTitle("Chat") .navigationBarTitleDisplayMode(.inline) } private func sendMessage() { let text = composeText.trimmingCharacters(in: .whitespacesAndNewlines) guard !text.isEmpty else { return } session.sendText(text, scope: .channel) composeText = "" } } private struct ChatBubble: View { let message: ChatMessage private var timeString: String { let fmt = DateFormatter() fmt.dateStyle = .none fmt.timeStyle = .short return fmt.string(from: message.timestamp) } var body: some View { VStack(alignment: .leading, spacing: 2) { HStack(spacing: 4) { Text(message.senderName) .font(.caption) .fontWeight(.semibold) .foregroundStyle(.secondary) Text(timeString) .font(.caption2) .foregroundStyle(.tertiary) } Text(message.text) .font(.body) .textSelection(.enabled) } .frame(maxWidth: .infinity, alignment: .leading) .accessibilityElement(children: .combine) .accessibilityLabel("\(message.senderName) at \(timeString): \(message.text)") } }