From cfffabc15dc2041be288ca59eb82c65e554670b1 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Mon, 20 Feb 2023 20:39:15 +0600 Subject: [PATCH 1/5] paritally done --- .../e/apps/api/StoreApiRepository.kt | 6 + .../e/apps/api/fused/FusedAPIImpl.kt | 22 ++- .../e/apps/api/gplay/GplayRepository.kt | 148 ++++++++++++++++++ .../e/apps/di/NamedRepositoryModule.kt | 29 ++++ .../e/apps/login/LoginSourceRepository.kt | 5 + 5 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt diff --git a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt new file mode 100644 index 000000000..cb3985672 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt @@ -0,0 +1,6 @@ +package foundation.e.apps.api + +interface StoreApiRepository { + suspend fun getHomeScreenData(): Any + fun getSearchResult(query: String): Any +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index 889cbb999..63c4eace2 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -36,6 +36,7 @@ import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme +import foundation.e.apps.api.StoreApiRepository import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.cleanapk.CleanAPKRepository import foundation.e.apps.api.cleanapk.data.categories.Categories @@ -48,6 +49,7 @@ import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.fused.data.Ratings import foundation.e.apps.api.fused.utils.CategoryUtils import foundation.e.apps.api.gplay.GPlayAPIRepository +import foundation.e.apps.api.gplay.GplayRepository import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.pkg.PkgManagerModule @@ -66,6 +68,7 @@ import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject +import javax.inject.Named import javax.inject.Singleton @Singleton @@ -76,6 +79,7 @@ class FusedAPIImpl @Inject constructor( private val pwaManagerModule: PWAManagerModule, private val preferenceManagerModule: PreferenceManagerModule, private val fdroidWebInterface: FdroidWebInterface, + @Named("gplayRepository") private val gplayRepository: StoreApiRepository, @ApplicationContext private val context: Context ) { @@ -1320,24 +1324,16 @@ class FusedAPIImpl @Inject constructor( private suspend fun fetchGPlayHome(authData: AuthData): List { val list = mutableListOf() - val homeElements = mutableMapOf( - context.getString(R.string.topselling_free_apps) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.topselling_free_games) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.GAME), - context.getString(R.string.topgrossing_apps) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.topgrossing_games) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.GAME), - context.getString(R.string.movers_shakers_apps) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.movers_shakers_games) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.GAME), - ) - homeElements.forEach { - val chart = it.value.keys.iterator().next() - val type = it.value.values.iterator().next() - val result = gPlayAPIRepository.getTopApps(type, chart, authData).map { app -> + val gplayHomeData = gplayRepository.getHomeScreenData() as Map> + gplayHomeData.map { + val fusedApps = it.value.map { app -> app.transformToFusedApp().apply { updateFilterLevel(authData) } } - list.add(FusedHome(it.key, result)) + list.add(FusedHome(it.key, fusedApps)) } + Timber.d("===> $list") return list } diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt new file mode 100644 index 000000000..f4e380549 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt @@ -0,0 +1,148 @@ +package foundation.e.apps.api.gplay + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.lifecycle.LiveDataScope +import androidx.lifecycle.liveData +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.gplayapi.helpers.SearchHelper +import com.aurora.gplayapi.helpers.TopChartsHelper +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.R +import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.api.fused.data.FusedHome +import foundation.e.apps.api.gplay.utils.GPlayHttpClient +import foundation.e.apps.login.LoginSourceRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class GplayRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val gPlayHttpClient: GPlayHttpClient, + private val loginSourceRepository: LoginSourceRepository +) : StoreApiRepository { + + override suspend fun getHomeScreenData(): Any { + val homeScreenData = mutableMapOf>() + val homeElements = mutableMapOf( + context.getString(R.string.topselling_free_apps) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.topselling_free_games) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.GAME), + context.getString(R.string.topgrossing_apps) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.topgrossing_games) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.GAME), + context.getString(R.string.movers_shakers_apps) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.movers_shakers_games) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.GAME), + ) + homeElements.forEach { + val chart = it.value.keys.iterator().next() + val type = it.value.values.iterator().next() + val result = getTopApps(type, chart, loginSourceRepository.gplayAuth!!) + homeScreenData[it.key] = result + } + + return homeScreenData + } + + override fun getSearchResult(query: String): LiveData, Boolean>> { + return liveData { + withContext(Dispatchers.IO) { + /* + * Variable names and logic made same as that of Aurora store. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 + */ + val searchHelper = SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) + val searchBundle = searchHelper.searchResults(query) + + val initialReplacedList = mutableListOf() + val INITIAL_LIMIT = 4 + + emitReplacedList( + this@liveData, + initialReplacedList, + INITIAL_LIMIT, + searchBundle, + true, + ) + + var nextSubBundleSet: MutableSet + do { + nextSubBundleSet = searchBundle.subBundles + val newSearchBundle = searchHelper.next(nextSubBundleSet) + if (newSearchBundle.appList.isNotEmpty()) { + searchBundle.apply { + subBundles.clear() + subBundles.addAll(newSearchBundle.subBundles) + emitReplacedList( + this@liveData, + initialReplacedList, + INITIAL_LIMIT, + newSearchBundle, + nextSubBundleSet.isNotEmpty(), + ) + } + } + } while (nextSubBundleSet.isNotEmpty()) + + /* + * 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) { + emit(Pair(initialReplacedList, false)) + } + } + } + } + + private suspend fun emitReplacedList( + scope: LiveDataScope, Boolean>>, + accumulationList: MutableList, + 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)) + } + 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. + */ + scope.emit(Pair(listOf(it), moreToEmit)) + } + } + } + } + + private suspend fun getTopApps( + type: TopChartsHelper.Type, + chart: TopChartsHelper.Chart, + authData: AuthData + ): List { + val topApps = mutableListOf() + withContext(Dispatchers.IO) { + val topChartsHelper = TopChartsHelper(authData).using(gPlayHttpClient) + topApps.addAll(topChartsHelper.getCluster(type, chart).clusterAppList) + } + return topApps + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt new file mode 100644 index 000000000..12a57e353 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt @@ -0,0 +1,29 @@ +package foundation.e.apps.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.gplay.GplayRepository +import foundation.e.apps.api.gplay.utils.GPlayHttpClient +import foundation.e.apps.login.LoginSourceRepository +import javax.inject.Named +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object NamedRepositoryModule { + @Singleton + @Provides + @Named("gplayRepository") + fun getGplayRepository( + @ApplicationContext context: Context, + gPlayHttpClient: GPlayHttpClient, + loginSourceRepository: LoginSourceRepository + ): StoreApiRepository { + return GplayRepository(context, gPlayHttpClient, loginSourceRepository) + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt index ddc4bb392..43dc65c94 100644 --- a/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt +++ b/app/src/main/java/foundation/e/apps/login/LoginSourceRepository.kt @@ -17,6 +17,7 @@ package foundation.e.apps.login +import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.utils.enums.User import javax.inject.Inject import javax.inject.Singleton @@ -28,6 +29,7 @@ class LoginSourceRepository @Inject constructor( private val sources: List, ) { + var gplayAuth: AuthData? = null suspend fun getAuthObjects(clearAuthTypes: List = listOf()): List { val authObjectsLocal = ArrayList() @@ -37,6 +39,9 @@ class LoginSourceRepository @Inject constructor( if (source::class.java.simpleName in clearAuthTypes) { source.clearSavedAuth() } + if (source is LoginSourceGPlay) { + gplayAuth = source.getAuthObject().result.data + } authObjectsLocal.add(source.getAuthObject()) } -- GitLab From ec95bfe07a521a45376b526455098197ffa0f215 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Mon, 27 Feb 2023 12:13:59 +0600 Subject: [PATCH 2/5] refactored search api of gplay --- .../e/apps/api/fused/FusedAPIImpl.kt | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index 63c4eace2..fdaf4338d 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -20,10 +20,7 @@ package foundation.e.apps.api.fused import android.content.Context import android.text.format.Formatter -import androidx.lifecycle.LiveData -import androidx.lifecycle.LiveDataScope -import androidx.lifecycle.liveData -import androidx.lifecycle.map +import androidx.lifecycle.* import com.aurora.gplayapi.Constants import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App @@ -65,6 +62,9 @@ import foundation.e.apps.utils.enums.isUnFiltered import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.modules.PreferenceManagerModule import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject @@ -324,7 +324,7 @@ class FusedAPIImpl @Inject constructor( searchResult: MutableList, packageSpecificResults: ArrayList ): LiveData, Boolean>>> = - getGplaySearchResults(query, authData).map { + getGplaySearchResult(query, authData).map { if (it.first.isNotEmpty()) { searchResult.addAll(it.first) } @@ -1203,6 +1203,19 @@ class FusedAPIImpl @Inject constructor( } } + private fun getGplaySearchResult( + query: String, + authData: AuthData + ): LiveData, Boolean>> { + val searchResults = gplayRepository.getSearchResult(query) as LiveData, Boolean>> + return searchResults.asFlow().map { + val fusedAppList = it.first.map { app -> replaceWithFDroid(app) } + Pair( + fusedAppList, + it.second + ) + }.asLiveData() + } /* * This function will replace a GPlay app with F-Droid app if exists, * else will show the GPlay app itself. -- GitLab From 9fa3bdaaedf31009b09a0d3987a3e45ff3ed8db9 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Tue, 28 Feb 2023 09:47:24 +0600 Subject: [PATCH 3/5] CleanApk repository is introduced --- .../e/apps/api/StoreApiRepository.kt | 2 +- .../api/cleanapk/CleanApkAppsRepository.kt | 28 +++++++++++++++++++ .../api/cleanapk/CleanApkPWARepository.kt | 19 +++++++++++++ .../e/apps/api/fused/FusedAPIImpl.kt | 18 ++++++------ .../e/apps/api/gplay/GplayRepository.kt | 2 +- .../e/apps/di/NamedRepositoryModule.kt | 24 ++++++++++++++++ 6 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt diff --git a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt index cb3985672..04d45b240 100644 --- a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt @@ -2,5 +2,5 @@ package foundation.e.apps.api interface StoreApiRepository { suspend fun getHomeScreenData(): Any - fun getSearchResult(query: String): Any + suspend fun getSearchResult(query: String): Any } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt new file mode 100644 index 000000000..0eceec6db --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt @@ -0,0 +1,28 @@ +package foundation.e.apps.api.cleanapk + +import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.cleanapk.data.home.HomeScreen +import retrofit2.Response + +class CleanApkAppsRepository( + private val cleanAPKInterface: CleanAPKInterface, + private val cleanApkAppDetailApi: CleanApkAppDetailApi +) : StoreApiRepository { + override suspend fun getHomeScreenData(): Response { + return cleanAPKInterface.getHomeScreenData( + CleanAPKInterface.APP_TYPE_ANY, + CleanAPKInterface.APP_SOURCE_FOSS + ) + } + + override suspend fun getSearchResult(query: String): Any { + return cleanAPKInterface.searchApps( + query, + CleanAPKInterface.APP_TYPE_ANY, + CleanAPKInterface.APP_SOURCE_FOSS, + 20, + 1, + null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt new file mode 100644 index 000000000..8a8a09871 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt @@ -0,0 +1,19 @@ +package foundation.e.apps.api.cleanapk + +import foundation.e.apps.api.StoreApiRepository + +class CleanApkPWARepository( + private val cleanAPKInterface: CleanAPKInterface, + private val cleanApkAppDetailApi: CleanApkAppDetailApi +) : StoreApiRepository { + override suspend fun getHomeScreenData(): Any { + return cleanAPKInterface.getHomeScreenData( + CleanAPKInterface.APP_TYPE_PWA, + CleanAPKInterface.APP_SOURCE_ANY + ) + } + + override suspend fun getSearchResult(query: String): Any { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index fdaf4338d..18b78bdb4 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -38,6 +38,7 @@ import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.cleanapk.CleanAPKRepository import foundation.e.apps.api.cleanapk.data.categories.Categories import foundation.e.apps.api.cleanapk.data.home.Home +import foundation.e.apps.api.cleanapk.data.home.HomeScreen import foundation.e.apps.api.cleanapk.data.search.Search import foundation.e.apps.api.fdroid.FdroidWebInterface import foundation.e.apps.api.fused.data.FusedApp @@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withTimeout +import retrofit2.Response import timber.log.Timber import javax.inject.Inject import javax.inject.Named @@ -80,6 +82,8 @@ class FusedAPIImpl @Inject constructor( private val preferenceManagerModule: PreferenceManagerModule, private val fdroidWebInterface: FdroidWebInterface, @Named("gplayRepository") private val gplayRepository: StoreApiRepository, + @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: StoreApiRepository, + @Named("cleanApkPWARepository") private val cleanApkPWARepository: StoreApiRepository, @ApplicationContext private val context: Context ) { @@ -156,20 +160,14 @@ class FusedAPIImpl @Inject constructor( }) Source.OPEN -> runCodeBlockWithTimeout({ - val response = cleanAPKRepository.getHomeScreenData( - CleanAPKInterface.APP_TYPE_ANY, - CleanAPKInterface.APP_SOURCE_FOSS - ).body() + val response = (cleanApkAppsRepository.getHomeScreenData() as Response).body() response?.home?.let { priorList.addAll(generateCleanAPKHome(it, APP_TYPE_OPEN)) } }) Source.PWA -> runCodeBlockWithTimeout({ - val response = cleanAPKRepository.getHomeScreenData( - CleanAPKInterface.APP_TYPE_PWA, - CleanAPKInterface.APP_SOURCE_ANY - ).body() + val response = (cleanApkPWARepository.getHomeScreenData() as Response).body() response?.home?.let { priorList.addAll(generateCleanAPKHome(it, APP_TYPE_PWA)) } @@ -318,7 +316,7 @@ class FusedAPIImpl @Inject constructor( ) } - private fun fetchGplaySearchResults( + private suspend fun fetchGplaySearchResults( query: String, authData: AuthData, searchResult: MutableList, @@ -1203,7 +1201,7 @@ class FusedAPIImpl @Inject constructor( } } - private fun getGplaySearchResult( + private suspend fun getGplaySearchResult( query: String, authData: AuthData ): LiveData, Boolean>> { diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt index f4e380549..496adaaf5 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt @@ -46,7 +46,7 @@ class GplayRepository @Inject constructor( return homeScreenData } - override fun getSearchResult(query: String): LiveData, Boolean>> { + override suspend fun getSearchResult(query: String): LiveData, Boolean>> { return liveData { withContext(Dispatchers.IO) { /* diff --git a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt index 12a57e353..85e3ef5d6 100644 --- a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt @@ -7,6 +7,9 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.cleanapk.CleanAPKInterface +import foundation.e.apps.api.cleanapk.CleanApkAppDetailApi +import foundation.e.apps.api.cleanapk.CleanApkAppsRepository import foundation.e.apps.api.gplay.GplayRepository import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.login.LoginSourceRepository @@ -26,4 +29,25 @@ object NamedRepositoryModule { ): StoreApiRepository { return GplayRepository(context, gPlayHttpClient, loginSourceRepository) } + + @Singleton + @Provides + @Named("cleanApkAppsRepository") + fun getCleanApkAppsRepository( + cleanAPKInterface: CleanAPKInterface, + cleanApkAppDetailApi: CleanApkAppDetailApi + ): StoreApiRepository { + return CleanApkAppsRepository(cleanAPKInterface, cleanApkAppDetailApi) + } + + @Singleton + @Provides + @Named("cleanApkPWARepository") + fun getCleanApkPWARepository( + cleanAPKInterface: CleanAPKInterface, + cleanApkAppDetailApi: CleanApkAppDetailApi + ): StoreApiRepository { + return CleanApkAppsRepository(cleanAPKInterface, cleanApkAppDetailApi) + } + } \ No newline at end of file -- GitLab From fa0ab8ae3a89d7b46b3345215a96270fce4dc4ec Mon Sep 17 00:00:00 2001 From: hasibprince Date: Mon, 6 Mar 2023 10:33:49 +0600 Subject: [PATCH 4/5] search apis refactored for storeapirepository --- .../e/apps/api/StoreApiRepository.kt | 3 +- .../api/cleanapk/CleanApkAppsRepository.kt | 11 +- .../api/cleanapk/CleanApkPWARepository.kt | 17 ++- .../e/apps/api/fused/FusedAPIImpl.kt | 36 +++---- .../e/apps/api/gplay/GplayRepository.kt | 101 ++++++++++-------- .../e/apps/di/NamedRepositoryModule.kt | 3 +- 6 files changed, 102 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt index 04d45b240..519222b93 100644 --- a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt @@ -3,4 +3,5 @@ package foundation.e.apps.api interface StoreApiRepository { suspend fun getHomeScreenData(): Any suspend fun getSearchResult(query: String): Any -} \ No newline at end of file + suspend fun getSearchSuggestions(query: String): Any +} diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt index 0eceec6db..e84b5e054 100644 --- a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt @@ -2,6 +2,7 @@ package foundation.e.apps.api.cleanapk import foundation.e.apps.api.StoreApiRepository import foundation.e.apps.api.cleanapk.data.home.HomeScreen +import foundation.e.apps.api.cleanapk.data.search.Search import retrofit2.Response class CleanApkAppsRepository( @@ -15,14 +16,18 @@ class CleanApkAppsRepository( ) } - override suspend fun getSearchResult(query: String): Any { + override suspend fun getSearchResult(query: String): Response { return cleanAPKInterface.searchApps( query, - CleanAPKInterface.APP_TYPE_ANY, CleanAPKInterface.APP_SOURCE_FOSS, + CleanAPKInterface.APP_TYPE_ANY, 20, 1, null ) } -} \ No newline at end of file + + override suspend fun getSearchSuggestions(query: String): Any { + TODO("Not yet implemented") + } +} diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt index 8a8a09871..d9bb243c5 100644 --- a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt @@ -1,6 +1,8 @@ package foundation.e.apps.api.cleanapk import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.cleanapk.data.search.Search +import retrofit2.Response class CleanApkPWARepository( private val cleanAPKInterface: CleanAPKInterface, @@ -13,7 +15,18 @@ class CleanApkPWARepository( ) } - override suspend fun getSearchResult(query: String): Any { + override suspend fun getSearchResult(query: String): Response { + return cleanAPKInterface.searchApps( + query, + CleanAPKInterface.APP_SOURCE_ANY, + CleanAPKInterface.APP_TYPE_PWA, + 20, + 1, + null + ) + } + + override suspend fun getSearchSuggestions(query: String): Any { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index 18b78bdb4..ff7269843 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -20,7 +20,11 @@ package foundation.e.apps.api.fused import android.content.Context import android.text.format.Formatter -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.LiveDataScope +import androidx.lifecycle.asLiveData +import androidx.lifecycle.liveData +import androidx.lifecycle.map import com.aurora.gplayapi.Constants import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App @@ -29,7 +33,6 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster -import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme @@ -47,7 +50,6 @@ import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.fused.data.Ratings import foundation.e.apps.api.fused.utils.CategoryUtils import foundation.e.apps.api.gplay.GPlayAPIRepository -import foundation.e.apps.api.gplay.GplayRepository import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.pkg.PkgManagerModule @@ -63,9 +65,8 @@ import foundation.e.apps.utils.enums.isUnFiltered import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.modules.PreferenceManagerModule import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withTimeout import retrofit2.Response import timber.log.Timber @@ -274,7 +275,7 @@ class FusedAPIImpl @Inject constructor( authData, searchResult, packageSpecificResults - ) + ).asLiveData() ) } } @@ -288,11 +289,8 @@ class FusedAPIImpl @Inject constructor( ): ResultSupreme, Boolean>> { val pwaApps: MutableList = mutableListOf() val status = fusedAPIImpl.runCodeBlockWithTimeout({ - getCleanAPKSearchResults( - query, - CleanAPKInterface.APP_SOURCE_ANY, - CleanAPKInterface.APP_TYPE_PWA - ).apply { + val apps = (cleanApkPWARepository.getSearchResult(query,) as Response).body()?.apps + apps?.apply { if (this.isNotEmpty()) { pwaApps.addAll(this) } @@ -321,7 +319,7 @@ class FusedAPIImpl @Inject constructor( authData: AuthData, searchResult: MutableList, packageSpecificResults: ArrayList - ): LiveData, Boolean>>> = + ): Flow, Boolean>>> = getGplaySearchResult(query, authData).map { if (it.first.isNotEmpty()) { searchResult.addAll(it.first) @@ -471,7 +469,7 @@ class FusedAPIImpl @Inject constructor( } suspend fun getSearchSuggestions(query: String, authData: AuthData): List { - return gPlayAPIRepository.getSearchSuggestions(query, authData) + return gplayRepository.getSearchSuggestions(query) as List } suspend fun getOnDemandModule( @@ -1175,8 +1173,8 @@ class FusedAPIImpl @Inject constructor( by: String? = null ): List { val list = mutableListOf() - val response = - cleanAPKRepository.searchApps(keyword, source, type, nres, page, by).body()?.apps + val response = (cleanApkAppsRepository.getSearchResult(keyword) as Response).body()?.apps +// val response = cleanAPKRepository.searchApps(keyword, source, type, nres,page, by).body()?.apps response?.forEach { it.updateStatus() @@ -1204,15 +1202,15 @@ class FusedAPIImpl @Inject constructor( private suspend fun getGplaySearchResult( query: String, authData: AuthData - ): LiveData, Boolean>> { - val searchResults = gplayRepository.getSearchResult(query) as LiveData, Boolean>> - return searchResults.asFlow().map { + ): Flow, Boolean>> { + val searchResults = gplayRepository.getSearchResult(query) as Flow, Boolean>> + return searchResults.map { val fusedAppList = it.first.map { app -> replaceWithFDroid(app) } Pair( fusedAppList, it.second ) - }.asLiveData() + } } /* * This function will replace a GPlay app with F-Droid app if exists, diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt index 496adaaf5..53ba3ae04 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt @@ -1,9 +1,7 @@ package foundation.e.apps.api.gplay import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.LiveDataScope -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 @@ -12,11 +10,13 @@ import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.api.StoreApiRepository -import foundation.e.apps.api.fused.data.FusedApp -import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.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 javax.inject.Inject @@ -46,60 +46,68 @@ class GplayRepository @Inject constructor( return homeScreenData } - override suspend fun getSearchResult(query: String): LiveData, Boolean>> { - return liveData { - withContext(Dispatchers.IO) { + override suspend fun getSearchResult(query: String): Flow, Boolean>> { + return flow { /* * Variable names and logic made same as that of Aurora store. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ - val searchHelper = SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) - val searchBundle = searchHelper.searchResults(query) + val searchHelper = + SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) + val searchBundle = searchHelper.searchResults(query) - val initialReplacedList = mutableListOf() - val INITIAL_LIMIT = 4 + val initialReplacedList = mutableListOf() + val INITIAL_LIMIT = 4 - emitReplacedList( - this@liveData, - initialReplacedList, - INITIAL_LIMIT, - searchBundle, - true, - ) + emitReplacedList( + this@flow, + initialReplacedList, + INITIAL_LIMIT, + searchBundle, + true, + ) - var nextSubBundleSet: MutableSet - do { - nextSubBundleSet = searchBundle.subBundles - val newSearchBundle = searchHelper.next(nextSubBundleSet) - if (newSearchBundle.appList.isNotEmpty()) { - searchBundle.apply { - subBundles.clear() - subBundles.addAll(newSearchBundle.subBundles) - emitReplacedList( - this@liveData, - initialReplacedList, - INITIAL_LIMIT, - newSearchBundle, - nextSubBundleSet.isNotEmpty(), - ) - } + var nextSubBundleSet: MutableSet + do { + nextSubBundleSet = searchBundle.subBundles + val newSearchBundle = searchHelper.next(nextSubBundleSet) + if (newSearchBundle.appList.isNotEmpty()) { + searchBundle.apply { + subBundles.clear() + subBundles.addAll(newSearchBundle.subBundles) + emitReplacedList( + this@flow, + initialReplacedList, + INITIAL_LIMIT, + newSearchBundle, + nextSubBundleSet.isNotEmpty(), + ) } - } while (nextSubBundleSet.isNotEmpty()) + } + } while (nextSubBundleSet.isNotEmpty()) /* * 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) { - emit(Pair(initialReplacedList, false)) - } + if (initialReplacedList.size < INITIAL_LIMIT) { + emitInMain(this@flow, initialReplacedList, false) } + }.flowOn(Dispatchers.IO) + } + + override suspend fun getSearchSuggestions(query: String): List { + val searchData = mutableListOf() + withContext(Dispatchers.IO) { + val searchHelper = SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) + searchData.addAll(searchHelper.searchSuggestions(query)) } + return searchData.filter { it.suggestedQuery.isNotBlank() } } private suspend fun emitReplacedList( - scope: LiveDataScope, Boolean>>, + scope: FlowCollector, Boolean>>, accumulationList: MutableList, accumulationLimit: Int, searchBundle: SearchBundle, @@ -121,18 +129,27 @@ class GplayRepository @Inject constructor( */ 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. */ - scope.emit(Pair(listOf(it), moreToEmit)) + emitInMain(scope, listOf(it), moreToEmit) } } } } + private suspend fun emitInMain( + scope: FlowCollector, Boolean>>, + it: List, + moreToEmit: Boolean + ) { + scope.emit(Pair(it, moreToEmit)) + } + private suspend fun getTopApps( type: TopChartsHelper.Type, chart: TopChartsHelper.Chart, @@ -145,4 +162,4 @@ class GplayRepository @Inject constructor( } return topApps } -} \ No newline at end of file +} diff --git a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt index 85e3ef5d6..3bde1dcf2 100644 --- a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt @@ -49,5 +49,4 @@ object NamedRepositoryModule { ): StoreApiRepository { return CleanApkAppsRepository(cleanAPKInterface, cleanApkAppDetailApi) } - -} \ No newline at end of file +} -- GitLab From 9796ce3c64b9381162e543af5de2fe0d482fb878 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Thu, 9 Mar 2023 19:41:13 +0600 Subject: [PATCH 5/5] some refactoring --- .../e/apps/api/StoreApiRepository.kt | 7 -- .../foundation/e/apps/api/StoreRepository.kt | 25 ++++ .../api/cleanapk/CleanApkAppsRepository.kt | 24 +++- .../api/cleanapk/CleanApkPWARepository.kt | 24 +++- .../e/apps/api/fused/FusedAPIImpl.kt | 58 +++++---- .../e/apps/api/gplay/GplayRepository.kt | 111 ++++++++++++------ .../e/apps/di/NamedRepositoryModule.kt | 34 ++++-- 7 files changed, 201 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/api/StoreRepository.kt diff --git a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt b/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt deleted file mode 100644 index 519222b93..000000000 --- a/app/src/main/java/foundation/e/apps/api/StoreApiRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package foundation.e.apps.api - -interface StoreApiRepository { - suspend fun getHomeScreenData(): Any - suspend fun getSearchResult(query: String): Any - suspend fun getSearchSuggestions(query: String): Any -} diff --git a/app/src/main/java/foundation/e/apps/api/StoreRepository.kt b/app/src/main/java/foundation/e/apps/api/StoreRepository.kt new file mode 100644 index 000000000..96d182cc3 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/StoreRepository.kt @@ -0,0 +1,25 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.api + +interface StoreRepository { + suspend fun getHomeScreenData(): Any + suspend fun getSearchResult(query: String): Any + suspend fun getSearchSuggestions(query: String): Any +} diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt index e84b5e054..709b22f9e 100644 --- a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkAppsRepository.kt @@ -1,14 +1,32 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package foundation.e.apps.api.cleanapk -import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.StoreRepository import foundation.e.apps.api.cleanapk.data.home.HomeScreen import foundation.e.apps.api.cleanapk.data.search.Search import retrofit2.Response class CleanApkAppsRepository( private val cleanAPKInterface: CleanAPKInterface, - private val cleanApkAppDetailApi: CleanApkAppDetailApi -) : StoreApiRepository { +) : StoreRepository { + override suspend fun getHomeScreenData(): Response { return cleanAPKInterface.getHomeScreenData( CleanAPKInterface.APP_TYPE_ANY, diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt index d9bb243c5..fa4ad5c27 100644 --- a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanApkPWARepository.kt @@ -1,13 +1,31 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package foundation.e.apps.api.cleanapk -import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.StoreRepository import foundation.e.apps.api.cleanapk.data.search.Search import retrofit2.Response class CleanApkPWARepository( private val cleanAPKInterface: CleanAPKInterface, - private val cleanApkAppDetailApi: CleanApkAppDetailApi -) : StoreApiRepository { +) : StoreRepository { + override suspend fun getHomeScreenData(): Any { return cleanAPKInterface.getHomeScreenData( CleanAPKInterface.APP_TYPE_PWA, diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index ff7269843..2eba6f4be 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -36,7 +36,7 @@ import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme -import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.StoreRepository import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.cleanapk.CleanAPKRepository import foundation.e.apps.api.cleanapk.data.categories.Categories @@ -74,6 +74,8 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton +typealias GplaySearchResultFlow = Flow, Boolean>>> + @Singleton class FusedAPIImpl @Inject constructor( private val cleanAPKRepository: CleanAPKRepository, @@ -82,9 +84,9 @@ class FusedAPIImpl @Inject constructor( private val pwaManagerModule: PWAManagerModule, private val preferenceManagerModule: PreferenceManagerModule, private val fdroidWebInterface: FdroidWebInterface, - @Named("gplayRepository") private val gplayRepository: StoreApiRepository, - @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: StoreApiRepository, - @Named("cleanApkPWARepository") private val cleanApkPWARepository: StoreApiRepository, + @Named("gplayRepository") private val gplayRepository: StoreRepository, + @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: StoreRepository, + @Named("cleanApkPWARepository") private val cleanApkPWARepository: StoreRepository, @ApplicationContext private val context: Context ) { @@ -161,14 +163,16 @@ class FusedAPIImpl @Inject constructor( }) Source.OPEN -> runCodeBlockWithTimeout({ - val response = (cleanApkAppsRepository.getHomeScreenData() as Response).body() + val response = + (cleanApkAppsRepository.getHomeScreenData() as Response).body() response?.home?.let { priorList.addAll(generateCleanAPKHome(it, APP_TYPE_OPEN)) } }) Source.PWA -> runCodeBlockWithTimeout({ - val response = (cleanApkPWARepository.getHomeScreenData() as Response).body() + val response = + (cleanApkPWARepository.getHomeScreenData() as Response).body() response?.home?.let { priorList.addAll(generateCleanAPKHome(it, APP_TYPE_PWA)) } @@ -289,7 +293,8 @@ class FusedAPIImpl @Inject constructor( ): ResultSupreme, Boolean>> { val pwaApps: MutableList = mutableListOf() val status = fusedAPIImpl.runCodeBlockWithTimeout({ - val apps = (cleanApkPWARepository.getSearchResult(query,) as Response).body()?.apps + val apps = + (cleanApkPWARepository.getSearchResult(query) as Response).body()?.apps apps?.apply { if (this.isNotEmpty()) { pwaApps.addAll(this) @@ -319,22 +324,21 @@ class FusedAPIImpl @Inject constructor( authData: AuthData, searchResult: MutableList, packageSpecificResults: ArrayList - ): Flow, Boolean>>> = - getGplaySearchResult(query, authData).map { - if (it.first.isNotEmpty()) { - searchResult.addAll(it.first) - } - ResultSupreme.Success( - Pair( - filterWithKeywordSearch( - searchResult, - packageSpecificResults, - query - ), - it.second - ) - ) + ): GplaySearchResultFlow = getGplaySearchResult(query, authData).map { + if (it.first.isNotEmpty()) { + searchResult.addAll(it.first) } + ResultSupreme.Success( + Pair( + filterWithKeywordSearch( + searchResult, + packageSpecificResults, + query + ), + it.second + ) + ) + } private suspend fun fetchOpenSourceSearchResult( fusedAPIImpl: FusedAPIImpl, @@ -1052,7 +1056,7 @@ class FusedAPIImpl @Inject constructor( private fun getCategoryIconName(category: FusedCategory): String { var categoryTitle = if (category.tag.getOperationalTag() - .contentEquals(AppTag.GPlay().getOperationalTag()) + .contentEquals(AppTag.GPlay().getOperationalTag()) ) category.id else category.title if (categoryTitle.contains(CATEGORY_TITLE_REPLACEABLE_CONJUNCTION)) { @@ -1173,8 +1177,8 @@ class FusedAPIImpl @Inject constructor( by: String? = null ): List { val list = mutableListOf() - val response = (cleanApkAppsRepository.getSearchResult(keyword) as Response).body()?.apps -// val response = cleanAPKRepository.searchApps(keyword, source, type, nres,page, by).body()?.apps + val response = + (cleanApkAppsRepository.getSearchResult(keyword) as Response).body()?.apps response?.forEach { it.updateStatus() @@ -1190,7 +1194,8 @@ class FusedAPIImpl @Inject constructor( query: String, authData: AuthData ): LiveData, Boolean>> { - val searchResults = gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) + val searchResults = + gPlayAPIRepository.getSearchResults(query, authData, ::replaceWithFDroid) return searchResults.map { Pair( it.first, @@ -1212,6 +1217,7 @@ class FusedAPIImpl @Inject constructor( ) } } + /* * This function will replace a GPlay app with F-Droid app if exists, * else will show the GPlay app itself. diff --git a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt index 53ba3ae04..76ed53e79 100644 --- a/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/gplay/GplayRepository.kt @@ -1,3 +1,21 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package foundation.e.apps.api.gplay import android.content.Context @@ -9,7 +27,7 @@ import com.aurora.gplayapi.helpers.SearchHelper import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R -import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.StoreRepository import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.login.LoginSourceRepository import kotlinx.coroutines.Dispatchers @@ -24,18 +42,12 @@ class GplayRepository @Inject constructor( @ApplicationContext private val context: Context, private val gPlayHttpClient: GPlayHttpClient, private val loginSourceRepository: LoginSourceRepository -) : StoreApiRepository { +) : StoreRepository { override suspend fun getHomeScreenData(): Any { val homeScreenData = mutableMapOf>() - val homeElements = mutableMapOf( - context.getString(R.string.topselling_free_apps) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.topselling_free_games) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.GAME), - context.getString(R.string.topgrossing_apps) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.topgrossing_games) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.GAME), - context.getString(R.string.movers_shakers_apps) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.APPLICATION), - context.getString(R.string.movers_shakers_games) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.GAME), - ) + val homeElements = createTopChartElements() + homeElements.forEach { val chart = it.value.keys.iterator().next() val type = it.value.values.iterator().next() @@ -46,12 +58,21 @@ class GplayRepository @Inject constructor( return homeScreenData } + private fun createTopChartElements() = mutableMapOf( + context.getString(R.string.topselling_free_apps) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.topselling_free_games) to mapOf(TopChartsHelper.Chart.TOP_SELLING_FREE to TopChartsHelper.Type.GAME), + context.getString(R.string.topgrossing_apps) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.topgrossing_games) to mapOf(TopChartsHelper.Chart.TOP_GROSSING to TopChartsHelper.Type.GAME), + context.getString(R.string.movers_shakers_apps) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.APPLICATION), + context.getString(R.string.movers_shakers_games) to mapOf(TopChartsHelper.Chart.MOVERS_SHAKERS to TopChartsHelper.Type.GAME), + ) + override suspend fun getSearchResult(query: String): Flow, Boolean>> { return flow { - /* - * Variable names and logic made same as that of Aurora store. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 - */ + /* + * Variable names and logic made same as that of Aurora store. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 + */ val searchHelper = SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) val searchBundle = searchHelper.searchResults(query) @@ -69,38 +90,56 @@ class GplayRepository @Inject constructor( var nextSubBundleSet: MutableSet do { - nextSubBundleSet = searchBundle.subBundles - val newSearchBundle = searchHelper.next(nextSubBundleSet) - if (newSearchBundle.appList.isNotEmpty()) { - searchBundle.apply { - subBundles.clear() - subBundles.addAll(newSearchBundle.subBundles) - emitReplacedList( - this@flow, - initialReplacedList, - INITIAL_LIMIT, - newSearchBundle, - nextSubBundleSet.isNotEmpty(), - ) - } - } + nextSubBundleSet = fetchNextSubBundle( + searchBundle, + searchHelper, + this@flow, + initialReplacedList, + INITIAL_LIMIT + ) } while (nextSubBundleSet.isNotEmpty()) - /* - * 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 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) } + private suspend fun fetchNextSubBundle( + searchBundle: SearchBundle, + searchHelper: SearchHelper, + scope: FlowCollector, Boolean>>, + accumulationList: MutableList, + accumulationLimit: Int, + ): MutableSet { + 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(), + ) + } + } + return nextSubBundleSet + } + override suspend fun getSearchSuggestions(query: String): List { val searchData = mutableListOf() withContext(Dispatchers.IO) { - val searchHelper = SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) + val searchHelper = + SearchHelper(loginSourceRepository.gplayAuth!!).using(gPlayHttpClient) searchData.addAll(searchHelper.searchSuggestions(query)) } return searchData.filter { it.suggestedQuery.isNotBlank() } @@ -122,6 +161,7 @@ class GplayRepository @Inject constructor( */ accumulationList.add(it) } + accumulationList.size == accumulationLimit - 1 -> { /* * If initial limit is 4, and we have reached till 3 apps, @@ -131,6 +171,7 @@ class GplayRepository @Inject constructor( scope.emit(Pair(accumulationList, moreToEmit)) emitInMain(scope, accumulationList, moreToEmit) } + accumulationList.size == accumulationLimit -> { /* * If initial limit is 4, and we have emitted 4 apps, diff --git a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt index 3bde1dcf2..102ef6e29 100644 --- a/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/NamedRepositoryModule.kt @@ -1,15 +1,35 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package foundation.e.apps.di + import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import foundation.e.apps.api.StoreApiRepository +import foundation.e.apps.api.StoreRepository import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.cleanapk.CleanApkAppDetailApi import foundation.e.apps.api.cleanapk.CleanApkAppsRepository +import foundation.e.apps.api.cleanapk.CleanApkPWARepository import foundation.e.apps.api.gplay.GplayRepository import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.login.LoginSourceRepository @@ -26,7 +46,7 @@ object NamedRepositoryModule { @ApplicationContext context: Context, gPlayHttpClient: GPlayHttpClient, loginSourceRepository: LoginSourceRepository - ): StoreApiRepository { + ): StoreRepository { return GplayRepository(context, gPlayHttpClient, loginSourceRepository) } @@ -35,9 +55,8 @@ object NamedRepositoryModule { @Named("cleanApkAppsRepository") fun getCleanApkAppsRepository( cleanAPKInterface: CleanAPKInterface, - cleanApkAppDetailApi: CleanApkAppDetailApi - ): StoreApiRepository { - return CleanApkAppsRepository(cleanAPKInterface, cleanApkAppDetailApi) + ): StoreRepository { + return CleanApkAppsRepository(cleanAPKInterface) } @Singleton @@ -45,8 +64,7 @@ object NamedRepositoryModule { @Named("cleanApkPWARepository") fun getCleanApkPWARepository( cleanAPKInterface: CleanAPKInterface, - cleanApkAppDetailApi: CleanApkAppDetailApi - ): StoreApiRepository { - return CleanApkAppsRepository(cleanAPKInterface, cleanApkAppDetailApi) + ): StoreRepository { + return CleanApkPWARepository(cleanAPKInterface) } } -- GitLab