Loading app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +63 −15 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import com.aurora.gplayapi.data.models.PlayFile import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.exceptions.GooglePlayException import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.CategoryHelper import com.aurora.gplayapi.helpers.ContentRatingHelper import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.contracts.CategoryStreamContract import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper import com.aurora.gplayapi.helpers.web.WebCategoryHelper import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper import com.aurora.gplayapi.helpers.web.WebTopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext Loading Loading @@ -116,14 +118,31 @@ class PlayStoreRepository @Inject constructor( ) fun getAppsByCategory(category: String, pageUrl: String?): StreamCluster { val subCategoryHelper = WebCategoryStreamHelper().using(gPlayHttpClient) val helper = WebCategoryStreamHelper().using(gPlayHttpClient) if (!pageUrl.isNullOrEmpty()) { return subCategoryHelper.nextStreamCluster(id = -1, nextPageUrl = pageUrl) val bundle = if (!pageUrl.isNullOrEmpty()) { helper.nextStreamBundle( id = -1, category = StreamContract.Category.NONE, nextPageToken = pageUrl, ) } else { helper.fetch(upperCaseCategory(category)) } val bundle = subCategoryHelper.fetch(upperCaseCategory(category)) return bundle.streamClusters.entries.first().value val drainedClusters = bundle.streamClusters.values.map { cluster -> drainCluster(helper, cluster) } val mergedApps = drainedClusters .flatMap { it.clusterAppList } .distinctBy { it.packageName } return StreamCluster( id = bundle.id, clusterAppList = mergedApps.toMutableList(), clusterNextPageUrl = bundle.streamNextPageUrl, ) } private fun upperCaseCategory(path: String): String { Loading @@ -132,17 +151,42 @@ class PlayStoreRepository @Inject constructor( return "$basePath/$lastPart" } suspend fun getCategories(type: CategoryType?): List<Category> { val categoryList = mutableListOf<Category>() if (type == null) { return categoryList } withContext(Dispatchers.IO) { val categoryHelper = WebCategoryHelper().using(gPlayHttpClient) categoryList.addAll(categoryHelper.getAllCategories(getCategoryType(type))) } return categoryList /** * Walks a cluster's pagination chain to fatten the category landing page, capped at * [MAX_CLUSTER_PAGES] pages to bound load time and rate-limit cost. Apps in pages beyond the * cap are dropped — acceptable because category browse is a one-shot fetch (no load-more), * so the returned cursor is informational only. */ private fun drainCluster( helper: CategoryStreamContract, first: StreamCluster, ): StreamCluster { val apps = first.clusterAppList.toMutableList() var nextUrl = first.clusterNextPageUrl var page = 0 while (nextUrl.isNotBlank() && page < MAX_CLUSTER_PAGES) { val next = helper.nextStreamCluster(id = first.id, nextPageUrl = nextUrl) apps += next.clusterAppList // Guard against a stuck cursor: if Play hands back the same nextPageUrl, stop // instead of looping forever within the page cap. if (next.clusterNextPageUrl == nextUrl) break nextUrl = next.clusterNextPageUrl page++ } return first.copy(clusterAppList = apps, clusterNextPageUrl = nextUrl) } suspend fun getCategories(type: CategoryType?): List<Category> = withContext(Dispatchers.IO) { if (type == null) return@withContext emptyList() executeWithPlayAuthRecovery( operationName = "categories", request = { val helper = CategoryHelper( playStoreAuthManager.requireValidatedPlayStoreAuth() ).using(gPlayHttpClient) helper.getAllCategories(getCategoryType(type)) }, ) } // batch request response might not contain images and descriptions of the app Loading Loading @@ -471,4 +515,8 @@ class PlayStoreRepository @Inject constructor( ) } } companion object { private const val MAX_CLUSTER_PAGES = 5 } } Loading
app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +63 −15 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import com.aurora.gplayapi.data.models.PlayFile import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.exceptions.GooglePlayException import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.CategoryHelper import com.aurora.gplayapi.helpers.ContentRatingHelper import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.contracts.CategoryStreamContract import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper import com.aurora.gplayapi.helpers.web.WebCategoryHelper import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper import com.aurora.gplayapi.helpers.web.WebTopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext Loading Loading @@ -116,14 +118,31 @@ class PlayStoreRepository @Inject constructor( ) fun getAppsByCategory(category: String, pageUrl: String?): StreamCluster { val subCategoryHelper = WebCategoryStreamHelper().using(gPlayHttpClient) val helper = WebCategoryStreamHelper().using(gPlayHttpClient) if (!pageUrl.isNullOrEmpty()) { return subCategoryHelper.nextStreamCluster(id = -1, nextPageUrl = pageUrl) val bundle = if (!pageUrl.isNullOrEmpty()) { helper.nextStreamBundle( id = -1, category = StreamContract.Category.NONE, nextPageToken = pageUrl, ) } else { helper.fetch(upperCaseCategory(category)) } val bundle = subCategoryHelper.fetch(upperCaseCategory(category)) return bundle.streamClusters.entries.first().value val drainedClusters = bundle.streamClusters.values.map { cluster -> drainCluster(helper, cluster) } val mergedApps = drainedClusters .flatMap { it.clusterAppList } .distinctBy { it.packageName } return StreamCluster( id = bundle.id, clusterAppList = mergedApps.toMutableList(), clusterNextPageUrl = bundle.streamNextPageUrl, ) } private fun upperCaseCategory(path: String): String { Loading @@ -132,17 +151,42 @@ class PlayStoreRepository @Inject constructor( return "$basePath/$lastPart" } suspend fun getCategories(type: CategoryType?): List<Category> { val categoryList = mutableListOf<Category>() if (type == null) { return categoryList } withContext(Dispatchers.IO) { val categoryHelper = WebCategoryHelper().using(gPlayHttpClient) categoryList.addAll(categoryHelper.getAllCategories(getCategoryType(type))) } return categoryList /** * Walks a cluster's pagination chain to fatten the category landing page, capped at * [MAX_CLUSTER_PAGES] pages to bound load time and rate-limit cost. Apps in pages beyond the * cap are dropped — acceptable because category browse is a one-shot fetch (no load-more), * so the returned cursor is informational only. */ private fun drainCluster( helper: CategoryStreamContract, first: StreamCluster, ): StreamCluster { val apps = first.clusterAppList.toMutableList() var nextUrl = first.clusterNextPageUrl var page = 0 while (nextUrl.isNotBlank() && page < MAX_CLUSTER_PAGES) { val next = helper.nextStreamCluster(id = first.id, nextPageUrl = nextUrl) apps += next.clusterAppList // Guard against a stuck cursor: if Play hands back the same nextPageUrl, stop // instead of looping forever within the page cap. if (next.clusterNextPageUrl == nextUrl) break nextUrl = next.clusterNextPageUrl page++ } return first.copy(clusterAppList = apps, clusterNextPageUrl = nextUrl) } suspend fun getCategories(type: CategoryType?): List<Category> = withContext(Dispatchers.IO) { if (type == null) return@withContext emptyList() executeWithPlayAuthRecovery( operationName = "categories", request = { val helper = CategoryHelper( playStoreAuthManager.requireValidatedPlayStoreAuth() ).using(gPlayHttpClient) helper.getAllCategories(getCategoryType(type)) }, ) } // batch request response might not contain images and descriptions of the app Loading Loading @@ -471,4 +515,8 @@ class PlayStoreRepository @Inject constructor( ) } } companion object { private const val MAX_CLUSTER_PAGES = 5 } }