diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt index 14e4b0955..aa2921dce 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt @@ -23,13 +23,13 @@ object ChannelFeedFilter : AdditiveFeedFilter() { .reversed() } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return collection - .filter { it.idHex in channel.notes.keys } - .filter { account.isAcceptable(it) } + .filter { it.idHex in channel.notes.keys && account.isAcceptable(it) } + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt index d885ce7e4..f96c68681 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt @@ -31,22 +31,22 @@ object ChatroomFeedFilter : AdditiveFeedFilter() { .reversed() } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { val myAccount = account val myUser = withUser - if (myAccount == null || myUser == null) return emptyList() + if (myAccount == null || myUser == null) return emptySet() val messages = myAccount .userProfile() - .privateChatrooms[myUser] ?: return emptyList() + .privateChatrooms[myUser] ?: return emptySet() return collection - .filter { it in messages.roomMessages } - .filter { account?.isAcceptable(it) == true } + .filter { it in messages.roomMessages && account?.isAcceptable(it) == true } + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.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 9254d766b..505a959ef 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 @@ -19,15 +19,15 @@ abstract class FeedFilter { } abstract class AdditiveFeedFilter : FeedFilter() { - abstract fun applyFilter(collection: Set): List - abstract fun sort(collection: List): List + abstract fun applyFilter(collection: Set): Set + abstract fun sort(collection: Set): List @OptIn(ExperimentalTime::class) fun updateListWith(oldList: List, newItems: Set): List { val (feed, elapsed) = measureTimedValue { val newItemsToBeAdded = applyFilter(newItems) if (newItemsToBeAdded.isNotEmpty()) { - val newList = oldList + newItemsToBeAdded + val newList = oldList.toSet() + newItemsToBeAdded sort(newList).take(1000) } else { oldList diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt index ebd4d2cdf..f17d5ade5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt @@ -15,11 +15,11 @@ object GlobalFeedFilter : AdditiveFeedFilter() { return sort(notes + longFormNotes) } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return innerApplyFilter(collection) } - private fun innerApplyFilter(collection: Collection): List { + private fun innerApplyFilter(collection: Collection): Set { val followChannels = account.followingChannels val followUsers = account.followingKeySet() val now = System.currentTimeMillis() / 1000 @@ -41,10 +41,10 @@ object GlobalFeedFilter : AdditiveFeedFilter() { // Do not show notes with the creation time exceeding the current time, as they will always stay at the top of the global feed, which is cheating. it.createdAt()!! <= now } - .toList() + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HashtagFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HashtagFeedFilter.kt index 32354c408..d4fd4765e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HashtagFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HashtagFeedFilter.kt @@ -21,12 +21,12 @@ object HashtagFeedFilter : AdditiveFeedFilter() { return sort(innerApplyFilter(LocalCache.notes.values)) } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return applyFilter(collection) } - private fun innerApplyFilter(collection: Collection): List { - val myTag = tag ?: return emptyList() + private fun innerApplyFilter(collection: Collection): Set { + val myTag = tag ?: return emptySet() return collection .asSequence() @@ -40,10 +40,10 @@ object HashtagFeedFilter : AdditiveFeedFilter() { it.event?.isTaggedHash(myTag) == true } .filter { account.isAcceptable(it) } - .toList() + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt index f18885612..6026e2669 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt @@ -13,11 +13,11 @@ object HomeConversationsFeedFilter : AdditiveFeedFilter() { return sort(innerApplyFilter(LocalCache.notes.values)) } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return innerApplyFilter(collection) } - private fun innerApplyFilter(collection: Collection): List { + private fun innerApplyFilter(collection: Collection): Set { val user = account.userProfile() val followingKeySet = user.cachedFollowingKeySet() val followingTagSet = user.cachedFollowingTagSet() @@ -31,10 +31,10 @@ object HomeConversationsFeedFilter : AdditiveFeedFilter() { it.author?.let { !account.isHidden(it) } ?: true && !it.isNewThread() } - .toList() + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt index 3ddf1471a..d31a45c68 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt @@ -18,11 +18,11 @@ object HomeNewThreadFeedFilter : AdditiveFeedFilter() { return sort(notes + longFormNotes) } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return innerApplyFilter(collection) } - private fun innerApplyFilter(collection: Collection): List { + private fun innerApplyFilter(collection: Collection): Set { val user = account.userProfile() val followingKeySet = user.cachedFollowingKeySet() val followingTagSet = user.cachedFollowingTagSet() @@ -36,10 +36,10 @@ object HomeNewThreadFeedFilter : AdditiveFeedFilter() { it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true && it.isNewThread() } - .toList() + .toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 8cb1f6280..ba746ebba 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -13,11 +13,11 @@ object NotificationFeedFilter : AdditiveFeedFilter() { return sort(innerApplyFilter(LocalCache.notes.values)) } - override fun applyFilter(collection: Set): List { + override fun applyFilter(collection: Set): Set { return innerApplyFilter(collection) } - private fun innerApplyFilter(collection: Collection): List { + private fun innerApplyFilter(collection: Collection): Set { val loggedInUser = account.userProfile() val loggedInUserHex = loggedInUser.pubkeyHex @@ -31,10 +31,10 @@ object NotificationFeedFilter : AdditiveFeedFilter() { it.event?.isTaggedUser(loggedInUserHex) ?: false && (it.author == null || !account.isHidden(it.author!!.pubkeyHex)) && tagsAnEventByUser(it, loggedInUser) - } + }.toSet() } - override fun sort(collection: List): List { + override fun sort(collection: Set): List { return collection.sortedBy { it.createdAt() }.reversed() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index f2a2eb9cc..f7e1a24c0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -14,7 +14,9 @@ import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.components.BundledUpdate +import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import kotlinx.coroutines.CoroutineScope @@ -29,7 +31,7 @@ import kotlin.time.measureTimedValue class NotificationViewModel : CardFeedViewModel(NotificationFeedFilter) -open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { +open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { private val _feedContent = MutableStateFlow(CardFeedState.Loading) val feedContent = _feedContent.asStateFlow() @@ -45,9 +47,9 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { @Synchronized private fun refreshSuspended() { - val notes = dataSource.loadTop() + val notes = localFilter.loadTop() - val thisAccount = (dataSource as? NotificationFeedFilter)?.account + val thisAccount = (localFilter as? NotificationFeedFilter)?.account val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null val oldNotesState = _feedContent.value @@ -55,18 +57,18 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { val newCards = convertToCard(notes.minus(lastNotesCopy)) if (newCards.isNotEmpty()) { lastNotes = notes - lastAccount = (dataSource as? NotificationFeedFilter)?.account + lastAccount = (localFilter as? NotificationFeedFilter)?.account updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedBy { it.createdAt() }.reversed()) } } else { val cards = convertToCard(notes) lastNotes = notes - lastAccount = (dataSource as? NotificationFeedFilter)?.account + lastAccount = (localFilter as? NotificationFeedFilter)?.account updateFeed(cards) } } - private fun convertToCard(notes: List): List { + private fun convertToCard(notes: Collection): List { val reactionsPerEvent = mutableMapOf>() notes .filter { it.event is ReactionEvent } @@ -171,6 +173,28 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } } + fun refreshFromOldState(newItems: Set) { + val oldNotesState = _feedContent.value + + val thisAccount = (localFilter as? NotificationFeedFilter)?.account + val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null + + if (lastNotesCopy != null && localFilter is AdditiveFeedFilter && oldNotesState is CardFeedState.Loaded) { + val filteredNewList = localFilter.applyFilter(newItems) + val actuallyNew = filteredNewList.minus(lastNotesCopy) + + val newCards = convertToCard(actuallyNew) + if (newCards.isNotEmpty()) { + lastNotes = lastNotesCopy + newItems + lastAccount = (localFilter as? NotificationFeedFilter)?.account + updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedBy { it.createdAt() }.reversed()) + } + } else { + // Refresh Everything + refreshSuspended() + } + } + @OptIn(ExperimentalTime::class) private val bundler = BundledUpdate(250, Dispatchers.IO) { // adds the time to perform the refresh into this delay @@ -180,13 +204,29 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } Log.d("Time", "${this.javaClass.simpleName} Card update $elapsed") } + private val bundlerInsert = BundledInsert>(250, Dispatchers.IO) fun invalidateData() { bundler.invalidate() } + @OptIn(ExperimentalTime::class) + fun invalidateInsertData(newItems: Set) { + bundlerInsert.invalidateList(newItems) { + val (value, elapsed) = measureTimedValue { + refreshFromOldState(it.flatten().toSet()) + } + Log.d("Time", "${this.javaClass.simpleName} Card additive update $elapsed") + } + } + private val cacheListener: (Set) -> Unit = { newNotes -> - invalidateData() + if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) { + invalidateInsertData(newNotes) + } else { + // Refresh Everything + invalidateData() + } } init {