feat: preload bookmark events via EventFinderFilterAssembler

Subscribe all bookmark and pinned note IDs to the EventFinderFilterAssembler
when the bookmark screen opens, so events are fetched in bulk via a single
REQ rather than loading one-by-one as the user scrolls.

Applied to both BookmarkListScreen (new bookmarks + pinned notes) and
OldBookmarkListScreen (legacy bookmarks).

https://claude.ai/code/session_01C8gevBfB8vLDFoBW3hnPeU
This commit is contained in:
Claude
2026-04-02 19:36:32 +00:00
parent cd11503b1e
commit 0c2a68b86e
2 changed files with 61 additions and 0 deletions
@@ -31,8 +31,10 @@ import androidx.compose.material3.SecondaryScrollableTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -40,6 +42,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -90,6 +93,9 @@ fun BookmarkListScreen(
pinnedNotesFeedViewModel.invalidateData() pinnedNotesFeedViewModel.invalidateData()
} }
// Preload all bookmarked and pinned events so they don't load one-by-one when scrolling
PreloadBookmarkEvents(bookmarkState, pinState, accountViewModel)
RenderBookmarkScreen(publicFeedViewModel, privateFeedViewModel, pinnedNotesFeedViewModel, accountViewModel, nav) RenderBookmarkScreen(publicFeedViewModel, privateFeedViewModel, pinnedNotesFeedViewModel, accountViewModel, nav)
} }
@@ -171,3 +177,28 @@ private fun RenderBookmarkScreen(
} }
} }
} }
@Composable
private fun PreloadBookmarkEvents(
bookmarkState: com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkListState.BookmarkList?,
pinState: List<com.vitorpamplona.amethyst.model.Note>?,
accountViewModel: AccountViewModel,
) {
val eventFinder = accountViewModel.dataSources().eventFinder
val account = accountViewModel.account
val queries =
remember(bookmarkState, pinState) {
val allNotes = (bookmarkState?.public.orEmpty() + bookmarkState?.private.orEmpty() + pinState.orEmpty())
allNotes
.filter { it.event == null }
.map { EventFinderQueryState(it, account) }
}
DisposableEffect(queries) {
eventFinder.subscribe(queries)
onDispose {
eventFinder.unsubscribe(queries)
}
}
}
@@ -36,8 +36,10 @@ import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -45,6 +47,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -81,6 +84,9 @@ fun OldBookmarkListScreen(
privateFeedViewModel.invalidateData() privateFeedViewModel.invalidateData()
} }
// Preload all bookmarked events so they don't load one-by-one when scrolling
PreloadOldBookmarkEvents(bookmarkState, accountViewModel)
RenderOldBookmarkScreen(publicFeedViewModel, privateFeedViewModel, accountViewModel, nav) RenderOldBookmarkScreen(publicFeedViewModel, privateFeedViewModel, accountViewModel, nav)
} }
@@ -172,3 +178,27 @@ private fun RenderOldBookmarkScreen(
} }
} }
} }
@Composable
private fun PreloadOldBookmarkEvents(
bookmarkState: com.vitorpamplona.amethyst.commons.model.nip51Lists.OldBookmarkListState.BookmarkList?,
accountViewModel: AccountViewModel,
) {
val eventFinder = accountViewModel.dataSources().eventFinder
val account = accountViewModel.account
val queries =
remember(bookmarkState) {
val allNotes = bookmarkState?.public.orEmpty() + bookmarkState?.private.orEmpty()
allNotes
.filter { it.event == null }
.map { EventFinderQueryState(it, account) }
}
DisposableEffect(queries) {
eventFinder.subscribe(queries)
onDispose {
eventFinder.unsubscribe(queries)
}
}
}