Loading app/src/main/java/foundation/e/apps/data/application/search/SearchRepository.kt +6 −10 Original line number Diff line number Diff line Loading @@ -27,18 +27,14 @@ typealias SearchResult = ResultSupreme<Pair<List<Application>, Boolean>> interface SearchRepository { /** * Fetches search results from cleanAPK and GPlay servers and returns them * @param query Query * @return ResultSupreme which contains a Pair<List<FusedApp>, Boolean> where List<FusedApp> * is the app list and [Boolean] indicates more data to load or not. * Fetches search results from CleanAPK. * * @return ResultSupreme containing a Pair<List<Application>, Boolean>, * where List<Application> contains search results and [Boolean] indicates more data to load or not. */ suspend fun getCleanApkSearchResults( query: String ): SearchResult suspend fun getOpenSourceSearchResults(query: String): SearchResult suspend fun getGplaySearchResult( query: String, ): SearchResult suspend fun getPlayStoreSearchResults(query: String): SearchResult suspend fun getSearchSuggestions(query: String): List<SearchSuggestion> } app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt +65 −117 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.exceptions.CleanApkIOException import foundation.e.apps.data.login.exceptions.GPlayIOException import foundation.e.apps.data.playstore.PlayStoreRepository import timber.log.Timber Loading @@ -47,124 +46,93 @@ class SearchRepositoryImpl @Inject constructor( @ApplicationContext lateinit var context: Context /** * Fetches search results from cleanAPK and returns them * @param query Query * @return A ResultSupreme with Pair of list of non-nullable [Application] and * a Boolean signifying if more search results are being loaded. */ override suspend fun getCleanApkSearchResults( query: String, ): SearchResult { var finalSearchResult: SearchResult = ResultSupreme.Error( message = "", exception = CleanApkIOException("Unable to reach CleanAPK API") ) val packageSpecificResults = if (isPackageName(query)) { fetchPackageSpecificResult(query).data?.first ?: emptyList() override suspend fun getOpenSourceSearchResults(query: String): SearchResult { val searchResultsByPackageName = if (isPackageName(query)) { fetchPackageSpecificResult(query).data?.first.orEmpty() } else { emptyList() } val searchResult = mutableListOf<Application>() val searchResultsByKeyword = buildList { if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { finalSearchResult = fetchOpenSourceSearchResult( query, searchResult, packageSpecificResults ) addAll(fetchOpenSourceSearchResult(query, searchResultsByPackageName)) } if (stores.isStoreEnabled(Source.PWA)) { finalSearchResult = fetchPWASearchResult( query, searchResult, packageSpecificResults ) addAll(fetchPwaSearchResult(query, searchResultsByPackageName)) } } if (!stores.isStoreEnabled(Source.OPEN_SOURCE) && !stores.isStoreEnabled(Source.PWA)) { finalSearchResult = ResultSupreme.Success( return buildFinalSearchResult(searchResultsByKeyword, searchResultsByPackageName) } /** * Builds the final search result, combining keyword and package-specific results. */ private fun buildFinalSearchResult( keywordSpecificResults: List<Application>, packageSpecificResults: List<Application>, ): SearchResult { return ResultSupreme.Success( Pair( filterWithKeywordSearch( keywordSpecificResults, packageSpecificResults, "" // Query string no longer needed ), // Add loading indication for Play Store if enabled stores.isStoreEnabled(Source.PLAY_STORE) ) ) } return finalSearchResult } private fun isPackageName(keyword: String): Boolean { val packageNameRegex = ".*\\..*".toRegex() // com.example return packageNameRegex.matches(keyword) } private suspend fun fetchPWASearchResult( private suspend fun fetchPwaSearchResult( query: String, searchResult: MutableList<Application>, packageSpecificResults: List<Application> ): SearchResult { val pwaApps: MutableList<Application> = mutableListOf() packageSpecificResults: List<Application>, ): List<Application> { val result = handleNetworkResult { val apps = stores.getStore(Source.PWA)?.getSearchResults(query) ?: emptyList() stores.getStore(Source.PWA)?.getSearchResults(query).orEmpty() } apps.forEach { applicationDataManager.updateStatus(it) it.source = Source.PWA it.updateType() pwaApps.add(it) return if (result.isSuccess()) { result.data.orEmpty().map { updatePwa(it) } .apply { filterWithKeywordSearch(this, packageSpecificResults, query) } } else { emptyList() } } if (pwaApps.isNotEmpty() || result.getResultStatus() != ResultStatus.OK) { searchResult.addAll(pwaApps) private fun updatePwa(app: Application): Application { applicationDataManager.updateStatus(app) return app.apply { this.source = Source.PWA this.updateType() } return ResultSupreme.create( result.getResultStatus(), Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), stores.isStoreEnabled(Source.PLAY_STORE) ), exception = result.exception ) } private suspend fun fetchOpenSourceSearchResult( query: String, searchResult: MutableList<Application>, packageSpecificResults: List<Application> ): SearchResult { val cleanApkResults = mutableListOf<Application>() packageSpecificResults: List<Application>, ): List<Application> { val result = handleNetworkResult { cleanApkResults.addAll(getCleanAPKSearchResults(query)) cleanApkResults stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(query).orEmpty().map { applicationDataManager.updateStatus(it) it.updateType() it } if (cleanApkResults.isNotEmpty()) { searchResult.addAll(cleanApkResults) } return ResultSupreme.create( result.getResultStatus(), Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), stores.isStoreEnabled(Source.PLAY_STORE) || stores.isStoreEnabled(Source.PWA) ), exception = result.exception ) return if (result.isSuccess()) { result.data.orEmpty().apply { filterWithKeywordSearch(this, packageSpecificResults, query) } } else { emptyList() } } private suspend fun fetchPackageSpecificResult( Loading Loading @@ -230,9 +198,7 @@ class SearchRepositoryImpl @Inject constructor( return finalList } private suspend fun getCleanApkPackageResult( query: String, ): Application? { private suspend fun getCleanApkPackageResult(query: String): Application? { getCleanApkSearchResult(query).let { if (it.isSuccess() && it.data!!.package_name.isNotBlank()) { return it.data!! Loading @@ -242,9 +208,7 @@ class SearchRepositoryImpl @Inject constructor( return null } private suspend fun getGplayPackageResult( query: String, ): Application? { private suspend fun getGplayPackageResult(query: String): Application? { val storeRepository = stores.getStore(Source.PLAY_STORE) as? PlayStoreRepository return storeRepository?.getAppDetailsWeb(query) } Loading @@ -259,7 +223,8 @@ class SearchRepositoryImpl @Inject constructor( private suspend fun getCleanApkSearchResult(packageName: String): ResultSupreme<Application> { var application = Application() val result = handleNetworkResult { val results = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName) ?: emptyList() val results = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName).orEmpty() if (results.isNotEmpty() && results.size == 1) { application = results[0] Loading @@ -282,35 +247,18 @@ class SearchRepositoryImpl @Inject constructor( return searchSuggestions } private suspend fun getCleanAPKSearchResults( keyword: String ): List<Application> { val list = mutableListOf<Application>() val response = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(keyword) ?: emptyList() override suspend fun getPlayStoreSearchResults(query: String): SearchResult { val source = Source.PLAY_STORE response.forEach { applicationDataManager.updateStatus(it) it.updateType() list.add(it) } return list } override suspend fun getGplaySearchResult( query: String, ): SearchResult { val result = handleNetworkResult { if (!stores.isStoreEnabled(Source.PLAY_STORE)) { if (!stores.isStoreEnabled(source)) { return@handleNetworkResult Pair( listOf<Application>(), setOf<SearchBundle.SubBundle>() ) } val searchResults = stores.getStore(Source.PLAY_STORE)?.getSearchResults(query) val searchResults = stores.getStore(source)?.getSearchResults(query) ?: throw IllegalStateException("Could not get store") val apps = replaceWithFDroid(searchResults).toMutableList() Loading app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt +5 −8 Original line number Diff line number Diff line Loading @@ -114,12 +114,10 @@ class SearchViewModel @Inject constructor( * without having to wait for all of the apps. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ private fun fetchCleanApkData( query: String ) { private fun fetchCleanApkData(query: String) { viewModelScope.launch(Dispatchers.IO) { val searchResultSupreme = searchRepository.getCleanApkSearchResults(query) emitFilteredResults(searchResultSupreme) val searchResults = searchRepository.getOpenSourceSearchResults(query) emitFilteredResults(searchResults) } } Loading @@ -141,10 +139,9 @@ class SearchViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { isLoading = true val gplaySearchResult = searchRepository.getGplaySearchResult(query) val playStoreSearchResults = searchRepository.getPlayStoreSearchResults(query) val currentAppList = updateCurrentAppList(gplaySearchResult) val currentAppList = updateCurrentAppList(playStoreSearchResults) val finalResult = ResultSupreme.Success( Pair(currentAppList.toList(), false) Loading app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -182,7 +182,7 @@ class SearchRepositoryImplTest { setupMockingSearchApp(playStoreApps, gplayPackageResult) val searchResultLiveData = searchRepository.getCleanApkSearchResults("com.search.package") searchRepository.getOpenSourceSearchResults("com.search.package") val size = searchResultLiveData.data?.first?.size ?: -2 assertEquals("getSearchResult", 8, size) Loading Loading @@ -270,7 +270,7 @@ class SearchRepositoryImplTest { preferenceManagerModule.isGplaySelectedFake = true val searchResultLiveData = searchRepository.getCleanApkSearchResults("com.search.package") searchRepository.getOpenSourceSearchResults("com.search.package") val size = searchResultLiveData.data?.first?.size ?: -2 assertEquals("getSearchResult", 4, size) Loading Loading
app/src/main/java/foundation/e/apps/data/application/search/SearchRepository.kt +6 −10 Original line number Diff line number Diff line Loading @@ -27,18 +27,14 @@ typealias SearchResult = ResultSupreme<Pair<List<Application>, Boolean>> interface SearchRepository { /** * Fetches search results from cleanAPK and GPlay servers and returns them * @param query Query * @return ResultSupreme which contains a Pair<List<FusedApp>, Boolean> where List<FusedApp> * is the app list and [Boolean] indicates more data to load or not. * Fetches search results from CleanAPK. * * @return ResultSupreme containing a Pair<List<Application>, Boolean>, * where List<Application> contains search results and [Boolean] indicates more data to load or not. */ suspend fun getCleanApkSearchResults( query: String ): SearchResult suspend fun getOpenSourceSearchResults(query: String): SearchResult suspend fun getGplaySearchResult( query: String, ): SearchResult suspend fun getPlayStoreSearchResults(query: String): SearchResult suspend fun getSearchSuggestions(query: String): List<SearchSuggestion> }
app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt +65 −117 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.exceptions.CleanApkIOException import foundation.e.apps.data.login.exceptions.GPlayIOException import foundation.e.apps.data.playstore.PlayStoreRepository import timber.log.Timber Loading @@ -47,124 +46,93 @@ class SearchRepositoryImpl @Inject constructor( @ApplicationContext lateinit var context: Context /** * Fetches search results from cleanAPK and returns them * @param query Query * @return A ResultSupreme with Pair of list of non-nullable [Application] and * a Boolean signifying if more search results are being loaded. */ override suspend fun getCleanApkSearchResults( query: String, ): SearchResult { var finalSearchResult: SearchResult = ResultSupreme.Error( message = "", exception = CleanApkIOException("Unable to reach CleanAPK API") ) val packageSpecificResults = if (isPackageName(query)) { fetchPackageSpecificResult(query).data?.first ?: emptyList() override suspend fun getOpenSourceSearchResults(query: String): SearchResult { val searchResultsByPackageName = if (isPackageName(query)) { fetchPackageSpecificResult(query).data?.first.orEmpty() } else { emptyList() } val searchResult = mutableListOf<Application>() val searchResultsByKeyword = buildList { if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { finalSearchResult = fetchOpenSourceSearchResult( query, searchResult, packageSpecificResults ) addAll(fetchOpenSourceSearchResult(query, searchResultsByPackageName)) } if (stores.isStoreEnabled(Source.PWA)) { finalSearchResult = fetchPWASearchResult( query, searchResult, packageSpecificResults ) addAll(fetchPwaSearchResult(query, searchResultsByPackageName)) } } if (!stores.isStoreEnabled(Source.OPEN_SOURCE) && !stores.isStoreEnabled(Source.PWA)) { finalSearchResult = ResultSupreme.Success( return buildFinalSearchResult(searchResultsByKeyword, searchResultsByPackageName) } /** * Builds the final search result, combining keyword and package-specific results. */ private fun buildFinalSearchResult( keywordSpecificResults: List<Application>, packageSpecificResults: List<Application>, ): SearchResult { return ResultSupreme.Success( Pair( filterWithKeywordSearch( keywordSpecificResults, packageSpecificResults, "" // Query string no longer needed ), // Add loading indication for Play Store if enabled stores.isStoreEnabled(Source.PLAY_STORE) ) ) } return finalSearchResult } private fun isPackageName(keyword: String): Boolean { val packageNameRegex = ".*\\..*".toRegex() // com.example return packageNameRegex.matches(keyword) } private suspend fun fetchPWASearchResult( private suspend fun fetchPwaSearchResult( query: String, searchResult: MutableList<Application>, packageSpecificResults: List<Application> ): SearchResult { val pwaApps: MutableList<Application> = mutableListOf() packageSpecificResults: List<Application>, ): List<Application> { val result = handleNetworkResult { val apps = stores.getStore(Source.PWA)?.getSearchResults(query) ?: emptyList() stores.getStore(Source.PWA)?.getSearchResults(query).orEmpty() } apps.forEach { applicationDataManager.updateStatus(it) it.source = Source.PWA it.updateType() pwaApps.add(it) return if (result.isSuccess()) { result.data.orEmpty().map { updatePwa(it) } .apply { filterWithKeywordSearch(this, packageSpecificResults, query) } } else { emptyList() } } if (pwaApps.isNotEmpty() || result.getResultStatus() != ResultStatus.OK) { searchResult.addAll(pwaApps) private fun updatePwa(app: Application): Application { applicationDataManager.updateStatus(app) return app.apply { this.source = Source.PWA this.updateType() } return ResultSupreme.create( result.getResultStatus(), Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), stores.isStoreEnabled(Source.PLAY_STORE) ), exception = result.exception ) } private suspend fun fetchOpenSourceSearchResult( query: String, searchResult: MutableList<Application>, packageSpecificResults: List<Application> ): SearchResult { val cleanApkResults = mutableListOf<Application>() packageSpecificResults: List<Application>, ): List<Application> { val result = handleNetworkResult { cleanApkResults.addAll(getCleanAPKSearchResults(query)) cleanApkResults stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(query).orEmpty().map { applicationDataManager.updateStatus(it) it.updateType() it } if (cleanApkResults.isNotEmpty()) { searchResult.addAll(cleanApkResults) } return ResultSupreme.create( result.getResultStatus(), Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), stores.isStoreEnabled(Source.PLAY_STORE) || stores.isStoreEnabled(Source.PWA) ), exception = result.exception ) return if (result.isSuccess()) { result.data.orEmpty().apply { filterWithKeywordSearch(this, packageSpecificResults, query) } } else { emptyList() } } private suspend fun fetchPackageSpecificResult( Loading Loading @@ -230,9 +198,7 @@ class SearchRepositoryImpl @Inject constructor( return finalList } private suspend fun getCleanApkPackageResult( query: String, ): Application? { private suspend fun getCleanApkPackageResult(query: String): Application? { getCleanApkSearchResult(query).let { if (it.isSuccess() && it.data!!.package_name.isNotBlank()) { return it.data!! Loading @@ -242,9 +208,7 @@ class SearchRepositoryImpl @Inject constructor( return null } private suspend fun getGplayPackageResult( query: String, ): Application? { private suspend fun getGplayPackageResult(query: String): Application? { val storeRepository = stores.getStore(Source.PLAY_STORE) as? PlayStoreRepository return storeRepository?.getAppDetailsWeb(query) } Loading @@ -259,7 +223,8 @@ class SearchRepositoryImpl @Inject constructor( private suspend fun getCleanApkSearchResult(packageName: String): ResultSupreme<Application> { var application = Application() val result = handleNetworkResult { val results = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName) ?: emptyList() val results = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName).orEmpty() if (results.isNotEmpty() && results.size == 1) { application = results[0] Loading @@ -282,35 +247,18 @@ class SearchRepositoryImpl @Inject constructor( return searchSuggestions } private suspend fun getCleanAPKSearchResults( keyword: String ): List<Application> { val list = mutableListOf<Application>() val response = stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(keyword) ?: emptyList() override suspend fun getPlayStoreSearchResults(query: String): SearchResult { val source = Source.PLAY_STORE response.forEach { applicationDataManager.updateStatus(it) it.updateType() list.add(it) } return list } override suspend fun getGplaySearchResult( query: String, ): SearchResult { val result = handleNetworkResult { if (!stores.isStoreEnabled(Source.PLAY_STORE)) { if (!stores.isStoreEnabled(source)) { return@handleNetworkResult Pair( listOf<Application>(), setOf<SearchBundle.SubBundle>() ) } val searchResults = stores.getStore(Source.PLAY_STORE)?.getSearchResults(query) val searchResults = stores.getStore(source)?.getSearchResults(query) ?: throw IllegalStateException("Could not get store") val apps = replaceWithFDroid(searchResults).toMutableList() Loading
app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt +5 −8 Original line number Diff line number Diff line Loading @@ -114,12 +114,10 @@ class SearchViewModel @Inject constructor( * without having to wait for all of the apps. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ private fun fetchCleanApkData( query: String ) { private fun fetchCleanApkData(query: String) { viewModelScope.launch(Dispatchers.IO) { val searchResultSupreme = searchRepository.getCleanApkSearchResults(query) emitFilteredResults(searchResultSupreme) val searchResults = searchRepository.getOpenSourceSearchResults(query) emitFilteredResults(searchResults) } } Loading @@ -141,10 +139,9 @@ class SearchViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { isLoading = true val gplaySearchResult = searchRepository.getGplaySearchResult(query) val playStoreSearchResults = searchRepository.getPlayStoreSearchResults(query) val currentAppList = updateCurrentAppList(gplaySearchResult) val currentAppList = updateCurrentAppList(playStoreSearchResults) val finalResult = ResultSupreme.Success( Pair(currentAppList.toList(), false) Loading
app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -182,7 +182,7 @@ class SearchRepositoryImplTest { setupMockingSearchApp(playStoreApps, gplayPackageResult) val searchResultLiveData = searchRepository.getCleanApkSearchResults("com.search.package") searchRepository.getOpenSourceSearchResults("com.search.package") val size = searchResultLiveData.data?.first?.size ?: -2 assertEquals("getSearchResult", 8, size) Loading Loading @@ -270,7 +270,7 @@ class SearchRepositoryImplTest { preferenceManagerModule.isGplaySelectedFake = true val searchResultLiveData = searchRepository.getCleanApkSearchResults("com.search.package") searchRepository.getOpenSourceSearchResults("com.search.package") val size = searchResultLiveData.data?.first?.size ?: -2 assertEquals("getSearchResult", 4, size) Loading