From 95708b9b792cffcdd372606ec57bf263fd43e47c Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sun, 10 May 2026 13:55:24 +0530 Subject: [PATCH] ref: publish search catalogs as they arrive --- .../app/features/search/SearchRepository.kt | 68 ++++++++++++++++--- .../nuvio/app/features/search/SearchScreen.kt | 5 ++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchRepository.kt index b71d97a2..cee95160 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchRepository.kt @@ -20,11 +20,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import nuvio.composeapp.generated.resources.* import org.jetbrains.compose.resources.getString @@ -91,16 +91,57 @@ object SearchRepository { _uiState.value = SearchUiState(isLoading = true) activeJob = scope.launch { - val results = requests.map { request -> - async { + val resultChannel = Channel(Channel.UNLIMITED) + val jobs = requests.mapIndexed { index, request -> + launch { runCatching { request.toSection() } + .fold( + onSuccess = { section -> + resultChannel.send( + IndexedSearchResult( + index = index, + section = section, + ), + ) + }, + onFailure = { error -> + if (error is CancellationException) throw error + resultChannel.send( + IndexedSearchResult( + index = index, + error = error, + ), + ) + }, + ) } - }.awaitAll() + } + val closeChannelJob = launch { + jobs.joinAll() + resultChannel.close() + } + val results = arrayOfNulls(requests.size) - val sections = results - .mapNotNull { it.getOrNull() } - val firstFailure = results.firstNotNullOfOrNull { it.exceptionOrNull()?.message } - val allFailed = results.isNotEmpty() && results.all { it.isFailure } + try { + for (result in resultChannel) { + results[result.index] = result + val sections = results.orderedSections() + if (sections.isNotEmpty()) { + _uiState.value = SearchUiState( + isLoading = true, + sections = sections, + ) + } + } + } finally { + closeChannelJob.cancel() + resultChannel.close() + } + + val completedResults = results.filterNotNull() + val sections = results.orderedSections() + val firstFailure = completedResults.firstNotNullOfOrNull { it.error?.message } + val allFailed = completedResults.isNotEmpty() && completedResults.all { it.error != null } _uiState.value = SearchUiState( isLoading = false, @@ -436,6 +477,15 @@ object SearchRepository { } } +private data class IndexedSearchResult( + val index: Int, + val section: HomeCatalogSection? = null, + val error: Throwable? = null, +) + +private fun Array.orderedSections(): List = + mapNotNull { result -> result?.section } + private fun CatalogPage.withUnreleasedFilter(): CatalogPage { if (!HomeCatalogSettingsRepository.snapshot().hideUnreleasedContent) return this val filteredItems = items.filterReleasedItems(CurrentDateProvider.todayIsoDate()) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt index bad6cc11..26a3c82f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchScreen.kt @@ -334,6 +334,11 @@ fun SearchScreen( onPosterLongClick = onPosterLongClick, ) } + if (uiState.isLoading) { + item(key = "search_loading_more") { + HomeSkeletonRow(modifier = Modifier.padding(horizontal = homeSectionPadding)) + } + } } } }