From d076ebb092221e740f237867d89cde7a367504bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Thu, 2 Nov 2023 09:01:04 +0100 Subject: [PATCH 1/4] Display "No result" when necessary --- .../demo/ui/integrationLayer/SearchView.kt | 38 ++++++++++++------- .../ui/integrationLayer/SearchViewModel.kt | 25 +++++++++--- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt index 951154aa3..6ba109866 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt @@ -26,9 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -53,14 +51,8 @@ private val bus = listOf(Bu.RTS, Bu.SRF, Bu.RSI, Bu.RTR, Bu.SWI) @Composable fun SearchView(searchViewModel: SearchViewModel, onSearchClicked: (Content.Media) -> Unit) { val lazyItems = searchViewModel.result.collectAsLazyPagingItems() - val currentBu = searchViewModel.bu.collectAsState() - val searchQuery = searchViewModel.query.collectAsState() - var queryState by remember(searchQuery.value) { - mutableStateOf(searchQuery.value) - } - LaunchedEffect(queryState) { - searchViewModel.query.value = queryState - } + val currentBu by searchViewModel.bu.collectAsState() + val searchQuery by searchViewModel.query.collectAsState() val focusRequester = remember { FocusRequester() } Column( modifier = Modifier.padding(8.dp), @@ -81,8 +73,8 @@ fun SearchView(searchViewModel: SearchViewModel, onSearchClicked: (Content.Media singleLine = true, maxLines = 1, placeholder = { Text(text = "Search") }, - value = queryState, - onValueChange = { queryState = it } + value = searchQuery, + onValueChange = searchViewModel::setQuery ) if (lazyItems.itemCount == 0 && lazyItems.loadState.refresh is LoadState.NotLoading) { Box( @@ -97,7 +89,7 @@ fun SearchView(searchViewModel: SearchViewModel, onSearchClicked: (Content.Media SearchResultList( lazyPagingItems = lazyItems, contentClick = onSearchClicked, - currentBu = currentBu.value, + currentBu = currentBu, buClicked = searchViewModel::selectBu ) } @@ -142,6 +134,16 @@ private fun SearchResultList( } } } + // We didn't receive any results + if (lazyPagingItems.itemCount == 1 && lazyPagingItems[0] is SearchContent.BuSelector) { + item { + NoResult( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) + } + } if (lazyPagingItems.loadState.refresh is LoadState.Error) { item { ErrorView(error = (lazyPagingItems.loadState.refresh as LoadState.Error).error) @@ -174,6 +176,16 @@ private fun BuSelector(listBu: List, selectedBu: Bu, onBuSelected: (Bu) -> U } } +@Composable +private fun NoResult(modifier: Modifier = Modifier) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Text(text = "No result") + } +} + @Composable private fun LoadingView(modifier: Modifier = Modifier) { Box(modifier = modifier, contentAlignment = Alignment.Center) { diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt index db06d5679..92be55918 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt @@ -19,6 +19,7 @@ import ch.srgssr.pillarbox.demo.ui.integrationLayer.data.ILRepository import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -31,15 +32,20 @@ import kotlinx.coroutines.flow.map * @constructor Create empty Search view model */ class SearchViewModel(private val ilRepository: ILRepository) : ViewModel() { + private val _bu = MutableStateFlow(Bu.RTS) + /** - * Current selected [Bu]. + * Currently selected [Bu]. */ - val bu = MutableStateFlow(Bu.RTS) + val bu: StateFlow = _bu + + private val _query = MutableStateFlow("") /** * Current search query string. */ - val query = MutableStateFlow("") + val query: StateFlow = _query + private val config = combine(bu, query) { bu, query -> Config(bu, query) } /** @@ -70,7 +76,16 @@ class SearchViewModel(private val ilRepository: ILRepository) : ViewModel() { * Clear search query parameter. */ fun clear() { - query.value = "" + _query.value = "" + } + + /** + * Set the search query. + * + * @param query The search query + */ + fun setQuery(query: String) { + _query.value = query } /** @@ -79,7 +94,7 @@ class SearchViewModel(private val ilRepository: ILRepository) : ViewModel() { * @param bu The [Bu] to select. */ fun selectBu(bu: Bu) { - this.bu.value = bu + _bu.value = bu } internal data class Config(val bu: Bu, val query: String) From ed03b0294cd120ad05ac4e5c74201c5b8ea5e704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Thu, 2 Nov 2023 11:17:34 +0100 Subject: [PATCH 2/4] Only display "No result" in the `NotLoading` state --- .../pillarbox/demo/ui/integrationLayer/SearchView.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt index 6ba109866..0e8615843 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchView.kt @@ -107,6 +107,10 @@ private fun SearchResultList( buClicked: (Bu) -> Unit, modifier: Modifier = Modifier ) { + val hasNoResult = lazyPagingItems.loadState.refresh is LoadState.NotLoading && + lazyPagingItems.itemCount == 1 && + lazyPagingItems[0] is SearchContent.BuSelector + LazyColumn(modifier = modifier) { items(count = lazyPagingItems.itemCount, key = lazyPagingItems.itemKey()) { index -> val item = lazyPagingItems[index] @@ -134,8 +138,7 @@ private fun SearchResultList( } } } - // We didn't receive any results - if (lazyPagingItems.itemCount == 1 && lazyPagingItems[0] is SearchContent.BuSelector) { + if (hasNoResult) { item { NoResult( modifier = Modifier From 8ae728a57b79d6d255500bd24bc7e426ee19f60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 2 Nov 2023 13:32:36 +0100 Subject: [PATCH 3/4] Effectively don't trigger search when query <3 --- .../pillarbox/demo/ui/integrationLayer/SearchViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt index 92be55918..c0320fa5a 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt @@ -53,7 +53,7 @@ class SearchViewModel(private val ilRepository: ILRepository) : ViewModel() { */ @OptIn(ExperimentalCoroutinesApi::class) val result: Flow> = config.flatMapLatest { config -> - if (config.query.isNotBlank() || config.query.length >= 3) { + if (config.query.length >= 3) { ilRepository.search(config.bu, config.query).map { mediaPagingData -> val pagingData: PagingData = mediaPagingData.map { item -> SearchContent.MediaResult(Content.Media(item)) } pagingData.insertHeaderItem(item = SearchContent.BuSelector) From a73b34b8a1d3c63ae131a8b2a7177205a5b19c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 2 Nov 2023 13:33:25 +0100 Subject: [PATCH 4/4] Add a small debounce --- .../pillarbox/demo/ui/integrationLayer/SearchViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt index c0320fa5a..d8bdb44b1 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/integrationLayer/SearchViewModel.kt @@ -21,9 +21,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlin.time.Duration.Companion.milliseconds /** * Search view model to search media for the chosen bu @@ -46,7 +48,7 @@ class SearchViewModel(private val ilRepository: ILRepository) : ViewModel() { */ val query: StateFlow = _query - private val config = combine(bu, query) { bu, query -> Config(bu, query) } + private val config = combine(bu, query) { bu, query -> Config(bu, query) }.debounce(600.milliseconds) /** * Result of the search trigger by [bu] and [query]