Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9b82d7e6 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury Committed by Nishith Khanna
Browse files

refactor(searchV2): handle error states for PlayStore and CleanAPK

parent 114e686a
Loading
Loading
Loading
Loading
+2 −47
Original line number Diff line number Diff line
@@ -204,51 +204,6 @@ class SearchResultsContentTest {
        composeRule.onAllNodesWithText("Open App").assertCountEquals(0)
    }

    @Test
    fun refreshError_showsRetry() {
        val pagingData = PagingData.empty<Application>(
            sourceLoadStates = loadStates(refresh = LoadState.Error(RuntimeException("boom")))
        )

        renderSearchResults(
            tabs = listOf(SearchTabType.OPEN_SOURCE),
            selectedTab = SearchTabType.OPEN_SOURCE,
            fossPagingData = pagingData,
        )

        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.search_error)
        ).assertIsDisplayed()
        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.retry)
        ).assertIsDisplayed()
    }

    @Test
    fun appendError_showsFooterRetryWithResults() {
        val pagingData = PagingData.from(
            listOf(sampleApp("Loaded App")),
            sourceLoadStates = loadStates(
                refresh = LoadState.NotLoading(endOfPaginationReached = false),
                append = LoadState.Error(RuntimeException("append boom"))
            )
        )

        renderSearchResults(
            tabs = listOf(SearchTabType.OPEN_SOURCE),
            selectedTab = SearchTabType.OPEN_SOURCE,
            fossPagingData = pagingData,
        )

        composeRule.onNodeWithText("Loaded App").assertIsDisplayed()
        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.search_error)
        ).assertIsDisplayed()
        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.retry)
        ).assertIsDisplayed()
    }

    @Test
    fun emptyResults_showsPlaceholder() {
        val pagingData = PagingData.empty<Application>(
@@ -256,7 +211,7 @@ class SearchResultsContentTest {
                refresh = LoadState.NotLoading(endOfPaginationReached = true)
            )
        )
        val noAppsText = composeRule.activity.getString(R.string.no_apps_found)
        val noAppsText = composeRule.activity.getString(R.string.search_empty_results_body)

        renderSearchResults(
            tabs = listOf(SearchTabType.OPEN_SOURCE),
@@ -269,7 +224,7 @@ class SearchResultsContentTest {

    @Test
    fun emptyResults_resetOnNewQuery_showsRefreshLoading() {
        val noAppsText = composeRule.activity.getString(R.string.no_apps_found)
        val noAppsText = composeRule.activity.getString(R.string.search_empty_results_body)
        val emptyPagingData = PagingData.empty<Application>(
            sourceLoadStates = loadStates(
                refresh = LoadState.NotLoading(endOfPaginationReached = true)
+4 −10
Original line number Diff line number Diff line
@@ -41,16 +41,13 @@ class SearchErrorStateTest {
        composeRule.setContent {
            AppTheme(darkTheme = false) {
                Surface(color = MaterialTheme.colorScheme.background) {
                    SearchErrorState(onRetry = {}, fullScreen = true)
                    SearchErrorState(errorTitleStringRes = R.string.search_error_title_opensource, fullScreen = true)
                }
            }
        }

        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.search_error)
        ).assertIsDisplayed()
        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.retry)
            composeRule.activity.getString(R.string.search_error_message)
        ).assertIsDisplayed()
    }

@@ -59,16 +56,13 @@ class SearchErrorStateTest {
        composeRule.setContent {
            AppTheme(darkTheme = false) {
                Surface(color = MaterialTheme.colorScheme.background) {
                    SearchErrorState(onRetry = {}, fullScreen = false)
                    SearchErrorState(errorTitleStringRes = R.string.search_error_title_opensource, fullScreen = false)
                }
            }
        }

        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.search_error)
        ).assertIsDisplayed()
        composeRule.onNodeWithText(
            composeRule.activity.getString(R.string.retry)
            composeRule.activity.getString(R.string.search_error_message)
        ).assertIsDisplayed()
    }
}
+18 −18
Original line number Diff line number Diff line
@@ -18,22 +18,20 @@

package foundation.e.apps.ui.compose.components

import androidx.compose.foundation.Image
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign.Companion.Center
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -41,7 +39,7 @@ import foundation.e.apps.R
import foundation.e.apps.ui.compose.theme.AppTheme

@Composable
fun SearchPlaceholder(modifier: Modifier = Modifier) {
fun SearchPlaceholder(@StringRes stringResource: Int, modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize(),
@@ -51,21 +49,23 @@ fun SearchPlaceholder(modifier: Modifier = Modifier) {
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            Image(
                painter = painterResource(id = R.drawable.ic_error_circular),
                contentDescription = stringResource(id = R.string.menu_search),
                contentScale = ContentScale.Fit,
                modifier = Modifier
                    .padding(bottom = 4.dp)
                    .size(96.dp),
            )
            Text(
                text = stringResource(id = R.string.no_apps_found),
                text = stringResource(id = stringResource),
                style = MaterialTheme.typography.bodyMedium.copy(
                    fontSize = 18.sp
                ),
                color = colorResource(id = R.color.light_grey),
                textAlign = androidx.compose.ui.text.style.TextAlign.Center,
                color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.87f),
                textAlign = Center,
            )
            Text(
                text = stringResource(R.string.search_empty_results_body),
                modifier = Modifier.padding(horizontal = 32.dp),
                textAlign = Center,
                style = MaterialTheme.typography.bodyMedium.copy(
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Normal
                ),
                color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.38f)
            )
        }
    }
@@ -75,6 +75,6 @@ fun SearchPlaceholder(modifier: Modifier = Modifier) {
@Composable
private fun SearchPlaceholderPreview() {
    AppTheme(darkTheme = true) {
        SearchPlaceholder()
        SearchPlaceholder(stringResource = R.string.search_empty_results_title_playstore)
    }
}
+37 −51
Original line number Diff line number Diff line
@@ -187,26 +187,34 @@ private fun SearchTabPage(
    installButtonStateProvider: (Application) -> InstallButtonState,
    modifier: Modifier = Modifier,
) {
    val items: LazyPagingItems<Application>?
    val emptyResultsStringResource: Int
    val errorTitleStringResource: Int

    when (tab) {
        SearchTabType.COMMON_APPS -> {
            items = playStoreItems
            emptyResultsStringResource = R.string.search_empty_results_title_playstore
            errorTitleStringResource = R.string.search_error_title_playstore
        }

        SearchTabType.OPEN_SOURCE -> {
            PagingSearchResultList(
                items = fossItems,
                searchVersion = searchVersion,
                tab = tab,
                getScrollPosition = getScrollPosition,
                onScrollPositionChange = onScrollPositionChange,
                onItemClick = onResultClick,
                onPrimaryActionClick = onPrimaryActionClick,
                onShowMoreClick = onShowMoreClick,
                onPrivacyClick = onPrivacyClick,
                installButtonStateProvider = installButtonStateProvider,
                modifier = modifier,
            )
            items = fossItems
            emptyResultsStringResource = R.string.search_empty_results_title_open_source
            errorTitleStringResource = R.string.search_error_title_opensource
        }

        SearchTabType.PWA -> {
            items = pwaItems
            emptyResultsStringResource = R.string.search_empty_results_title_pwa
            errorTitleStringResource = R.string.search_error_title_pwa
        }
    }

    when (tab) {
        SearchTabType.OPEN_SOURCE, SearchTabType.PWA -> {
            PagingSearchResultList(
                items = pwaItems,
                items = items,
                searchVersion = searchVersion,
                tab = tab,
                getScrollPosition = getScrollPosition,
@@ -216,13 +224,15 @@ private fun SearchTabPage(
                onShowMoreClick = onShowMoreClick,
                onPrivacyClick = onPrivacyClick,
                installButtonStateProvider = installButtonStateProvider,
                emptyResultsStringResource = emptyResultsStringResource,
                errorTitleStringResource = errorTitleStringResource,
                modifier = modifier,
            )
        }

        SearchTabType.COMMON_APPS -> {
            PagingPlayStoreResultList(
                items = playStoreItems,
                items = items,
                searchVersion = searchVersion,
                getScrollPosition = getScrollPosition,
                onScrollPositionChange = onScrollPositionChange,
@@ -231,6 +241,8 @@ private fun SearchTabPage(
                onShowMoreClick = onShowMoreClick,
                onPrivacyClick = onPrivacyClick,
                installButtonStateProvider = installButtonStateProvider,
                emptyResultsStringResource = emptyResultsStringResource,
                errorTitleStringResource = errorTitleStringResource,
                modifier = modifier,
            )
        }
@@ -248,6 +260,8 @@ private fun PagingPlayStoreResultList(
    onShowMoreClick: (Application) -> Unit,
    onPrivacyClick: (Application) -> Unit,
    installButtonStateProvider: (Application) -> InstallButtonState,
    emptyResultsStringResource: Int,
    errorTitleStringResource: Int,
    modifier: Modifier = Modifier,
) {
    val lazyItems = items ?: return
@@ -295,11 +309,7 @@ private fun PagingPlayStoreResultList(
    }

    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

@@ -311,7 +321,7 @@ private fun PagingPlayStoreResultList(

            initialLoadError -> {
                SearchErrorState(
                    onRetry = { lazyItems.retry() },
                    errorTitleStringRes = errorTitleStringResource,
                    modifier = Modifier.fillMaxSize(),
                    fullScreen = true,
                )
@@ -319,6 +329,7 @@ private fun PagingPlayStoreResultList(

            isEmpty -> {
                SearchPlaceholder(
                    stringResource = emptyResultsStringResource,
                    modifier = Modifier
                        .fillMaxWidth()
                        .align(Alignment.Center)
@@ -375,18 +386,6 @@ 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,
                            )
                        }
                    }
                }
            }
        }
@@ -405,6 +404,8 @@ private fun PagingSearchResultList(
    onShowMoreClick: (Application) -> Unit,
    onPrivacyClick: (Application) -> Unit,
    installButtonStateProvider: (Application) -> InstallButtonState,
    emptyResultsStringResource: Int,
    errorTitleStringResource: Int,
    modifier: Modifier = Modifier,
) {
    val lazyItems = items ?: return
@@ -448,11 +449,7 @@ private fun PagingSearchResultList(
    }

    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

@@ -466,7 +463,7 @@ private fun PagingSearchResultList(

            initialLoadError -> {
                SearchErrorState(
                    onRetry = { lazyItems.retry() },
                    errorTitleStringRes = errorTitleStringResource,
                    modifier = Modifier.fillMaxSize(),
                    fullScreen = true,
                )
@@ -474,6 +471,7 @@ private fun PagingSearchResultList(

            isEmpty -> {
                SearchPlaceholder(
                    stringResource = emptyResultsStringResource,
                    modifier = Modifier
                        .fillMaxWidth()
                        .align(Alignment.Center)
@@ -535,18 +533,6 @@ 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,
                            )
                        }
                    }
                }
            }
        }
+21 −26
Original line number Diff line number Diff line
@@ -18,28 +18,30 @@

package foundation.e.apps.ui.compose.components.search

import androidx.annotation.StringRes
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
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import foundation.e.apps.R
import foundation.e.apps.ui.compose.theme.AppTheme

@Composable
fun SearchErrorState(
    onRetry: () -> Unit,
    @StringRes errorTitleStringRes: Int,
    modifier: Modifier = Modifier,
    fullScreen: Boolean = true,
) {
@@ -49,31 +51,29 @@ fun SearchErrorState(
        modifier.fillMaxWidth()
    }

    val contentPadding = if (fullScreen) {
        PaddingValues(all = 24.dp)
    } else {
        PaddingValues(horizontal = 16.dp, vertical = 12.dp)
    }

    Box(
        modifier = containerModifier,
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(contentPadding),
                .fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            Text(
                text = stringResource(id = R.string.search_error),
                style = MaterialTheme.typography.bodyLarge,
                color = MaterialTheme.colorScheme.onBackground,
                text = stringResource(id = errorTitleStringRes),
                fontSize = 18.sp,
                fontWeight = FontWeight.Medium,
                color = MaterialTheme.colorScheme.onPrimary,
            )
            Text(
                modifier = Modifier.padding(horizontal = 24.dp),
                textAlign = TextAlign.Center,
                text = stringResource(id = R.string.search_error_message),
                fontSize = 15.sp,
                color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.38F),
            )
            Button(onClick = onRetry) {
                Text(text = stringResource(id = R.string.retry))
            }
        }
    }
}
@@ -82,14 +82,9 @@ fun SearchErrorState(
@Composable
private fun SearchErrorStateFullScreenPreview() {
    AppTheme {
        SearchErrorState(onRetry = {}, fullScreen = true)
    }
}

@Preview(showBackground = true)
@Composable
private fun SearchErrorStateFooterPreview() {
    AppTheme {
        SearchErrorState(onRetry = {}, fullScreen = false)
        SearchErrorState(
            errorTitleStringRes = R.string.search_error_title_opensource,
            fullScreen = true
        )
    }
}
Loading