66 lines
2.3 KiB
Swift
66 lines
2.3 KiB
Swift
import Foundation
|
|
import Combine
|
|
|
|
/// Per-dialog observation isolation for ChatDetailView.
|
|
///
|
|
/// Instead of `@ObservedObject messageRepository` (which re-renders on ANY dialog change),
|
|
/// this ViewModel subscribes only to the specific dialog's messages via Combine pipeline
|
|
/// with `removeDuplicates()`. The view re-renders ONLY when its own dialog's data changes.
|
|
@MainActor
|
|
final class ChatDetailViewModel: ObservableObject {
|
|
let dialogKey: String
|
|
|
|
@Published private(set) var messages: [ChatMessage] = []
|
|
@Published private(set) var isTyping: Bool = false
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
init(dialogKey: String) {
|
|
self.dialogKey = dialogKey
|
|
|
|
let repo = MessageRepository.shared
|
|
|
|
// Seed with current values
|
|
messages = repo.messages(for: dialogKey)
|
|
isTyping = repo.isTyping(dialogKey: dialogKey)
|
|
|
|
// Subscribe to messagesByDialog changes, filtered to our dialog only.
|
|
// Broken into steps to help the Swift type-checker.
|
|
let key = dialogKey
|
|
let messagesPublisher = repo.$messagesByDialog
|
|
.map { (dict: [String: [ChatMessage]]) -> [ChatMessage] in
|
|
dict[key] ?? []
|
|
}
|
|
.removeDuplicates { (lhs: [ChatMessage], rhs: [ChatMessage]) -> Bool in
|
|
guard lhs.count == rhs.count else { return false }
|
|
for i in lhs.indices {
|
|
if lhs[i].id != rhs[i].id || lhs[i].deliveryStatus != rhs[i].deliveryStatus {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
messagesPublisher
|
|
.sink { [weak self] newMessages in
|
|
self?.messages = newMessages
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
// Subscribe to typing state changes, filtered to our dialog
|
|
let typingPublisher = repo.$typingDialogs
|
|
.map { (dialogs: Set<String>) -> Bool in
|
|
dialogs.contains(key)
|
|
}
|
|
.removeDuplicates()
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
typingPublisher
|
|
.sink { [weak self] typing in
|
|
self?.isTyping = typing
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
}
|