UserMessageList: use @FetchRequest to prevent the N^2 behavior that was happening in calls to allPrivateMessages

This commit is contained in:
Mike Robbins 2025-10-13 15:49:39 -04:00
parent 0fa913551b
commit adc2cdb522
2 changed files with 34 additions and 19 deletions

View file

@ -10,12 +10,20 @@ import CoreData
import MeshtasticProtobufs
extension UserEntity {
var messagePredicate: NSPredicate {
return NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10", self, self)
}
var messageFetchRequest: NSFetchRequest<MessageEntity> {
let fetchRequest = MessageEntity.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
fetchRequest.predicate = messagePredicate
return fetchRequest
}
var messageList: [MessageEntity] {
let context = PersistenceController.shared.container.viewContext
let fetchRequest = MessageEntity.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10", self, self)
let fetchRequest = messageFetchRequest
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
}
@ -26,9 +34,8 @@ extension UserEntity {
// Most recent DM for this user (descending, limit 1)
let context = PersistenceController.shared.container.viewContext
let fetchRequest = MessageEntity.fetchRequest()
let fetchRequest = messageFetchRequest
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: false)]
fetchRequest.predicate = NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10", self, self)
fetchRequest.fetchLimit = 1
return (try? context.fetch(fetchRequest))?.first
@ -48,9 +55,10 @@ extension UserEntity {
// (For our own node, set skipLastMessageCheck=true, because we don't update lastMessage on our own connected node.)
guard self.lastMessage != nil || skipLastMessageCheck else { return 0; }
let fetchRequest = MessageEntity.fetchRequest()
// sort is irrelvant.
fetchRequest.predicate = NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 AND read == false", self, self)
let fetchRequest = messageFetchRequest
fetchRequest.sortDescriptors = [] // sort is irrelvant.
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fetchRequest.predicate!, NSPredicate(format: "read == false")])
return (try? context.count(for: fetchRequest)) ?? 0
}

View file

@ -21,17 +21,21 @@ struct UserMessageList: View {
@State private var messageToHighlight: Int64 = 0
@State private var redrawTapbacksTrigger = UUID()
@AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1
private var allPrivateMessages: [MessageEntity] {
// Cast user.messageList to an array for easier indexing and ForEach.
return user.messageList.compactMap { $0 as MessageEntity }
@FetchRequest private var allPrivateMessages: FetchedResults<MessageEntity>
init(user: UserEntity) {
self.user = user
// Configure fetch request here
let request: NSFetchRequest<MessageEntity> = user.messageFetchRequest
_allPrivateMessages = FetchRequest(fetchRequest: request)
}
func handleInteractionComplete() {
markMessagesAsRead()
redrawTapbacksTrigger = UUID()
}
func markMessagesAsRead() {
do {
for unreadMessage in allPrivateMessages.filter({ !$0.read }) {
@ -51,19 +55,22 @@ struct UserMessageList: View {
Logger.data.error("Failed to read direct messages: \(error.localizedDescription, privacy: .public)")
}
}
var body: some View {
// Cast user.messageList to an array for easier indexing and ForEach.
let messages = allPrivateMessages.compactMap { $0 as MessageEntity }
VStack {
ScrollViewReader { scrollView in
ScrollView {
LazyVStack {
ForEach(allPrivateMessages.indices, id: \.self) { index in
let message = allPrivateMessages[index]
let previousMessage = index > 0 ? allPrivateMessages[index - 1] : nil
ForEach(messages.indices, id: \.self) { index in
let message = messages[index]
let previousMessage = index > 0 ? messages[index - 1] : nil
UserMessageRow(
message: message,
allMessages: allPrivateMessages,
allMessages: messages,
previousMessage: previousMessage,
preferredPeripheralNum: preferredPeripheralNum,
user: user,