Loading app/src/main/java/foundation/e/apps/data/fused/FusedAPIRepository.kt +11 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package foundation.e.apps.data.fused import androidx.lifecycle.LiveData import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Origin Loading Loading @@ -107,11 +108,18 @@ class FusedAPIRepository @Inject constructor(private val fusedAPIImpl: FusedApi) return fusedAPIImpl.getSearchSuggestions(query) } fun getSearchResults( suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> { return fusedAPIImpl.getSearchResults(query, authData) ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { return fusedAPIImpl.getCleanApkSearchResults(query, authData) } suspend fun getGplaySearchResults( query: String, nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult { return fusedAPIImpl.getGplaySearchResult(query, nextPageSubBundle) } suspend fun getAppsListBasedOnCategory( Loading app/src/main/java/foundation/e/apps/data/fused/FusedApi.kt +12 −5 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.cleanapk.data.download.Download import foundation.e.apps.data.enums.FilterLevel Loading @@ -17,6 +18,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.fusedDownload.models.FusedDownload import retrofit2.Response typealias GplaySearchResult = ResultSupreme<Pair<List<FusedApp>, Set<SearchBundle.SubBundle>>> interface FusedApi { companion object { const val APP_TYPE_ANY = "any" Loading Loading @@ -55,14 +58,18 @@ interface FusedApi { * Fetches search results from cleanAPK and GPlay servers and returns them * @param query Query * @param authData [AuthData] * @return A livedata Pair of list of non-nullable [FusedApp] and * a Boolean signifying if more search results are being loaded. * Observe this livedata to display new apps as they are fetched from the network. * @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. */ fun getSearchResults( suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> suspend fun getGplaySearchResult( query: String, nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> Loading app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt +69 −87 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R Loading Loading @@ -62,7 +63,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.fused.utils.CategoryUtils import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.gplay.GplayStoreRepository import foundation.e.apps.data.gplay.utils.runFlowWithTimeout import foundation.e.apps.data.gplay.utils.GplayHttpRequestException import foundation.e.apps.data.login.exceptions.GPlayException import foundation.e.apps.data.preference.PreferenceManagerModule import foundation.e.apps.install.pkg.PWAManagerModule import foundation.e.apps.install.pkg.PkgManagerModule Loading @@ -71,16 +73,15 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withTimeout import retrofit2.Response import timber.log.Timber import java.net.SocketTimeoutException import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton typealias GplaySearchResultLiveData = LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> typealias FusedHomeDeferred = Deferred<ResultSupreme<List<FusedHome>>> @Singleton Loading @@ -99,6 +100,8 @@ class FusedApiImpl @Inject constructor( private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&" private const val CATEGORY_OPEN_GAMES_ID = "game_open_games" private const val CATEGORY_OPEN_GAMES_TITLE = "Open games" private const val ERROR_GPLAY_SEARCH = "Gplay search has failed!" private const val ERROR_GPLAY_SOURCE_NOT_SELECTED = "Gplay apps are not selected!" } /** Loading Loading @@ -244,22 +247,21 @@ class FusedApiImpl @Inject constructor( * a Boolean signifying if more search results are being loaded. * Observe this livedata to display new apps as they are fetched from the network. */ override fun getSearchResults( override suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> { ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { /* * Returning livedata to improve performance, so that we do not have to wait forever * for all results to be fetched from network before showing them. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ return liveData { val packageSpecificResults = ArrayList<FusedApp>() var finalSearchResult: ResultSupreme<Pair<List<FusedApp>, Boolean>> = ResultSupreme.Error() fetchPackageSpecificResult(authData, query, packageSpecificResults).let { if (it.data?.second != true) { // if there are no data to load emit(it) return@liveData return it } } Loading @@ -267,44 +269,32 @@ class FusedApiImpl @Inject constructor( val cleanApkResults = mutableListOf<FusedApp>() if (preferenceManagerModule.isOpenSourceSelected()) { fetchOpenSourceSearchResult( this@FusedApiImpl, finalSearchResult = fetchOpenSourceSearchResult( cleanApkResults, query, searchResult, packageSpecificResults ).let { emit(it) } ) } if (preferenceManagerModule.isPWASelected()) { fetchPWASearchResult( this@FusedApiImpl, query, searchResult, packageSpecificResults ).let { emit(it) } } if (preferenceManagerModule.isGplaySelected()) { emitSource( fetchGplaySearchResults( finalSearchResult = fetchPWASearchResult( query, searchResult, packageSpecificResults ) ) } } return finalSearchResult } private suspend fun fetchPWASearchResult( fusedAPIImpl: FusedApiImpl, query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { val pwaApps: MutableList<FusedApp> = mutableListOf() val status = fusedAPIImpl.runCodeWithTimeout({ val status = runCodeWithTimeout({ val apps = cleanApkPWARepository.getSearchResult(query).body()?.apps apps?.apply { Loading @@ -331,46 +321,13 @@ class FusedApiImpl @Inject constructor( ) } private suspend fun fetchGplaySearchResults( query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): GplaySearchResultLiveData { return runFlowWithTimeout( { getGplaySearchResult(query) }, { it.second }, { Pair(listOf(), false) // empty data for timeout } ).map { if (it.isSuccess()) { searchResult.addAll(it.data!!.first) ResultSupreme.Success( Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), it.data!!.second ) ) } else { it } } } private suspend fun fetchOpenSourceSearchResult( fusedAPIImpl: FusedApiImpl, cleanApkResults: MutableList<FusedApp>, query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { val status = fusedAPIImpl.runCodeWithTimeout({ val status = runCodeWithTimeout({ cleanApkResults.addAll(getCleanAPKSearchResults(query)) }) Loading Loading @@ -1115,16 +1072,39 @@ class FusedApiImpl @Inject constructor( return list } private suspend fun getGplaySearchResult( override suspend fun getGplaySearchResult( query: String, ): Flow<Pair<List<FusedApp>, Boolean>> { val searchResults = gplayRepository.getSearchResult(query) return searchResults.map { val fusedAppList = it.first.map { app -> replaceWithFDroid(app) } Pair( fusedAppList, it.second ) nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult { try { val searchResults = gplayRepository.getSearchResult(query, nextPageSubBundle?.toMutableSet()) if (!preferenceManagerModule.isGplaySelected()) { return ResultSupreme.Error(ERROR_GPLAY_SOURCE_NOT_SELECTED) } val fusedAppList = searchResults.first.map { app -> replaceWithFDroid(app) }.toMutableList() if (searchResults.second.isNotEmpty()) { fusedAppList.add(FusedApp(isPlaceHolder = true)) } return ResultSupreme.Success(Pair(fusedAppList.toList(), searchResults.second.toSet())) } catch (e: GplayHttpRequestException) { val message = ( e.localizedMessage?.ifBlank { ERROR_GPLAY_SEARCH } ?: ERROR_GPLAY_SEARCH ) + "Status: ${e.status}" val exception = GPlayException(e.status == 408, message) return ResultSupreme.Error(message, exception) } catch (e: Exception) { val exception = GPlayException(e is SocketTimeoutException, e.localizedMessage) return ResultSupreme.Error(e.localizedMessage ?: "", exception) } } Loading Loading @@ -1431,7 +1411,9 @@ class FusedApiImpl @Inject constructor( var nextPageUrl = "" val status = runCodeWithTimeout({ val streamCluster = gplayRepository.getAppsByCategory(category, pageUrl) as StreamCluster val streamCluster = gplayRepository.getAppsByCategory(category, pageUrl) as StreamCluster val filteredAppList = filterRestrictedGPlayApps(authData, streamCluster.clusterAppList) filteredAppList.data?.let { fusedAppList = it.toMutableList() Loading app/src/main/java/foundation/e/apps/data/gplay/GplayStoreRepository.kt +2 −2 Original line number Diff line number Diff line Loading @@ -22,12 +22,12 @@ import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.BaseStoreRepository import foundation.e.apps.data.fused.utils.CategoryType import kotlinx.coroutines.flow.Flow interface GplayStoreRepository : BaseStoreRepository { suspend fun getSearchResult(query: String): Flow<Pair<List<App>, Boolean>> suspend fun getSearchResult(query: String, subBundle: MutableSet<SearchBundle.SubBundle>?): Pair<List<App>, MutableSet<SearchBundle.SubBundle>> suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> suspend fun getAppsByCategory(category: String, pageUrl: String? = null): Any suspend fun getCategories(type: CategoryType? = null): List<Category> Loading app/src/main/java/foundation/e/apps/data/gplay/GplayStoreRepositoryImpl.kt +22 −116 Original line number Diff line number Diff line Loading @@ -39,11 +39,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.gplay.utils.GPlayHttpClient import foundation.e.apps.data.login.LoginSourceRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject class GplayStoreRepositoryImpl @Inject constructor( Loading Loading @@ -78,75 +75,30 @@ class GplayStoreRepositoryImpl @Inject constructor( override suspend fun getSearchResult( query: String, ): Flow<Pair<List<App>, Boolean>> { return flow { /* * Variable names and logic made same as that of Aurora store. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ var authData = loginSourceRepository.gplayAuth ?: return@flow subBundle: MutableSet<SearchBundle.SubBundle>? ): Pair<List<App>, MutableSet<SearchBundle.SubBundle>> { var authData = loginSourceRepository.gplayAuth ?: return Pair(emptyList(), mutableSetOf()) val searchHelper = SearchHelper(authData).using(gPlayHttpClient) val searchBundle = searchHelper.searchResults(query) val initialReplacedList = mutableListOf<App>() val INITIAL_LIMIT = 4 emitReplacedList( this@flow, initialReplacedList, INITIAL_LIMIT, searchBundle, true, ) var nextSubBundleSet: MutableSet<SearchBundle.SubBundle> do { nextSubBundleSet = fetchNextSubBundle( searchBundle, searchHelper, this@flow, initialReplacedList, INITIAL_LIMIT ) } while (nextSubBundleSet.isNotEmpty()) Timber.d("Fetching search result for $query, subBundle: $subBundle") /* * If initialReplacedList size is less than INITIAL_LIMIT, * it means the results were very less and nothing has been emitted so far. * Hence emit the list. */ if (initialReplacedList.size < INITIAL_LIMIT) { emitInMain(this@flow, initialReplacedList, false) } }.flowOn(Dispatchers.IO) subBundle?.let { val searchResult = searchHelper.next(it) Timber.d("fetching next page search data...") return getSearchResultPair(searchResult) } private suspend fun fetchNextSubBundle( searchBundle: SearchBundle, searchHelper: SearchHelper, scope: FlowCollector<Pair<List<App>, Boolean>>, accumulationList: MutableList<App>, accumulationLimit: Int, ): MutableSet<SearchBundle.SubBundle> { val nextSubBundleSet = searchBundle.subBundles val newSearchBundle = searchHelper.next(nextSubBundleSet) if (newSearchBundle.appList.isNotEmpty()) { searchBundle.apply { subBundles.clear() subBundles.addAll(newSearchBundle.subBundles) emitReplacedList( scope, accumulationList, accumulationLimit, newSearchBundle, nextSubBundleSet.isNotEmpty(), ) val searchResult = searchHelper.searchResults(query) return getSearchResultPair(searchResult) } } return nextSubBundleSet private fun getSearchResultPair( searchBundle: SearchBundle ): Pair<MutableList<App>, MutableSet<SearchBundle.SubBundle>> { val apps = searchBundle.appList Timber.d("Search result is found: ${apps.size}") return Pair(apps, searchBundle.subBundles) } override suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> { Loading Loading @@ -214,52 +166,6 @@ class GplayStoreRepositoryImpl @Inject constructor( return if (type == CategoryType.APPLICATION) Category.Type.APPLICATION else Category.Type.GAME } private suspend fun emitReplacedList( scope: FlowCollector<Pair<List<App>, Boolean>>, accumulationList: MutableList<App>, accumulationLimit: Int, searchBundle: SearchBundle, moreToEmit: Boolean, ) { searchBundle.appList.forEach { when { accumulationList.size < accumulationLimit - 1 -> { /* * If initial limit is 4, add apps to list (without emitting) * till 2 apps. */ accumulationList.add(it) } accumulationList.size == accumulationLimit - 1 -> { /* * If initial limit is 4, and we have reached till 3 apps, * add the 4th app and emit the list. */ accumulationList.add(it) scope.emit(Pair(accumulationList, moreToEmit)) emitInMain(scope, accumulationList, moreToEmit) } accumulationList.size == accumulationLimit -> { /* * If initial limit is 4, and we have emitted 4 apps, * for all rest of the apps, emit each app one by one. */ emitInMain(scope, listOf(it), moreToEmit) } } } } private suspend fun emitInMain( scope: FlowCollector<Pair<List<App>, Boolean>>, it: List<App>, moreToEmit: Boolean ) { scope.emit(Pair(it, moreToEmit)) } private suspend fun getTopApps( type: TopChartsHelper.Type, chart: Chart, Loading Loading
app/src/main/java/foundation/e/apps/data/fused/FusedAPIRepository.kt +11 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package foundation.e.apps.data.fused import androidx.lifecycle.LiveData import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Origin Loading Loading @@ -107,11 +108,18 @@ class FusedAPIRepository @Inject constructor(private val fusedAPIImpl: FusedApi) return fusedAPIImpl.getSearchSuggestions(query) } fun getSearchResults( suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> { return fusedAPIImpl.getSearchResults(query, authData) ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { return fusedAPIImpl.getCleanApkSearchResults(query, authData) } suspend fun getGplaySearchResults( query: String, nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult { return fusedAPIImpl.getGplaySearchResult(query, nextPageSubBundle) } suspend fun getAppsListBasedOnCategory( Loading
app/src/main/java/foundation/e/apps/data/fused/FusedApi.kt +12 −5 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.cleanapk.data.download.Download import foundation.e.apps.data.enums.FilterLevel Loading @@ -17,6 +18,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.fusedDownload.models.FusedDownload import retrofit2.Response typealias GplaySearchResult = ResultSupreme<Pair<List<FusedApp>, Set<SearchBundle.SubBundle>>> interface FusedApi { companion object { const val APP_TYPE_ANY = "any" Loading Loading @@ -55,14 +58,18 @@ interface FusedApi { * Fetches search results from cleanAPK and GPlay servers and returns them * @param query Query * @param authData [AuthData] * @return A livedata Pair of list of non-nullable [FusedApp] and * a Boolean signifying if more search results are being loaded. * Observe this livedata to display new apps as they are fetched from the network. * @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. */ fun getSearchResults( suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> suspend fun getGplaySearchResult( query: String, nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> Loading
app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt +69 −87 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R Loading Loading @@ -62,7 +63,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.fused.utils.CategoryUtils import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.gplay.GplayStoreRepository import foundation.e.apps.data.gplay.utils.runFlowWithTimeout import foundation.e.apps.data.gplay.utils.GplayHttpRequestException import foundation.e.apps.data.login.exceptions.GPlayException import foundation.e.apps.data.preference.PreferenceManagerModule import foundation.e.apps.install.pkg.PWAManagerModule import foundation.e.apps.install.pkg.PkgManagerModule Loading @@ -71,16 +73,15 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withTimeout import retrofit2.Response import timber.log.Timber import java.net.SocketTimeoutException import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton typealias GplaySearchResultLiveData = LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> typealias FusedHomeDeferred = Deferred<ResultSupreme<List<FusedHome>>> @Singleton Loading @@ -99,6 +100,8 @@ class FusedApiImpl @Inject constructor( private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&" private const val CATEGORY_OPEN_GAMES_ID = "game_open_games" private const val CATEGORY_OPEN_GAMES_TITLE = "Open games" private const val ERROR_GPLAY_SEARCH = "Gplay search has failed!" private const val ERROR_GPLAY_SOURCE_NOT_SELECTED = "Gplay apps are not selected!" } /** Loading Loading @@ -244,22 +247,21 @@ class FusedApiImpl @Inject constructor( * a Boolean signifying if more search results are being loaded. * Observe this livedata to display new apps as they are fetched from the network. */ override fun getSearchResults( override suspend fun getCleanApkSearchResults( query: String, authData: AuthData ): LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>> { ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { /* * Returning livedata to improve performance, so that we do not have to wait forever * for all results to be fetched from network before showing them. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ return liveData { val packageSpecificResults = ArrayList<FusedApp>() var finalSearchResult: ResultSupreme<Pair<List<FusedApp>, Boolean>> = ResultSupreme.Error() fetchPackageSpecificResult(authData, query, packageSpecificResults).let { if (it.data?.second != true) { // if there are no data to load emit(it) return@liveData return it } } Loading @@ -267,44 +269,32 @@ class FusedApiImpl @Inject constructor( val cleanApkResults = mutableListOf<FusedApp>() if (preferenceManagerModule.isOpenSourceSelected()) { fetchOpenSourceSearchResult( this@FusedApiImpl, finalSearchResult = fetchOpenSourceSearchResult( cleanApkResults, query, searchResult, packageSpecificResults ).let { emit(it) } ) } if (preferenceManagerModule.isPWASelected()) { fetchPWASearchResult( this@FusedApiImpl, query, searchResult, packageSpecificResults ).let { emit(it) } } if (preferenceManagerModule.isGplaySelected()) { emitSource( fetchGplaySearchResults( finalSearchResult = fetchPWASearchResult( query, searchResult, packageSpecificResults ) ) } } return finalSearchResult } private suspend fun fetchPWASearchResult( fusedAPIImpl: FusedApiImpl, query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { val pwaApps: MutableList<FusedApp> = mutableListOf() val status = fusedAPIImpl.runCodeWithTimeout({ val status = runCodeWithTimeout({ val apps = cleanApkPWARepository.getSearchResult(query).body()?.apps apps?.apply { Loading @@ -331,46 +321,13 @@ class FusedApiImpl @Inject constructor( ) } private suspend fun fetchGplaySearchResults( query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): GplaySearchResultLiveData { return runFlowWithTimeout( { getGplaySearchResult(query) }, { it.second }, { Pair(listOf(), false) // empty data for timeout } ).map { if (it.isSuccess()) { searchResult.addAll(it.data!!.first) ResultSupreme.Success( Pair( filterWithKeywordSearch( searchResult, packageSpecificResults, query ), it.data!!.second ) ) } else { it } } } private suspend fun fetchOpenSourceSearchResult( fusedAPIImpl: FusedApiImpl, cleanApkResults: MutableList<FusedApp>, query: String, searchResult: MutableList<FusedApp>, packageSpecificResults: ArrayList<FusedApp> ): ResultSupreme<Pair<List<FusedApp>, Boolean>> { val status = fusedAPIImpl.runCodeWithTimeout({ val status = runCodeWithTimeout({ cleanApkResults.addAll(getCleanAPKSearchResults(query)) }) Loading Loading @@ -1115,16 +1072,39 @@ class FusedApiImpl @Inject constructor( return list } private suspend fun getGplaySearchResult( override suspend fun getGplaySearchResult( query: String, ): Flow<Pair<List<FusedApp>, Boolean>> { val searchResults = gplayRepository.getSearchResult(query) return searchResults.map { val fusedAppList = it.first.map { app -> replaceWithFDroid(app) } Pair( fusedAppList, it.second ) nextPageSubBundle: Set<SearchBundle.SubBundle>? ): GplaySearchResult { try { val searchResults = gplayRepository.getSearchResult(query, nextPageSubBundle?.toMutableSet()) if (!preferenceManagerModule.isGplaySelected()) { return ResultSupreme.Error(ERROR_GPLAY_SOURCE_NOT_SELECTED) } val fusedAppList = searchResults.first.map { app -> replaceWithFDroid(app) }.toMutableList() if (searchResults.second.isNotEmpty()) { fusedAppList.add(FusedApp(isPlaceHolder = true)) } return ResultSupreme.Success(Pair(fusedAppList.toList(), searchResults.second.toSet())) } catch (e: GplayHttpRequestException) { val message = ( e.localizedMessage?.ifBlank { ERROR_GPLAY_SEARCH } ?: ERROR_GPLAY_SEARCH ) + "Status: ${e.status}" val exception = GPlayException(e.status == 408, message) return ResultSupreme.Error(message, exception) } catch (e: Exception) { val exception = GPlayException(e is SocketTimeoutException, e.localizedMessage) return ResultSupreme.Error(e.localizedMessage ?: "", exception) } } Loading Loading @@ -1431,7 +1411,9 @@ class FusedApiImpl @Inject constructor( var nextPageUrl = "" val status = runCodeWithTimeout({ val streamCluster = gplayRepository.getAppsByCategory(category, pageUrl) as StreamCluster val streamCluster = gplayRepository.getAppsByCategory(category, pageUrl) as StreamCluster val filteredAppList = filterRestrictedGPlayApps(authData, streamCluster.clusterAppList) filteredAppList.data?.let { fusedAppList = it.toMutableList() Loading
app/src/main/java/foundation/e/apps/data/gplay/GplayStoreRepository.kt +2 −2 Original line number Diff line number Diff line Loading @@ -22,12 +22,12 @@ import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.BaseStoreRepository import foundation.e.apps.data.fused.utils.CategoryType import kotlinx.coroutines.flow.Flow interface GplayStoreRepository : BaseStoreRepository { suspend fun getSearchResult(query: String): Flow<Pair<List<App>, Boolean>> suspend fun getSearchResult(query: String, subBundle: MutableSet<SearchBundle.SubBundle>?): Pair<List<App>, MutableSet<SearchBundle.SubBundle>> suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> suspend fun getAppsByCategory(category: String, pageUrl: String? = null): Any suspend fun getCategories(type: CategoryType? = null): List<Category> Loading
app/src/main/java/foundation/e/apps/data/gplay/GplayStoreRepositoryImpl.kt +22 −116 Original line number Diff line number Diff line Loading @@ -39,11 +39,8 @@ import foundation.e.apps.data.fused.utils.CategoryType import foundation.e.apps.data.gplay.utils.GPlayHttpClient import foundation.e.apps.data.login.LoginSourceRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject class GplayStoreRepositoryImpl @Inject constructor( Loading Loading @@ -78,75 +75,30 @@ class GplayStoreRepositoryImpl @Inject constructor( override suspend fun getSearchResult( query: String, ): Flow<Pair<List<App>, Boolean>> { return flow { /* * Variable names and logic made same as that of Aurora store. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ var authData = loginSourceRepository.gplayAuth ?: return@flow subBundle: MutableSet<SearchBundle.SubBundle>? ): Pair<List<App>, MutableSet<SearchBundle.SubBundle>> { var authData = loginSourceRepository.gplayAuth ?: return Pair(emptyList(), mutableSetOf()) val searchHelper = SearchHelper(authData).using(gPlayHttpClient) val searchBundle = searchHelper.searchResults(query) val initialReplacedList = mutableListOf<App>() val INITIAL_LIMIT = 4 emitReplacedList( this@flow, initialReplacedList, INITIAL_LIMIT, searchBundle, true, ) var nextSubBundleSet: MutableSet<SearchBundle.SubBundle> do { nextSubBundleSet = fetchNextSubBundle( searchBundle, searchHelper, this@flow, initialReplacedList, INITIAL_LIMIT ) } while (nextSubBundleSet.isNotEmpty()) Timber.d("Fetching search result for $query, subBundle: $subBundle") /* * If initialReplacedList size is less than INITIAL_LIMIT, * it means the results were very less and nothing has been emitted so far. * Hence emit the list. */ if (initialReplacedList.size < INITIAL_LIMIT) { emitInMain(this@flow, initialReplacedList, false) } }.flowOn(Dispatchers.IO) subBundle?.let { val searchResult = searchHelper.next(it) Timber.d("fetching next page search data...") return getSearchResultPair(searchResult) } private suspend fun fetchNextSubBundle( searchBundle: SearchBundle, searchHelper: SearchHelper, scope: FlowCollector<Pair<List<App>, Boolean>>, accumulationList: MutableList<App>, accumulationLimit: Int, ): MutableSet<SearchBundle.SubBundle> { val nextSubBundleSet = searchBundle.subBundles val newSearchBundle = searchHelper.next(nextSubBundleSet) if (newSearchBundle.appList.isNotEmpty()) { searchBundle.apply { subBundles.clear() subBundles.addAll(newSearchBundle.subBundles) emitReplacedList( scope, accumulationList, accumulationLimit, newSearchBundle, nextSubBundleSet.isNotEmpty(), ) val searchResult = searchHelper.searchResults(query) return getSearchResultPair(searchResult) } } return nextSubBundleSet private fun getSearchResultPair( searchBundle: SearchBundle ): Pair<MutableList<App>, MutableSet<SearchBundle.SubBundle>> { val apps = searchBundle.appList Timber.d("Search result is found: ${apps.size}") return Pair(apps, searchBundle.subBundles) } override suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> { Loading Loading @@ -214,52 +166,6 @@ class GplayStoreRepositoryImpl @Inject constructor( return if (type == CategoryType.APPLICATION) Category.Type.APPLICATION else Category.Type.GAME } private suspend fun emitReplacedList( scope: FlowCollector<Pair<List<App>, Boolean>>, accumulationList: MutableList<App>, accumulationLimit: Int, searchBundle: SearchBundle, moreToEmit: Boolean, ) { searchBundle.appList.forEach { when { accumulationList.size < accumulationLimit - 1 -> { /* * If initial limit is 4, add apps to list (without emitting) * till 2 apps. */ accumulationList.add(it) } accumulationList.size == accumulationLimit - 1 -> { /* * If initial limit is 4, and we have reached till 3 apps, * add the 4th app and emit the list. */ accumulationList.add(it) scope.emit(Pair(accumulationList, moreToEmit)) emitInMain(scope, accumulationList, moreToEmit) } accumulationList.size == accumulationLimit -> { /* * If initial limit is 4, and we have emitted 4 apps, * for all rest of the apps, emit each app one by one. */ emitInMain(scope, listOf(it), moreToEmit) } } } } private suspend fun emitInMain( scope: FlowCollector<Pair<List<App>, Boolean>>, it: List<App>, moreToEmit: Boolean ) { scope.emit(Pair(it, moreToEmit)) } private suspend fun getTopApps( type: TopChartsHelper.Type, chart: Chart, Loading