Adapting interfaces for the additive filter.

This commit is contained in:
Vitor Pamplona 2023-04-19 16:35:26 -04:00
parent abdad7fbea
commit 6981fe8f8a
9 changed files with 81 additions and 41 deletions

View File

@ -23,13 +23,13 @@ object ChannelFeedFilter : AdditiveFeedFilter<Note>() {
.reversed()
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
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<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -31,22 +31,22 @@ object ChatroomFeedFilter : AdditiveFeedFilter<Note>() {
.reversed()
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
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<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -19,15 +19,15 @@ abstract class FeedFilter<T> {
}
abstract class AdditiveFeedFilter<T> : FeedFilter<T>() {
abstract fun applyFilter(collection: Set<T>): List<T>
abstract fun sort(collection: List<T>): List<T>
abstract fun applyFilter(collection: Set<T>): Set<T>
abstract fun sort(collection: Set<T>): List<T>
@OptIn(ExperimentalTime::class)
fun updateListWith(oldList: List<T>, newItems: Set<T>): List<T> {
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

View File

@ -15,11 +15,11 @@ object GlobalFeedFilter : AdditiveFeedFilter<Note>() {
return sort(notes + longFormNotes)
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): List<Note> {
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val followChannels = account.followingChannels
val followUsers = account.followingKeySet()
val now = System.currentTimeMillis() / 1000
@ -41,10 +41,10 @@ object GlobalFeedFilter : AdditiveFeedFilter<Note>() {
// 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<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -21,12 +21,12 @@ object HashtagFeedFilter : AdditiveFeedFilter<Note>() {
return sort(innerApplyFilter(LocalCache.notes.values))
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
return applyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): List<Note> {
val myTag = tag ?: return emptyList()
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myTag = tag ?: return emptySet()
return collection
.asSequence()
@ -40,10 +40,10 @@ object HashtagFeedFilter : AdditiveFeedFilter<Note>() {
it.event?.isTaggedHash(myTag) == true
}
.filter { account.isAcceptable(it) }
.toList()
.toSet()
}
override fun sort(collection: List<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -13,11 +13,11 @@ object HomeConversationsFeedFilter : AdditiveFeedFilter<Note>() {
return sort(innerApplyFilter(LocalCache.notes.values))
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): List<Note> {
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val user = account.userProfile()
val followingKeySet = user.cachedFollowingKeySet()
val followingTagSet = user.cachedFollowingTagSet()
@ -31,10 +31,10 @@ object HomeConversationsFeedFilter : AdditiveFeedFilter<Note>() {
it.author?.let { !account.isHidden(it) } ?: true &&
!it.isNewThread()
}
.toList()
.toSet()
}
override fun sort(collection: List<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -18,11 +18,11 @@ object HomeNewThreadFeedFilter : AdditiveFeedFilter<Note>() {
return sort(notes + longFormNotes)
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): List<Note> {
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val user = account.userProfile()
val followingKeySet = user.cachedFollowingKeySet()
val followingTagSet = user.cachedFollowingTagSet()
@ -36,10 +36,10 @@ object HomeNewThreadFeedFilter : AdditiveFeedFilter<Note>() {
it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true &&
it.isNewThread()
}
.toList()
.toSet()
}
override fun sort(collection: List<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}
}

View File

@ -13,11 +13,11 @@ object NotificationFeedFilter : AdditiveFeedFilter<Note>() {
return sort(innerApplyFilter(LocalCache.notes.values))
}
override fun applyFilter(collection: Set<Note>): List<Note> {
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): List<Note> {
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val loggedInUser = account.userProfile()
val loggedInUserHex = loggedInUser.pubkeyHex
@ -31,10 +31,10 @@ object NotificationFeedFilter : AdditiveFeedFilter<Note>() {
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
tagsAnEventByUser(it, loggedInUser)
}
}.toSet()
}
override fun sort(collection: List<Note>): List<Note> {
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedBy { it.createdAt() }.reversed()
}

View File

@ -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<Note>) : ViewModel() {
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
val feedContent = _feedContent.asStateFlow()
@ -45,9 +47,9 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>) : 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<Note>) : 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<Note>): List<Card> {
private fun convertToCard(notes: Collection<Note>): List<Card> {
val reactionsPerEvent = mutableMapOf<Note, MutableList<Note>>()
notes
.filter { it.event is ReactionEvent }
@ -171,6 +173,28 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>) : ViewModel() {
}
}
fun refreshFromOldState(newItems: Set<Note>) {
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,14 +204,30 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>) : ViewModel() {
}
Log.d("Time", "${this.javaClass.simpleName} Card update $elapsed")
}
private val bundlerInsert = BundledInsert<Set<Note>>(250, Dispatchers.IO)
fun invalidateData() {
bundler.invalidate()
}
@OptIn(ExperimentalTime::class)
fun invalidateInsertData(newItems: Set<Note>) {
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<Note>) -> Unit = { newNotes ->
if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) {
invalidateInsertData(newNotes)
} else {
// Refresh Everything
invalidateData()
}
}
init {
LocalCache.live.observeForever(cacheListener)