From 26cadfee88061908faa9a14115682ec5e1cd6344 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 1 Aug 2025 11:40:19 -0400 Subject: [PATCH] Fixes the index after deletions --- .../amethyst/model/LocalCache.kt | 55 ++++++++----------- .../amethyst/ui/feeds/FeedContentState.kt | 7 +++ .../userSuggestions/ShowUserSuggestionList.kt | 14 ++++- .../amethyst/ui/screen/FeedViewModel.kt | 7 +++ .../amethyst/ui/screen/UserFeedViewModel.kt | 7 +++ .../loggedIn/DecryptAndIndexProcessor.kt | 11 +--- .../profile/zaps/LnZapFeedViewModel.kt | 9 +-- .../ui/screen/loggedIn/search/SearchScreen.kt | 18 +++++- .../loggedIn/settings/StringFeedViewModel.kt | 6 ++ 9 files changed, 84 insertions(+), 50 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index e3e40c6aa..55cdc7830 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -200,9 +200,6 @@ import kotlinx.coroutines.launch import java.io.File import java.io.FileOutputStream import java.io.IOException -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter import java.util.concurrent.ConcurrentHashMap interface ILocalCache { @@ -520,12 +517,6 @@ object LocalCache : ILocalCache { wasVerified: Boolean, ) = consumeBaseReplaceable(event, relay, wasVerified) - fun formattedDateTime(timestamp: Long): String = - Instant - .ofEpochSecond(timestamp) - .atZone(ZoneId.systemDefault()) - .format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a")) - fun consume( event: TextNoteEvent, relay: NormalizedRelayUrl? = null, @@ -831,7 +822,7 @@ object LocalCache : ILocalCache { event.mapTaggedEventId { checkGetOrCreateNote(it) } + event.mapTaggedAddress { checkGetOrCreateAddressableNote(it) } } - else -> emptyList() + else -> emptyList() } fun consume( @@ -1339,6 +1330,9 @@ object LocalCache : ILocalCache { masterNote.removeReport(deleteNote) } + deleteNote.inChatroom?.removeMessageSync(deleteNote) + deleteNote.inChannel?.removeNote(deleteNote) + getAnyChannel(deleteNote)?.removeNote(deleteNote) (deletedEvent as? TorrentCommentEvent)?.torrentIds()?.let { @@ -1357,18 +1351,18 @@ object LocalCache : ILocalCache { } fun deleteWraps(event: WrappedEvent) { - event.host?.let { + event.host?.let { hostStub -> // seal - getNoteIfExists(it.id)?.let { - val noteEvent = it.event + getNoteIfExists(hostStub.id)?.let { hostNote -> + val noteEvent = hostNote.event if (noteEvent is WrappedEvent) { deleteWraps(noteEvent) } - it.clearFlow() - refreshDeletedNoteObservers(it) + hostNote.clearFlow() + refreshDeletedNoteObservers(hostNote) } - notes.remove(it.id) + notes.remove(hostStub.id) } } @@ -1590,7 +1584,7 @@ object LocalCache : ILocalCache { oldChannel.updateChannelInfo(author, event) true } else { - wasVerified + false } } else { wasVerified @@ -2055,11 +2049,7 @@ object LocalCache : ILocalCache { if (note.event?.tags?.tagValueContains(text, true) == true || note.idHex.startsWith(text, true) ) { - if (!note.isHiddenFor(hiddenUsers.flow.value)) { - return@filter true - } else { - return@filter false - } + return@filter !note.isHiddenFor(hiddenUsers.flow.value) } if (note.event?.isContentEncoded() == false) { @@ -2080,11 +2070,7 @@ object LocalCache : ILocalCache { if (addressable.event?.tags?.tagValueContains(text, true) == true || addressable.idHex.startsWith(text, true) ) { - if (!addressable.isHiddenFor(hiddenUsers.flow.value)) { - return@filter true - } else { - return@filter false - } + return@filter !addressable.isHiddenFor(hiddenUsers.flow.value) } if (addressable.event?.isContentEncoded() == false) { @@ -2187,7 +2173,7 @@ object LocalCache : ILocalCache { suspend fun findLatestModificationForNote(note: Note): List { checkNotInMainThread() - val originalAuthor = note.author?.pubkeyHex ?: return emptyList() + val noteAuthor = note.author ?: return emptyList() modificationCache[note.idHex]?.let { return it @@ -2200,7 +2186,7 @@ object LocalCache : ILocalCache { .filter { _, item -> val noteEvent = item.event - noteEvent is TextNoteModificationEvent && note.author == item.author && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time) + noteEvent is TextNoteModificationEvent && noteAuthor == item.author && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time) }.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) modificationCache.put(note.idHex, newNotes) @@ -2315,8 +2301,7 @@ object LocalCache : ILocalCache { val childrenToBeRemoved = mutableListOf() toBeRemoved.forEach { - // TODO: NEED TO TEST IF WRAPS COME BACK WHEN NEEDED BEFORE ACTIVATING - // childrenToBeRemoved.addAll(removeIfWrap(it)) + childrenToBeRemoved.addAll(removeIfWrap(it)) removeFromCache(it) childrenToBeRemoved.addAll(it.removeAllChildNotes()) @@ -2428,6 +2413,9 @@ object LocalCache : ILocalCache { masterNote.removeReport(note) } + note.inChatroom?.removeMessageSync(note) + note.inChannel?.removeNote(note) + val noteEvent = note.event if (noteEvent is LnZapEvent) { @@ -2896,6 +2884,9 @@ class LocalCacheFlow { // Refreshes observers in batches. private val bundler = BundledInsert(1000, Dispatchers.Default) + // Refreshes observers in batches. + private val bundler2 = BundledInsert(1000, Dispatchers.Default) + fun newNote(newNote: Note) { bundler.invalidateList(newNote) { bundledNewNotes -> _newEventBundles.emit(bundledNewNotes) @@ -2903,7 +2894,7 @@ class LocalCacheFlow { } fun removedNote(newNote: Note) { - bundler.invalidateList(newNote) { bundledNewNotes -> + bundler2.invalidateList(newNote) { bundledNewNotes -> _deletedEventBundles.emit(bundledNewNotes) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt index a01ee756b..b2410ce85 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt @@ -126,6 +126,13 @@ class FeedContentState( } } + fun deleteFromFeed(deletedNotes: Set) { + val feed = _feedContent.value + if (feed is FeedState.Loaded) { + updateFeed((feed.feed.value.list - deletedNotes).toImmutableList()) + } + } + fun refreshFromOldState(newItems: Set) { val oldNotesState = _feedContent.value if (localFilter is AdditiveFeedFilter && lastFeedKey == localFilter.feedKey()) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt index 4fffbc7b5..373a6f1d3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt @@ -46,7 +46,10 @@ import com.vitorpamplona.amethyst.ui.note.AboutDisplay import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.AnimateOnNewSearch import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @Composable fun ShowUserSuggestionList( @@ -62,8 +65,15 @@ fun ShowUserSuggestionList( AnimateOnNewSearch(userSuggestions, listState) LaunchedEffect(Unit) { - LocalCache.live.newEventBundles.collect { - userSuggestions.invalidateData() + launch(Dispatchers.Default) { + LocalCache.live.newEventBundles.collect { + userSuggestions.invalidateData() + } + } + launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { + userSuggestions.invalidateData() + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index d71826742..62ad0b4d3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -55,6 +55,13 @@ abstract class FeedViewModel( feedState.updateFeedWith(newNotes) } } + + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Delete from feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}") + feedState.deleteFromFeed(newNotes) + } + } } override fun onCleared() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 45e7da9b5..64d0813cb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -106,6 +106,13 @@ open class UserFeedViewModel( invalidateData() } } + + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Delete from feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}") + invalidateData() + } + } } override fun onCleared() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt index b12107f36..75bac2b2b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt @@ -198,7 +198,8 @@ class DecryptAndIndexProcessor( is DraftEvent -> { // Avoid decrypting over and over again if the event already exist. if (event.pubKey == account.signer.pubKey) { - deindexDraftAsRealEvent(eventNote) + // already deindexed by LocalCache + // deindexDraftAsRealEvent(eventNote) } } @@ -303,14 +304,6 @@ class DecryptAndIndexProcessor( } } - fun deindexDraftAsRealEvent(draftEventWrap: Note) { - draftEventWrap.replyTo?.forEach { it.removeReply(draftEventWrap) } - draftEventWrap.replyTo = null - - draftEventWrap.inChatroom?.removeMessageSync(draftEventWrap) - draftEventWrap.inChannel?.removeNote(draftEventWrap) - } - suspend fun runNew(newNotes: Set) { try { newNotes.forEach { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt index 77b8e7e56..b7769fb46 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/LnZapFeedViewModel.kt @@ -87,11 +87,12 @@ open class LnZapFeedViewModel( init { Log.d("Init", "${this.javaClass.simpleName}") viewModelScope.launch(Dispatchers.IO) { - checkNotInMainThread() - LocalCache.live.newEventBundles.collect { newNotes -> - checkNotInMainThread() - + invalidateData() + } + } + viewModelScope.launch(Dispatchers.IO) { + LocalCache.live.deletedEventBundles.collect { newNotes -> invalidateData() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt index c2f9ae4bc..adc9244d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt @@ -77,7 +77,9 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.StdTopPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.launch @Composable fun SearchScreen( @@ -142,9 +144,19 @@ private fun SearchBar( TextSearchDataSourceSubscription(searchBarViewModel, accountViewModel) LaunchedEffect(Unit) { - LocalCache.live.newEventBundles.collect { - if (searchBarViewModel.isSearchingFun()) { - searchBarViewModel.invalidateData() + launch(Dispatchers.Default) { + LocalCache.live.newEventBundles.collect { + if (searchBarViewModel.isSearchingFun()) { + searchBarViewModel.invalidateData() + } + } + } + + launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { + if (searchBarViewModel.isSearchingFun()) { + searchBarViewModel.invalidateData() + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt index a98bd98ef..607d494b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt @@ -105,6 +105,12 @@ open class StringFeedViewModel( invalidateData() } } + viewModelScope.launch(Dispatchers.Default) { + LocalCache.live.deletedEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Update feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}") + invalidateData() + } + } } override fun onCleared() {