Loading app/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -214,6 +214,7 @@ dependencies { implementation(libs.navigation.fragment.ktx) implementation(libs.navigation.ui.ktx) implementation(libs.activity.ktx) implementation(libs.paging.runtime.ktx) // Material Design implementation(libs.material) Loading Loading @@ -304,6 +305,7 @@ dependencies { implementation libs.activity.compose implementation libs.lifecycle.viewmodel.compose implementation libs.runtime.livedata implementation libs.paging.compose // Android Studio Preview support for Compose implementation libs.compose.ui.tooling.preview Loading app/src/androidTest/java/foundation/e/apps/ui/compose/components/SearchResultsContentTest.kt +197 −91 Original line number Diff line number Diff line Loading @@ -28,10 +28,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.paging.LoadState import androidx.paging.LoadStates import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.flowOf import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import foundation.e.apps.R import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.data.Ratings Loading @@ -39,10 +49,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.ui.compose.theme.AppTheme import foundation.e.apps.ui.search.v2.SearchTabType import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.util.Locale @RunWith(AndroidJUnit4::class) class SearchResultsContentTest { Loading @@ -51,62 +58,55 @@ class SearchResultsContentTest { @Test fun emptyTabs_renderNothing() { composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = emptyList(), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Hidden App")) ), onTabSelect = {}, fossPagingData = PagingData.empty(), ) } } } composeRule.onAllNodesWithText("Hidden App") .assertCountEquals(0) composeRule.onAllNodesWithText(noAppsText).assertCountEquals(0) } @Test fun selectedTabOutsideTabs_renderNothing() { composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Missing Tab App")) ), onTabSelect = {}, fossPagingData = PagingData.from(listOf(sampleApp("Hidden App"))), ) } } } composeRule.onAllNodesWithText("Missing Tab App") .assertCountEquals(0) composeRule.onAllNodesWithText(noAppsText).assertCountEquals(0) } @Test fun tabSelection_updatesDisplayedResults() { val selectedTabs = mutableListOf<SearchTabType>() val openSourceLabel = composeRule.activity.getString(R.string.search_tab_open_source) val pwaLabel = composeRule.activity.getString(R.string.search_tab_web_apps) composeRule.setContent { var selectedTab by remember { mutableStateOf(SearchTabType.COMMON_APPS) } var selectedTab by remember { mutableStateOf(SearchTabType.OPEN_SOURCE) } val fossItems = remember { flowOf(pagingData(listOf(sampleApp("Open App")))) }.collectAsLazyPagingItems() val pwaItems = remember { flowOf(pagingData(listOf(sampleApp("PWA App")))) }.collectAsLazyPagingItems() AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = listOf(SearchTabType.COMMON_APPS, SearchTabType.OPEN_SOURCE), tabs = listOf(SearchTabType.OPEN_SOURCE, SearchTabType.PWA), selectedTab = selectedTab, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Common App")), SearchTabType.OPEN_SOURCE to listOf(sampleApp("Open App")), ), fossItems = fossItems, pwaItems = pwaItems, searchVersion = 0, getScrollPosition = { null }, onScrollPositionChange = { _, _, _ -> }, onTabSelect = { tab -> selectedTab = tab selectedTabs.add(tab) Loading @@ -116,17 +116,14 @@ class SearchResultsContentTest { } } composeRule.onNodeWithText("Common App") .assertIsDisplayed() composeRule.onNodeWithText(openSourceLabel) .performClick() composeRule.onNodeWithText("Open App").assertIsDisplayed() composeRule.onNodeWithText(openSourceLabel).assertIsDisplayed() composeRule.onNodeWithText(pwaLabel).performClick() composeRule.waitForIdle() composeRule.onNodeWithText("Open App") .assertIsDisplayed() composeRule.onNodeWithText("PWA App").assertIsDisplayed() composeRule.runOnIdle { assertTrue(selectedTabs.contains(SearchTabType.OPEN_SOURCE)) assertTrue(selectedTabs.contains(SearchTabType.PWA)) } } Loading @@ -134,15 +131,14 @@ class SearchResultsContentTest { fun applicationMapping_setsAuthorRatingAndPrimaryAction() { val notAvailable = composeRule.activity.getString(R.string.not_available) val openLabel = composeRule.activity.getString(R.string.open) val expectedRating = String.format(Locale.getDefault(), "%.1f", 4.4) val unexpectedRating = String.format(Locale.getDefault(), "%.1f", 4.9) composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = listOf(SearchTabType.COMMON_APPS), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf( renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData( listOf( Application( name = "Rated App", author = "", Loading @@ -168,24 +164,134 @@ class SearchResultsContentTest { status = Status.UPDATABLE, ), ) ), onTabSelect = {}, ) ) composeRule.onNodeWithText("com.example.rated").assertIsDisplayed() composeRule.onNodeWithText(expectedRating).assertIsDisplayed() composeRule.onNodeWithText(openLabel).assertIsDisplayed() composeRule.onNodeWithText(notAvailable).assertIsDisplayed() composeRule.onAllNodesWithText(unexpectedRating).assertCountEquals(0) } @Test fun refreshLoading_showsShimmer() { val pagingData = PagingData.empty<Application>( sourceLoadStates = loadStates(refresh = LoadState.Loading) ) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithTag(SearchResultsContentTestTags.REFRESH_LOADER) .assertIsDisplayed() 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() } composeRule.onNodeWithText("com.example.rated") .assertIsDisplayed() composeRule.onNodeWithText("4.4") .assertIsDisplayed() composeRule.onNodeWithText(openLabel) .assertIsDisplayed() composeRule.onNodeWithText(notAvailable) @Test fun emptyResults_showsPlaceholder() { val pagingData = PagingData.empty<Application>( sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = true) ) ) val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithText(noAppsText).assertIsDisplayed() } @Test fun appendLoading_showsBottomSpinner() { val pagingData = PagingData.from( listOf(sampleApp("Open App")), sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = false), append = LoadState.Loading ) ) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithText("Open App").assertIsDisplayed() composeRule.onNodeWithTag(SearchResultsContentTestTags.APPEND_LOADER) .assertIsDisplayed() composeRule.onAllNodesWithText("4.9") .assertCountEquals(0) } private fun renderSearchResults( tabs: List<SearchTabType>, selectedTab: SearchTabType, fossPagingData: PagingData<Application>, pwaPagingData: PagingData<Application>? = null, searchVersion: Int = 0, ) { composeRule.setContent { val fossItems = remember { flowOf(fossPagingData) }.collectAsLazyPagingItems() val pwaItems = pwaPagingData?.let { remember(it) { flowOf(it) }.collectAsLazyPagingItems() } AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = tabs, selectedTab = selectedTab, fossItems = fossItems, pwaItems = pwaItems, searchVersion = searchVersion, getScrollPosition = { null }, onScrollPositionChange = { _, _, _ -> }, onTabSelect = {}, ) } } } } private fun loadStates( refresh: LoadState, append: LoadState = LoadState.NotLoading(endOfPaginationReached = true), prepend: LoadState = LoadState.NotLoading(endOfPaginationReached = true), ) = LoadStates(refresh = refresh, prepend = prepend, append = append) private fun sampleApp(name: String) = Application(name = name) private fun pagingData(apps: List<Application>) = PagingData.from( apps, sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = false) ) ) } app/src/main/java/foundation/e/apps/data/application/data/Application.kt +18 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import androidx.core.net.toUri import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.ContentRating import com.google.gson.annotations.SerializedName import foundation.e.apps.data.cleanapk.CleanApkRetrofit import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status Loading Loading @@ -99,6 +100,23 @@ data class Application( val antiFeatures: List<Map<String, String>> = emptyList(), var isSystemApp: Boolean = false, ) { val iconUrl: String? get() { if (icon_image_path.isBlank()) { return null } return when (source) { Source.OPEN_SOURCE, Source.PWA -> { if (icon_image_path.startsWith("http")) { icon_image_path } else { CleanApkRetrofit.ASSET_URL + icon_image_path } } Source.SYSTEM_APP, Source.PLAY_STORE -> icon_image_path } } fun updateType() { this.type = if (this.is_pwa) PWA else NATIVE } Loading app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt +24 −24 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ interface CleanApkRetrofit { @Query("keyword") keyword: String, @Query("source") source: String = APP_SOURCE_FOSS, @Query("type") type: String = APP_TYPE_ANY, @Query("nres") nres: Int = 20, @Query("nres") pageSize: Int = 20, @Query("page") page: Int = 1, @Query("by") by: String? = null, @Query("architectures") architectures: List<String>? = null, Loading app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkSearchHelper.kt +37 −5 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package foundation.e.apps.data.cleanapk import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.cleanapk.data.search.Search import foundation.e.apps.data.cleanapk.repositories.NUMBER_OF_ITEMS import foundation.e.apps.data.cleanapk.repositories.NUMBER_OF_PAGES import foundation.e.apps.data.enums.Source Loading @@ -36,16 +37,47 @@ class CleanApkSearchHelper @Inject constructor( appType: String ): List<Application> { return withContext(Dispatchers.IO) { val searchResult = cleanApkRetrofit.searchApps( getSearchResultPage( keyword = keyword, appSource = appSource, appType = appType, page = NUMBER_OF_PAGES, pageSize = NUMBER_OF_ITEMS, ).apps } } suspend fun getSearchResultPage( keyword: String, appSource: String, appType: String, page: Int, pageSize: Int, ): Search { return withContext(Dispatchers.IO) { val response = cleanApkRetrofit.searchApps( keyword = keyword, source = appSource, type = appType, nres = NUMBER_OF_ITEMS, page = NUMBER_OF_PAGES, pageSize = pageSize, page = page, architectures = SystemInfoProvider.getSupportedArchitectureList(), ) searchResult.body()?.apps.orEmpty() .map { it.apply { source = mapSource(it) } } check(response.isSuccessful) { "CleanAPK search failed: HTTP ${response.code()}" } val body = checkNotNull(response.body()) { "CleanAPK search failed: empty body" } check(body.success) { "CleanAPK search failed: success=false" } body.apps.forEach { app -> app.source = mapSource(app) } body } } Loading Loading
app/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -214,6 +214,7 @@ dependencies { implementation(libs.navigation.fragment.ktx) implementation(libs.navigation.ui.ktx) implementation(libs.activity.ktx) implementation(libs.paging.runtime.ktx) // Material Design implementation(libs.material) Loading Loading @@ -304,6 +305,7 @@ dependencies { implementation libs.activity.compose implementation libs.lifecycle.viewmodel.compose implementation libs.runtime.livedata implementation libs.paging.compose // Android Studio Preview support for Compose implementation libs.compose.ui.tooling.preview Loading
app/src/androidTest/java/foundation/e/apps/ui/compose/components/SearchResultsContentTest.kt +197 −91 Original line number Diff line number Diff line Loading @@ -28,10 +28,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.paging.LoadState import androidx.paging.LoadStates import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.flowOf import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import foundation.e.apps.R import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.data.Ratings Loading @@ -39,10 +49,7 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.ui.compose.theme.AppTheme import foundation.e.apps.ui.search.v2.SearchTabType import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.util.Locale @RunWith(AndroidJUnit4::class) class SearchResultsContentTest { Loading @@ -51,62 +58,55 @@ class SearchResultsContentTest { @Test fun emptyTabs_renderNothing() { composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = emptyList(), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Hidden App")) ), onTabSelect = {}, fossPagingData = PagingData.empty(), ) } } } composeRule.onAllNodesWithText("Hidden App") .assertCountEquals(0) composeRule.onAllNodesWithText(noAppsText).assertCountEquals(0) } @Test fun selectedTabOutsideTabs_renderNothing() { composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Missing Tab App")) ), onTabSelect = {}, fossPagingData = PagingData.from(listOf(sampleApp("Hidden App"))), ) } } } composeRule.onAllNodesWithText("Missing Tab App") .assertCountEquals(0) composeRule.onAllNodesWithText(noAppsText).assertCountEquals(0) } @Test fun tabSelection_updatesDisplayedResults() { val selectedTabs = mutableListOf<SearchTabType>() val openSourceLabel = composeRule.activity.getString(R.string.search_tab_open_source) val pwaLabel = composeRule.activity.getString(R.string.search_tab_web_apps) composeRule.setContent { var selectedTab by remember { mutableStateOf(SearchTabType.COMMON_APPS) } var selectedTab by remember { mutableStateOf(SearchTabType.OPEN_SOURCE) } val fossItems = remember { flowOf(pagingData(listOf(sampleApp("Open App")))) }.collectAsLazyPagingItems() val pwaItems = remember { flowOf(pagingData(listOf(sampleApp("PWA App")))) }.collectAsLazyPagingItems() AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = listOf(SearchTabType.COMMON_APPS, SearchTabType.OPEN_SOURCE), tabs = listOf(SearchTabType.OPEN_SOURCE, SearchTabType.PWA), selectedTab = selectedTab, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf(sampleApp("Common App")), SearchTabType.OPEN_SOURCE to listOf(sampleApp("Open App")), ), fossItems = fossItems, pwaItems = pwaItems, searchVersion = 0, getScrollPosition = { null }, onScrollPositionChange = { _, _, _ -> }, onTabSelect = { tab -> selectedTab = tab selectedTabs.add(tab) Loading @@ -116,17 +116,14 @@ class SearchResultsContentTest { } } composeRule.onNodeWithText("Common App") .assertIsDisplayed() composeRule.onNodeWithText(openSourceLabel) .performClick() composeRule.onNodeWithText("Open App").assertIsDisplayed() composeRule.onNodeWithText(openSourceLabel).assertIsDisplayed() composeRule.onNodeWithText(pwaLabel).performClick() composeRule.waitForIdle() composeRule.onNodeWithText("Open App") .assertIsDisplayed() composeRule.onNodeWithText("PWA App").assertIsDisplayed() composeRule.runOnIdle { assertTrue(selectedTabs.contains(SearchTabType.OPEN_SOURCE)) assertTrue(selectedTabs.contains(SearchTabType.PWA)) } } Loading @@ -134,15 +131,14 @@ class SearchResultsContentTest { fun applicationMapping_setsAuthorRatingAndPrimaryAction() { val notAvailable = composeRule.activity.getString(R.string.not_available) val openLabel = composeRule.activity.getString(R.string.open) val expectedRating = String.format(Locale.getDefault(), "%.1f", 4.4) val unexpectedRating = String.format(Locale.getDefault(), "%.1f", 4.9) composeRule.setContent { AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = listOf(SearchTabType.COMMON_APPS), selectedTab = SearchTabType.COMMON_APPS, resultsByTab = mapOf( SearchTabType.COMMON_APPS to listOf( renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData( listOf( Application( name = "Rated App", author = "", Loading @@ -168,24 +164,134 @@ class SearchResultsContentTest { status = Status.UPDATABLE, ), ) ), onTabSelect = {}, ) ) composeRule.onNodeWithText("com.example.rated").assertIsDisplayed() composeRule.onNodeWithText(expectedRating).assertIsDisplayed() composeRule.onNodeWithText(openLabel).assertIsDisplayed() composeRule.onNodeWithText(notAvailable).assertIsDisplayed() composeRule.onAllNodesWithText(unexpectedRating).assertCountEquals(0) } @Test fun refreshLoading_showsShimmer() { val pagingData = PagingData.empty<Application>( sourceLoadStates = loadStates(refresh = LoadState.Loading) ) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithTag(SearchResultsContentTestTags.REFRESH_LOADER) .assertIsDisplayed() 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() } composeRule.onNodeWithText("com.example.rated") .assertIsDisplayed() composeRule.onNodeWithText("4.4") .assertIsDisplayed() composeRule.onNodeWithText(openLabel) .assertIsDisplayed() composeRule.onNodeWithText(notAvailable) @Test fun emptyResults_showsPlaceholder() { val pagingData = PagingData.empty<Application>( sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = true) ) ) val noAppsText = composeRule.activity.getString(R.string.no_apps_found) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithText(noAppsText).assertIsDisplayed() } @Test fun appendLoading_showsBottomSpinner() { val pagingData = PagingData.from( listOf(sampleApp("Open App")), sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = false), append = LoadState.Loading ) ) renderSearchResults( tabs = listOf(SearchTabType.OPEN_SOURCE), selectedTab = SearchTabType.OPEN_SOURCE, fossPagingData = pagingData, ) composeRule.onNodeWithText("Open App").assertIsDisplayed() composeRule.onNodeWithTag(SearchResultsContentTestTags.APPEND_LOADER) .assertIsDisplayed() composeRule.onAllNodesWithText("4.9") .assertCountEquals(0) } private fun renderSearchResults( tabs: List<SearchTabType>, selectedTab: SearchTabType, fossPagingData: PagingData<Application>, pwaPagingData: PagingData<Application>? = null, searchVersion: Int = 0, ) { composeRule.setContent { val fossItems = remember { flowOf(fossPagingData) }.collectAsLazyPagingItems() val pwaItems = pwaPagingData?.let { remember(it) { flowOf(it) }.collectAsLazyPagingItems() } AppTheme(darkTheme = false) { Surface(color = MaterialTheme.colorScheme.background) { SearchResultsContent( tabs = tabs, selectedTab = selectedTab, fossItems = fossItems, pwaItems = pwaItems, searchVersion = searchVersion, getScrollPosition = { null }, onScrollPositionChange = { _, _, _ -> }, onTabSelect = {}, ) } } } } private fun loadStates( refresh: LoadState, append: LoadState = LoadState.NotLoading(endOfPaginationReached = true), prepend: LoadState = LoadState.NotLoading(endOfPaginationReached = true), ) = LoadStates(refresh = refresh, prepend = prepend, append = append) private fun sampleApp(name: String) = Application(name = name) private fun pagingData(apps: List<Application>) = PagingData.from( apps, sourceLoadStates = loadStates( refresh = LoadState.NotLoading(endOfPaginationReached = false) ) ) }
app/src/main/java/foundation/e/apps/data/application/data/Application.kt +18 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import androidx.core.net.toUri import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.ContentRating import com.google.gson.annotations.SerializedName import foundation.e.apps.data.cleanapk.CleanApkRetrofit import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status Loading Loading @@ -99,6 +100,23 @@ data class Application( val antiFeatures: List<Map<String, String>> = emptyList(), var isSystemApp: Boolean = false, ) { val iconUrl: String? get() { if (icon_image_path.isBlank()) { return null } return when (source) { Source.OPEN_SOURCE, Source.PWA -> { if (icon_image_path.startsWith("http")) { icon_image_path } else { CleanApkRetrofit.ASSET_URL + icon_image_path } } Source.SYSTEM_APP, Source.PLAY_STORE -> icon_image_path } } fun updateType() { this.type = if (this.is_pwa) PWA else NATIVE } Loading
app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt +24 −24 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ interface CleanApkRetrofit { @Query("keyword") keyword: String, @Query("source") source: String = APP_SOURCE_FOSS, @Query("type") type: String = APP_TYPE_ANY, @Query("nres") nres: Int = 20, @Query("nres") pageSize: Int = 20, @Query("page") page: Int = 1, @Query("by") by: String? = null, @Query("architectures") architectures: List<String>? = null, Loading
app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkSearchHelper.kt +37 −5 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package foundation.e.apps.data.cleanapk import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.cleanapk.data.search.Search import foundation.e.apps.data.cleanapk.repositories.NUMBER_OF_ITEMS import foundation.e.apps.data.cleanapk.repositories.NUMBER_OF_PAGES import foundation.e.apps.data.enums.Source Loading @@ -36,16 +37,47 @@ class CleanApkSearchHelper @Inject constructor( appType: String ): List<Application> { return withContext(Dispatchers.IO) { val searchResult = cleanApkRetrofit.searchApps( getSearchResultPage( keyword = keyword, appSource = appSource, appType = appType, page = NUMBER_OF_PAGES, pageSize = NUMBER_OF_ITEMS, ).apps } } suspend fun getSearchResultPage( keyword: String, appSource: String, appType: String, page: Int, pageSize: Int, ): Search { return withContext(Dispatchers.IO) { val response = cleanApkRetrofit.searchApps( keyword = keyword, source = appSource, type = appType, nres = NUMBER_OF_ITEMS, page = NUMBER_OF_PAGES, pageSize = pageSize, page = page, architectures = SystemInfoProvider.getSupportedArchitectureList(), ) searchResult.body()?.apps.orEmpty() .map { it.apply { source = mapSource(it) } } check(response.isSuccessful) { "CleanAPK search failed: HTTP ${response.code()}" } val body = checkNotNull(response.body()) { "CleanAPK search failed: empty body" } check(body.success) { "CleanAPK search failed: success=false" } body.apps.forEach { app -> app.source = mapSource(app) } body } } Loading