diff --git a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+MQTT.swift b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+MQTT.swift index 7c286336..49416f1a 100644 --- a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+MQTT.swift +++ b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+MQTT.swift @@ -33,7 +33,7 @@ extension AccessoryManager { } // Set initial unread message badge states appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages ?? 0 - appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0 + appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages(in: context, skipLastMessageCheck: true) ?? 0 // skipLastMessageCheck=true because we don't update lastMessage on our own connected node } if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true { wantRangeTestPackets = true diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index df49ac0f..28a49e21 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -43,17 +43,21 @@ extension UserEntity { return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } - var unreadMessages: Int { + func unreadMessages(in ctx: NSManagedObjectContext?, skipLastMessageCheck: Bool = false) -> Int { // Most contacts will have no DMs history, so we can return early. - guard self.lastMessage != nil else { return 0; } + // (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 context = PersistenceController.shared.container.viewContext + let context = ctx ?? PersistenceController.shared.container.viewContext // default to viewContext 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) return (try? context.count(for: fetchRequest)) ?? 0 } + // Backwards-compatible property (uses viewContext) + var unreadMessages: Int { unreadMessages(in: nil) } + /// SVG Images for Vendors who are signed project backers var hardwareImage: String? { guard let hwModel else { return nil } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 4d217dc7..e030cd16 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1040,7 +1040,10 @@ func textMessageAppPacket( if newMessage.fromUser != nil && newMessage.toUser != nil { // Set Unread Message Indicators if packet.to == connectedNode { - appState?.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 + let unreadCount = newMessage.toUser?.unreadMessages(in: context, skipLastMessageCheck: true) ?? 0 // skipLastMessageCheck=true because we don't update lastMessage on our own connected node + Task { @MainActor in + appState?.unreadDirectMessages = unreadCount + } } if !(newMessage.fromUser?.mute ?? false) && newMessage.isEmoji == false { // Create an iOS Notification for the received DM message diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 87afb16f..af283d0f 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -39,7 +39,13 @@ struct UserMessageList: View { } try context.save() Logger.data.info("📖 [App] All unread direct messages marked as read for user \(user.num, privacy: .public).") - appState.unreadDirectMessages = user.unreadMessages + + if let connectedPeripheralNum = accessoryManager.activeDeviceNum, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: context), + let connectedUser = connectedNode.user { + appState.unreadDirectMessages = connectedUser.unreadMessages(in: context, skipLastMessageCheck: true) // skipLastMessageCheck=true because we don't update lastMessage on our own connected node + } + context.refresh(user, mergeChanges: true) } catch { Logger.data.error("Failed to read direct messages: \(error.localizedDescription, privacy: .public)")