ChatLogView.swift
struct FirebaseConstants { static let fromId = "fromId" static let toId = "toId" static let text = "text"}struct ChatMessage: Identifiable { var id: String { documentId } let documentId: String let fromId, toId, text: String init(documentId: String, data: [String: Any]) { self.documentId = documentId self.fromId = data[FirebaseConstants.fromId] as? String ?? "" self.toId = data[FirebaseConstants.toId] as? String ?? "" self.text = data[FirebaseConstants.text] as? String ?? "" }}class ChatLogViewModel: ObservableObject { @Published var chatText = "" @Published var errorMessage = "" @Published var chatMessages = [ChatMessage]() let chatUser: ChatUser? init(chatUser: ChatUser?) { self.chatUser = chatUser fetchMessages() } private func fetchMessages() { guard let fromId = FirebaseManager.shared.auth.currentUser?.uid else { return } guard let toId = chatUser?.uid else { return } FirebaseManager.shared.firestore .collection("messages") .document(fromId) .collection(toId) .order(by: "timestamp") .addSnapshotListener { querySnapshot, error in if let error = error { self.errorMessage = "Failed to listen for messages: \(error)" print(error) return } querySnapshot?.documentChanges.forEach({ change in if change.type == .added { let data = change.document.data() self.chatMessages.append(.init(documentId: change.document.documentID, data: data)) } }) } } // ...}struct ChatLogView: View { let chatUser: ChatUser? init(chatUser: ChatUser?) { self.chatUser = chatUser self.vm = .init(chatUser: chatUser) } @ObservedObject var vm: ChatLogViewModel var body: some View { ZStack { messagesView Text(vm.errorMessage) } .navigationTitle(chatUser?.email ?? "") .navigationBarTitleDisplayMode(.inline) } private var messagesView: some View { VStack { if #available(iOS 15.0, *) { ScrollView { ForEach(vm.chatMessages) { message in VStack { if message.fromId == FirebaseManager.shared.auth.currentUser?.uid { HStack { Spacer() HStack { Text(message.text) .foregroundColor(.white) } .padding() .background(Color.blue) .cornerRadius(8) } } else { HStack { HStack { Text(message.text) .foregroundColor(.black) } .padding() .background(Color.white) .cornerRadius(8) Spacer() } } } .padding(.horizontal) .padding(.top, 8) } HStack{ Spacer() } } .background(Color(.init(white: 0.95, alpha: 1))) .safeAreaInset(edge: .bottom) { chatBottomBar .background(Color(.systemBackground).ignoresSafeArea()) } } else { // Fallback on earlier versions } } } private var chatBottomBar: some View { HStack(spacing: 16) { Image(systemName: "photo.on.rectangle") .font(.system(size: 24)) .foregroundColor(Color(.darkGray)) ZStack { DescriptionPlaceholder() TextEditor(text: $vm.chatText) .opacity(vm.chatText.isEmpty ? 0.5 : 1) } .frame(height: 40) Button { vm.handleSend() } label: { Text("Send") .foregroundColor(.white) } .padding(.horizontal) .padding(.vertical, 8) .background(Color.blue) .cornerRadius(4) } .padding(.horizontal) .padding(.vertical, 8) }}