diff --git a/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt b/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt index 96ba593..84ca83f 100644 --- a/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt +++ b/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -50,6 +51,7 @@ internal fun SearchResultsPane( var restoredScrollPosition by remember { mutableStateOf(false) } var pendingScrollToTop by remember { mutableStateOf(false) } var sawRefreshForPendingScroll by remember { mutableStateOf(false) } + var staleEntries by remember { mutableStateOf(emptyList()) } val imeBottomPadding = with(LocalDensity.current) { WindowInsets.ime.getBottom(this).toDp() } @@ -72,9 +74,28 @@ internal fun SearchResultsPane( restoredScrollPosition = true pendingScrollToTop = query.isNotBlank() sawRefreshForPendingScroll = false + if (query.isBlank()) { + staleEntries = emptyList() + } } } + val loaded = ArrayList(results.itemCount) + for (index in 0 until results.itemCount) { + val entry = results.peek(index) + if (entry != null) { + loaded.add(entry) + } + } + SideEffect { + if (results.loadState.refresh is LoadState.NotLoading && loaded.isNotEmpty()) { + staleEntries = loaded + } + } + val pendingReload = results.itemCount == 0 && + results.loadState.refresh is LoadState.Loading + val shownStale = if (pendingReload && searchState.text.isNotBlank()) staleEntries else emptyList() + LaunchedEffect(results.loadState.refresh, pendingScrollToTop) { if (pendingScrollToTop && results.loadState.refresh is LoadState.Loading) { sawRefreshForPendingScroll = true @@ -125,6 +146,7 @@ internal fun SearchResultsPane( listState = listState, showDictionaryName = showDictionaryName, contentPadding = PaddingValues(bottom = imeBottomPadding), + staleEntries = shownStale, ) } } diff --git a/app/src/main/java/com/example/research/ui/main/components/SearchResultsList.kt b/app/src/main/java/com/example/research/ui/main/components/SearchResultsList.kt index 6b9756c..639db2f 100644 --- a/app/src/main/java/com/example/research/ui/main/components/SearchResultsList.kt +++ b/app/src/main/java/com/example/research/ui/main/components/SearchResultsList.kt @@ -30,8 +30,11 @@ fun SearchResultsList( modifier: Modifier = Modifier, listState: LazyListState = rememberLazyListState(), showDictionaryName: Boolean = true, - contentPadding: PaddingValues = PaddingValues() + contentPadding: PaddingValues = PaddingValues(), + staleEntries: List = emptyList(), ) { + val showStale = staleEntries.isNotEmpty() && results.itemCount == 0 + val rowCount = if (showStale) staleEntries.size else results.itemCount LazyColumn( modifier = modifier .testTag("search_results") @@ -41,11 +44,18 @@ fun SearchResultsList( verticalArrangement = Arrangement.spacedBy(4.dp) ) { items( - count = results.itemCount, - key = results.itemKey { "${it.dictionaryPath}:${it.offset.value}:${it.word}" }, - contentType = results.itemContentType { "search_result" } + count = rowCount, + key = { index -> + val entry = if (showStale) staleEntries.getOrNull(index) + else if (index in 0 until results.itemCount) results.peek(index) else null + entry?.let { "${it.dictionaryPath}:${it.offset.value}:${it.word}" } + ?: "search_result_$index" + }, + contentType = { "search_result" } ) { index -> - results[index]?.let { entry -> + val entry = if (showStale) staleEntries.getOrNull(index) + else if (index in 0 until results.itemCount) results.peek(index) else null + if (entry != null) { Box( modifier = Modifier.animateItem( fadeInSpec = if (index < 8) tween(150) else null,