diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt index 66d44afe4..b0397c6d5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt @@ -1,9 +1,16 @@ package com.vitorpamplona.amethyst.ui.dal +import android.util.Log import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent +import com.vitorpamplona.amethyst.service.model.PrivateDmEvent +import com.vitorpamplona.amethyst.ui.actions.updated +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue -object ChatroomListKnownFeedFilter : FeedFilter() { +object ChatroomListKnownFeedFilter : AdditiveFeedFilter() { lateinit var account: Account // returns the last Note of each user. @@ -35,4 +42,120 @@ object ChatroomListKnownFeedFilter : FeedFilter() { .sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .reversed() } + + + @OptIn(ExperimentalTime::class) + override fun updateListWith(oldList: List, newItems: Set): List { + val (feed, elapsed) = measureTimedValue { + val me = account.userProfile() + + // Gets the latest message by channel from the new items. + val newRelevantPublicMessages = filterRelevantPublicMessages(newItems, account) + + // Gets the latest message by room from the new items. + val newRelevantPrivateMessages = filterRelevantPrivateMessages(newItems, account) + + if (newRelevantPrivateMessages.isEmpty() && newRelevantPublicMessages.isEmpty()) { + return oldList + } + + var myNewList = oldList + + newRelevantPublicMessages.forEach { newNotePair -> + oldList.forEach { oldNote -> + if ( + (newNotePair.key == oldNote.channelHex()) && (newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0) + ) { + myNewList = myNewList.updated(oldNote, newNotePair.value) + } + } + } + + newRelevantPrivateMessages.forEach { newNotePair -> + oldList.forEach { oldNote -> + val oldAuthor = oldNote.author?.pubkeyHex + val oldRecipient = (oldNote.event as? PrivateDmEvent)?.verifiedRecipientPubKey() + + val oldRoom = if (oldAuthor == me.pubkeyHex) oldRecipient else oldAuthor + + if ( + (newNotePair.key == oldRoom) && (newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0) + ) { + myNewList = myNewList.updated(oldNote, newNotePair.value) + } + } + } + + myNewList + } + + Log.d("Time", "${this.javaClass.simpleName} Modified Additive Feed in $elapsed with ${feed.size} objects") + return feed + } + + override fun applyFilter(newItems: Set): Set { + // Gets the latest message by channel from the new items. + val newRelevantPublicMessages = filterRelevantPublicMessages(newItems, account) + + // Gets the latest message by room from the new items. + val newRelevantPrivateMessages = filterRelevantPrivateMessages(newItems, account) + + return if (newRelevantPrivateMessages.isEmpty() && newRelevantPublicMessages.isEmpty()) { + emptySet() + } else { + (newRelevantPrivateMessages.values + newRelevantPublicMessages.values).toSet() + } + } + + private fun filterRelevantPublicMessages(newItems: Set, account: Account): MutableMap { + val followingChannels = account.followingChannels + val newRelevantPublicMessages = mutableMapOf() + newItems.filter { it.event is ChannelMessageEvent }.forEach { newNote -> + newNote.channelHex()?.let { channelHex -> + if (channelHex in followingChannels && account.isAcceptable(newNote)) { + val lastNote = newRelevantPublicMessages.get(channelHex) + if (lastNote != null) { + if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) { + newRelevantPublicMessages.put(channelHex, newNote) + } + } else { + newRelevantPublicMessages.put(channelHex, newNote) + } + } + } + } + return newRelevantPublicMessages + } + + private fun filterRelevantPrivateMessages(newItems: Set, account: Account): MutableMap { + val me = account.userProfile() + val followingKeySet = account.followingKeySet() + + val newRelevantPrivateMessages = mutableMapOf() + newItems.filter { it.event is PrivateDmEvent }.forEach { newNote -> + val newAuthor = newNote.author?.pubkeyHex + val newRecipient = (newNote.event as? PrivateDmEvent)?.verifiedRecipientPubKey() + + val roomUserHex = if (newAuthor == me.pubkeyHex) newRecipient else newAuthor + val roomUser = roomUserHex?.let { LocalCache.users[it] } + + if (roomUserHex != null && (newAuthor == me.pubkeyHex || roomUserHex in followingKeySet || me.hasSentMessagesTo(roomUser)) && !account.isHidden(roomUserHex) ) { + val lastNote = newRelevantPrivateMessages.get(roomUserHex) + if (lastNote != null) { + if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) { + newRelevantPrivateMessages.put(roomUserHex, newNote) + } + } else { + newRelevantPrivateMessages.put(roomUserHex, newNote) + } + } + } + return newRelevantPrivateMessages + } + + override fun sort(collection: Set): List { + return collection + .sortedWith(compareBy({ it.createdAt() }, { it.idHex })) + .reversed() + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/FeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/FeedFilter.kt index 7dde70d4c..4e1916fb6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/FeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/FeedFilter.kt @@ -23,7 +23,7 @@ abstract class AdditiveFeedFilter : FeedFilter() { abstract fun sort(collection: Set): List @OptIn(ExperimentalTime::class) - fun updateListWith(oldList: List, newItems: Set): List { + open fun updateListWith(oldList: List, newItems: Set): List { val (feed, elapsed) = measureTimedValue { val newItemsToBeAdded = applyFilter(newItems) if (newItemsToBeAdded.isNotEmpty()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index ee631624c..145845a88 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -170,7 +170,7 @@ object NotificationLatestItem : LatestItem() { } } -object MessagesLatestItem { +object MessagesLatestItem : LatestItem() { fun hasNewItems( account: Account, cache: NotificationCache, @@ -178,13 +178,11 @@ object MessagesLatestItem { ): Boolean { ChatroomListKnownFeedFilter.account = account - val note = ChatroomListKnownFeedFilter.loadTop().firstOrNull { - it.createdAt() != null && it.channel() == null && it.author != account.userProfile() - } ?: return false + val newestItem = updateNewestItem(newNotes, account, ChatroomListKnownFeedFilter) - val lastTime = cache.load("Room/${note.author?.pubkeyHex}") + val lastTime = cache.load("Room/${newestItem?.author?.pubkeyHex}") - return (note.createdAt() ?: 0) > lastTime + return (newestItem?.createdAt() ?: 0) > lastTime } }