Loading app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +72 −16 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshotFlow Loading Loading @@ -322,13 +324,27 @@ private fun PagingPlayStoreResultList( } val loadState = lazyItems.loadState val errorState = loadState.refresh as? LoadState.Error ?: loadState.prepend as? LoadState.Error ?: loadState.append as? LoadState.Error val isRefreshing = loadState.refresh is LoadState.Loading val refreshState = loadState.refresh val refreshError = refreshState as? LoadState.Error val appendError = loadState.append as? LoadState.Error val prependError = loadState.prepend as? LoadState.Error val isRefreshing = refreshState is LoadState.Loading val isAppending = loadState.append is LoadState.Loading val isError = errorState != null val isEmpty = !isRefreshing && !isError && lazyItems.itemCount == 0 val hasLoadedCurrentQuery = remember(searchVersion) { mutableStateOf(false) } LaunchedEffect(searchVersion, refreshState, lazyItems.itemCount) { if (refreshState is LoadState.NotLoading && lazyItems.itemCount > 0) { hasLoadedCurrentQuery.value = true } if (refreshState is LoadState.Loading && lazyItems.itemCount == 0) { hasLoadedCurrentQuery.value = false } } val initialLoadError = refreshError != null && !hasLoadedCurrentQuery.value val showFooterError = hasLoadedCurrentQuery.value && listOf(refreshError, appendError, prependError).any { it != null } val isEmpty = !isRefreshing && refreshError == null && appendError == null && prependError == null && lazyItems.itemCount == 0 Box(modifier = modifier) { when { Loading @@ -336,10 +352,11 @@ private fun PagingPlayStoreResultList( SearchShimmerList() } isError -> { initialLoadError -> { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(), fullScreen = true, ) } Loading Loading @@ -399,6 +416,18 @@ private fun PagingPlayStoreResultList( } } } if (showFooterError) { item(key = "error_footer_play_store") { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), fullScreen = false, ) } } } } } Loading Loading @@ -438,13 +467,27 @@ private fun PagingSearchResultList( } val loadState = lazyItems.loadState val errorState = loadState.refresh as? LoadState.Error ?: loadState.prepend as? LoadState.Error ?: loadState.append as? LoadState.Error val isRefreshing = loadState.refresh is LoadState.Loading val refreshState = loadState.refresh val refreshError = refreshState as? LoadState.Error val appendError = loadState.append as? LoadState.Error val prependError = loadState.prepend as? LoadState.Error val isRefreshing = refreshState is LoadState.Loading val isAppending = loadState.append is LoadState.Loading val isError = errorState != null val isEmpty = !isRefreshing && !isError && lazyItems.itemCount == 0 val hasLoadedCurrentQuery = remember(searchVersion) { mutableStateOf(false) } LaunchedEffect(searchVersion, refreshState, lazyItems.itemCount) { if (refreshState is LoadState.NotLoading && lazyItems.itemCount > 0) { hasLoadedCurrentQuery.value = true } if (refreshState is LoadState.Loading && lazyItems.itemCount == 0) { hasLoadedCurrentQuery.value = false } } val initialLoadError = refreshError != null && !hasLoadedCurrentQuery.value val showFooterError = hasLoadedCurrentQuery.value && listOf(refreshError, appendError, prependError).any { it != null } val isEmpty = !isRefreshing && refreshError == null && appendError == null && prependError == null && lazyItems.itemCount == 0 Box(modifier = modifier) { when { Loading @@ -452,10 +495,11 @@ private fun PagingSearchResultList( SearchShimmerList() } isError -> { initialLoadError -> { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(), fullScreen = true, ) } Loading Loading @@ -516,6 +560,18 @@ private fun PagingSearchResultList( } } } if (showFooterError) { item(key = "error_footer") { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), fullScreen = false, ) } } } } } Loading app/src/main/java/foundation/e/apps/ui/compose/components/search/SearchErrorState.kt +34 −2 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package foundation.e.apps.ui.compose.components.search import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding Loading @@ -13,22 +14,37 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import foundation.e.apps.R import foundation.e.apps.ui.compose.theme.AppTheme @Composable fun SearchErrorState( onRetry: () -> Unit, modifier: Modifier = Modifier, fullScreen: Boolean = true, ) { val containerModifier = if (fullScreen) { modifier.fillMaxSize() } else { modifier.fillMaxWidth() } val contentPadding = if (fullScreen) { PaddingValues(all = 24.dp) } else { PaddingValues(horizontal = 16.dp, vertical = 12.dp) } Box( modifier = modifier.fillMaxSize(), modifier = containerModifier, contentAlignment = Alignment.Center ) { Column( modifier = Modifier .fillMaxWidth() .padding(24.dp), .padding(contentPadding), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), ) { Loading @@ -43,3 +59,19 @@ fun SearchErrorState( } } } @Preview(showBackground = true) @Composable private fun SearchErrorStateFullScreenPreview() { AppTheme { SearchErrorState(onRetry = {}, fullScreen = true) } } @Preview(showBackground = true) @Composable private fun SearchErrorStateFooterPreview() { AppTheme { SearchErrorState(onRetry = {}, fullScreen = false) } } app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt +14 −7 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ class SearchViewModelV2 @Inject constructor( private data class SearchRequest( val query: String, val visibleTabs: List<SearchTabType>, val version: Int, ) private val searchRequests = MutableStateFlow<SearchRequest?>(null) Loading Loading @@ -254,7 +255,9 @@ class SearchViewModelV2 @Inject constructor( val selectedTab = _uiState.value.selectedTab?.takeIf { visibleTabs.contains(it) } ?: visibleTabs.firstOrNull() var nextVersion = _uiState.value.searchVersion + 1 _uiState.update { current -> nextVersion = current.searchVersion + 1 current.copy( query = trimmedQuery, suggestions = emptyList(), Loading @@ -262,14 +265,15 @@ class SearchViewModelV2 @Inject constructor( availableTabs = visibleTabs, selectedTab = selectedTab, hasSubmittedSearch = visibleTabs.isNotEmpty(), searchVersion = current.searchVersion + 1, searchVersion = nextVersion, ) } if (visibleTabs.isNotEmpty()) { searchRequests.value = SearchRequest( query = trimmedQuery, visibleTabs = visibleTabs visibleTabs = visibleTabs, version = nextVersion, ) _scrollPositions.update { emptyMap() } } Loading Loading @@ -299,17 +303,20 @@ class SearchViewModelV2 @Inject constructor( ) } val currentQuery = _uiState.value.query val shouldUpdateRequest = _uiState.value.hasSubmittedSearch && currentQuery.isNotBlank() val currentState = _uiState.value val currentQuery = currentState.query val shouldUpdateRequest = currentState.hasSubmittedSearch && currentQuery.isNotBlank() if (shouldUpdateRequest && visibleTabs.isNotEmpty()) { searchRequests.value = SearchRequest( query = currentQuery, visibleTabs = visibleTabs visibleTabs = visibleTabs, version = currentState.searchVersion, ) } else if (!_uiState.value.hasSubmittedSearch) { } else if (!currentState.hasSubmittedSearch) { searchRequests.value = SearchRequest( query = "", visibleTabs = visibleTabs visibleTabs = visibleTabs, version = currentState.searchVersion, ) } } Loading Loading
app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +72 −16 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshotFlow Loading Loading @@ -322,13 +324,27 @@ private fun PagingPlayStoreResultList( } val loadState = lazyItems.loadState val errorState = loadState.refresh as? LoadState.Error ?: loadState.prepend as? LoadState.Error ?: loadState.append as? LoadState.Error val isRefreshing = loadState.refresh is LoadState.Loading val refreshState = loadState.refresh val refreshError = refreshState as? LoadState.Error val appendError = loadState.append as? LoadState.Error val prependError = loadState.prepend as? LoadState.Error val isRefreshing = refreshState is LoadState.Loading val isAppending = loadState.append is LoadState.Loading val isError = errorState != null val isEmpty = !isRefreshing && !isError && lazyItems.itemCount == 0 val hasLoadedCurrentQuery = remember(searchVersion) { mutableStateOf(false) } LaunchedEffect(searchVersion, refreshState, lazyItems.itemCount) { if (refreshState is LoadState.NotLoading && lazyItems.itemCount > 0) { hasLoadedCurrentQuery.value = true } if (refreshState is LoadState.Loading && lazyItems.itemCount == 0) { hasLoadedCurrentQuery.value = false } } val initialLoadError = refreshError != null && !hasLoadedCurrentQuery.value val showFooterError = hasLoadedCurrentQuery.value && listOf(refreshError, appendError, prependError).any { it != null } val isEmpty = !isRefreshing && refreshError == null && appendError == null && prependError == null && lazyItems.itemCount == 0 Box(modifier = modifier) { when { Loading @@ -336,10 +352,11 @@ private fun PagingPlayStoreResultList( SearchShimmerList() } isError -> { initialLoadError -> { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(), fullScreen = true, ) } Loading Loading @@ -399,6 +416,18 @@ private fun PagingPlayStoreResultList( } } } if (showFooterError) { item(key = "error_footer_play_store") { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), fullScreen = false, ) } } } } } Loading Loading @@ -438,13 +467,27 @@ private fun PagingSearchResultList( } val loadState = lazyItems.loadState val errorState = loadState.refresh as? LoadState.Error ?: loadState.prepend as? LoadState.Error ?: loadState.append as? LoadState.Error val isRefreshing = loadState.refresh is LoadState.Loading val refreshState = loadState.refresh val refreshError = refreshState as? LoadState.Error val appendError = loadState.append as? LoadState.Error val prependError = loadState.prepend as? LoadState.Error val isRefreshing = refreshState is LoadState.Loading val isAppending = loadState.append is LoadState.Loading val isError = errorState != null val isEmpty = !isRefreshing && !isError && lazyItems.itemCount == 0 val hasLoadedCurrentQuery = remember(searchVersion) { mutableStateOf(false) } LaunchedEffect(searchVersion, refreshState, lazyItems.itemCount) { if (refreshState is LoadState.NotLoading && lazyItems.itemCount > 0) { hasLoadedCurrentQuery.value = true } if (refreshState is LoadState.Loading && lazyItems.itemCount == 0) { hasLoadedCurrentQuery.value = false } } val initialLoadError = refreshError != null && !hasLoadedCurrentQuery.value val showFooterError = hasLoadedCurrentQuery.value && listOf(refreshError, appendError, prependError).any { it != null } val isEmpty = !isRefreshing && refreshError == null && appendError == null && prependError == null && lazyItems.itemCount == 0 Box(modifier = modifier) { when { Loading @@ -452,10 +495,11 @@ private fun PagingSearchResultList( SearchShimmerList() } isError -> { initialLoadError -> { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(), fullScreen = true, ) } Loading Loading @@ -516,6 +560,18 @@ private fun PagingSearchResultList( } } } if (showFooterError) { item(key = "error_footer") { SearchErrorState( onRetry = { lazyItems.retry() }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), fullScreen = false, ) } } } } } Loading
app/src/main/java/foundation/e/apps/ui/compose/components/search/SearchErrorState.kt +34 −2 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package foundation.e.apps.ui.compose.components.search import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding Loading @@ -13,22 +14,37 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import foundation.e.apps.R import foundation.e.apps.ui.compose.theme.AppTheme @Composable fun SearchErrorState( onRetry: () -> Unit, modifier: Modifier = Modifier, fullScreen: Boolean = true, ) { val containerModifier = if (fullScreen) { modifier.fillMaxSize() } else { modifier.fillMaxWidth() } val contentPadding = if (fullScreen) { PaddingValues(all = 24.dp) } else { PaddingValues(horizontal = 16.dp, vertical = 12.dp) } Box( modifier = modifier.fillMaxSize(), modifier = containerModifier, contentAlignment = Alignment.Center ) { Column( modifier = Modifier .fillMaxWidth() .padding(24.dp), .padding(contentPadding), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), ) { Loading @@ -43,3 +59,19 @@ fun SearchErrorState( } } } @Preview(showBackground = true) @Composable private fun SearchErrorStateFullScreenPreview() { AppTheme { SearchErrorState(onRetry = {}, fullScreen = true) } } @Preview(showBackground = true) @Composable private fun SearchErrorStateFooterPreview() { AppTheme { SearchErrorState(onRetry = {}, fullScreen = false) } }
app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt +14 −7 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ class SearchViewModelV2 @Inject constructor( private data class SearchRequest( val query: String, val visibleTabs: List<SearchTabType>, val version: Int, ) private val searchRequests = MutableStateFlow<SearchRequest?>(null) Loading Loading @@ -254,7 +255,9 @@ class SearchViewModelV2 @Inject constructor( val selectedTab = _uiState.value.selectedTab?.takeIf { visibleTabs.contains(it) } ?: visibleTabs.firstOrNull() var nextVersion = _uiState.value.searchVersion + 1 _uiState.update { current -> nextVersion = current.searchVersion + 1 current.copy( query = trimmedQuery, suggestions = emptyList(), Loading @@ -262,14 +265,15 @@ class SearchViewModelV2 @Inject constructor( availableTabs = visibleTabs, selectedTab = selectedTab, hasSubmittedSearch = visibleTabs.isNotEmpty(), searchVersion = current.searchVersion + 1, searchVersion = nextVersion, ) } if (visibleTabs.isNotEmpty()) { searchRequests.value = SearchRequest( query = trimmedQuery, visibleTabs = visibleTabs visibleTabs = visibleTabs, version = nextVersion, ) _scrollPositions.update { emptyMap() } } Loading Loading @@ -299,17 +303,20 @@ class SearchViewModelV2 @Inject constructor( ) } val currentQuery = _uiState.value.query val shouldUpdateRequest = _uiState.value.hasSubmittedSearch && currentQuery.isNotBlank() val currentState = _uiState.value val currentQuery = currentState.query val shouldUpdateRequest = currentState.hasSubmittedSearch && currentQuery.isNotBlank() if (shouldUpdateRequest && visibleTabs.isNotEmpty()) { searchRequests.value = SearchRequest( query = currentQuery, visibleTabs = visibleTabs visibleTabs = visibleTabs, version = currentState.searchVersion, ) } else if (!_uiState.value.hasSubmittedSearch) { } else if (!currentState.hasSubmittedSearch) { searchRequests.value = SearchRequest( query = "", visibleTabs = visibleTabs visibleTabs = visibleTabs, version = currentState.searchVersion, ) } } Loading