diff --git a/app/build.gradle b/app/build.gradle index a3df297013e3ee28f0ace3e4b3457d92c23fa232..c324b36834b93086c4ae9296d977f19915afdaa4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,7 +153,7 @@ dependencies { api files('libs/splitinstall-lib.jar') implementation 'foundation.e.lib:telemetry:0.0.11-alpha' - implementation "foundation.e:gplayapi:3.2.10-2" + implementation "foundation.e:gplayapi:3.2.10-3" implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.fragment:fragment-ktx:1.5.6' diff --git a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt index 50480c04e5915be98cf7519230a5ca7017929153..798eb4daa09362d8dfed036f1b60cab5a96ee0f2 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt @@ -1,6 +1,5 @@ /* - * Copyright MURENA SAS 2023 - * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2024 MURENA SAS * * 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 @@ -14,6 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ package foundation.e.apps.data.playstore @@ -21,6 +21,7 @@ package foundation.e.apps.data.playstore 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.ContentRating import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.StoreRepository @@ -43,4 +44,9 @@ interface PlayStoreRepository : StoreRepository { versionCode: Int, offerType: Int ): List + + suspend fun updateContentRatingWithId( + appPackage: String, + contentRating: ContentRating + ): ContentRating } diff --git a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepositoryImpl.kt b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepositoryImpl.kt index 63a467e92bba59fb25e1f16ae407080c7cd8c254..d971814641eab2d081f901391e634fea012d5cff 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepositoryImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepositoryImpl.kt @@ -1,6 +1,5 @@ /* - * Copyright MURENA SAS 2023 - * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2024 MURENA SAS * * 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 @@ -14,6 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ package foundation.e.apps.data.playstore @@ -23,6 +23,7 @@ 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.Category +import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.StreamCluster @@ -30,14 +31,15 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.CategoryAppsHelper import com.aurora.gplayapi.helpers.CategoryHelper import com.aurora.gplayapi.helpers.Chart +import com.aurora.gplayapi.helpers.ContentRatingHelper import com.aurora.gplayapi.helpers.PurchaseHelper 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.data.application.utils.CategoryType -import foundation.e.apps.data.playstore.utils.GPlayHttpClient import foundation.e.apps.data.login.AuthenticatorRepository +import foundation.e.apps.data.playstore.utils.GPlayHttpClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber @@ -78,8 +80,7 @@ class PlayStoreRepositoryImpl @Inject constructor( subBundle: MutableSet? ): Pair, MutableSet> { var authData = authenticatorRepository.gplayAuth!! - val searchHelper = - SearchHelper(authData).using(gPlayHttpClient) + val searchHelper = SearchHelper(authData).using(gPlayHttpClient) Timber.d("Fetching search result for $query, subBundle: $subBundle") @@ -115,8 +116,7 @@ class PlayStoreRepositoryImpl @Inject constructor( override suspend fun getAppsByCategory(category: String, pageUrl: String?): StreamCluster { val authData = authenticatorRepository.gplayAuth!! - val subCategoryHelper = - CategoryAppsHelper(authData).using(gPlayHttpClient) + val subCategoryHelper = CategoryAppsHelper(authData).using(gPlayHttpClient) if (!pageUrl.isNullOrEmpty()) { return subCategoryHelper.next(pageUrl) @@ -163,7 +163,8 @@ class PlayStoreRepositoryImpl @Inject constructor( } private fun getCategoryType(type: CategoryType): Category.Type { - return if (type == CategoryType.APPLICATION) Category.Type.APPLICATION else Category.Type.GAME + return if (type == CategoryType.APPLICATION) Category.Type.APPLICATION + else Category.Type.GAME } private suspend fun getTopApps( @@ -207,14 +208,24 @@ class PlayStoreRepositoryImpl @Inject constructor( withContext(Dispatchers.IO) { val purchaseHelper = PurchaseHelper(authData).using(gPlayHttpClient) downloadData.addAll( - purchaseHelper.getOnDemandModule( - packageName, - moduleName, - versionCode, - offerType - ) + purchaseHelper.getOnDemandModule(packageName, moduleName, versionCode, offerType) ) } return downloadData } + + override suspend fun updateContentRatingWithId( + appPackage: String, + contentRating: ContentRating + ): ContentRating { + val authData = authenticatorRepository.gplayAuth!! + val contentRatingHelper = ContentRatingHelper(authData) + + return withContext(Dispatchers.IO) { + contentRatingHelper.updateContentRatingWithId( + appPackage, + contentRating + ) + } + } } diff --git a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt index 204767a8ffc96b6f0d58ffeec6ad754c6b4b0a99..4e43c269faa886ce92e1bdbee3926fd6d80df01b 100644 --- a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2024 MURENA SAS + * + * 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 dagger.Binds @@ -12,6 +30,8 @@ import foundation.e.apps.data.fdroid.FdroidRepository import foundation.e.apps.data.fdroid.IFdroidRepository import foundation.e.apps.data.fusedDownload.FusedManagerImpl import foundation.e.apps.data.fusedDownload.IFusedManager +import foundation.e.apps.data.playstore.PlayStoreRepository +import foundation.e.apps.data.playstore.PlayStoreRepositoryImpl import javax.inject.Singleton @Module @@ -32,4 +52,8 @@ interface RepositoryModule { @Singleton @Binds fun getPrivacyScoreRepository(privacyScoreRepositoryImpl: PrivacyScoreRepositoryImpl): PrivacyScoreRepository + + @Singleton + @Binds + fun getPlayStoreRepository(playStoreRepository: PlayStoreRepositoryImpl): PlayStoreRepository } diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt index d71d6def54bc50c53f44a07174826322ea67a2cb..e2011f01b23d36412450353097e7941a4d6e53a0 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt @@ -494,23 +494,13 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private fun collectAppContentRatingState() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - applicationViewModel.appContentRating.collectLatest(::updateContentRatingUi) + applicationViewModel.appContentRatingState + .collectLatest(::updateContentRatingUi) } } } - private fun updateContentRatingUi(contentRating: ContentRating) { - fun loadContentRating(contentRating: ContentRating) { - lifecycleScope.launch { - val drawable = loadContentRatingDrawable(contentRating.artwork.url) - displayRating(contentRating, drawable) - } - } - - fun hideContentRating() { - binding.ratingsInclude.appContentRatingLayout.visibility = View.GONE - } - + private suspend fun updateContentRatingUi(contentRating: ContentRating) { if (contentRating.isValid()) { loadContentRating(contentRating) } else { @@ -518,6 +508,15 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } } + private suspend fun loadContentRating(contentRating: ContentRating) { + val drawable = loadContentRatingDrawable(contentRating.artwork.url) + displayRating(contentRating, drawable) + } + + private fun hideContentRating() { + binding.ratingsInclude.appContentRatingLayout.visibility = View.GONE + } + private fun displayRating(contentRating: ContentRating, drawable: Drawable?) { binding.ratingsInclude.apply { appContentRatingTitle.text = contentRating.title diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt index 8d1815588b215f8ff99a433f278ccda6b24f4890..57b2be86f6c3d5ce6790a22581f1e1327c222308 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt @@ -36,6 +36,7 @@ import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.login.exceptions.CleanApkException import foundation.e.apps.data.login.exceptions.GPlayException +import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.install.download.data.DownloadProgress import foundation.e.apps.install.download.data.DownloadProgressLD import foundation.e.apps.ui.application.ShareButtonVisibilityState.Hidden @@ -53,6 +54,7 @@ class ApplicationViewModel @Inject constructor( downloadProgressLD: DownloadProgressLD, private val applicationRepository: ApplicationRepository, private val fusedManagerRepository: FusedManagerRepository, + private val playStoreRepository: PlayStoreRepository, ) : LoadingViewModel() { val application: MutableLiveData> = MutableLiveData() @@ -64,8 +66,8 @@ class ApplicationViewModel @Inject constructor( private val _shareButtonVisibilityState = MutableStateFlow(Hidden) val shareButtonVisibilityState = _shareButtonVisibilityState.asStateFlow() - private val _appContentRating = MutableStateFlow(ContentRating()) - val appContentRating = _appContentRating.asStateFlow() + private val _appContentRatingState = MutableStateFlow(ContentRating()) + val appContentRatingState = _appContentRatingState.asStateFlow() fun loadData( id: String, @@ -119,7 +121,7 @@ class ApplicationViewModel @Inject constructor( application.postValue(appData) updateShareVisibilityState(appData.first.shareUri.toString()) - updateAppContentRatingState(appData.first.contentRating) + updateAppContentRatingState(packageName, appData.first.contentRating) val status = appData.second @@ -146,8 +148,17 @@ class ApplicationViewModel @Inject constructor( } } - private fun updateAppContentRatingState(value: ContentRating) { - _appContentRating.update { value } + private suspend fun updateAppContentRatingState( + packageName: String, + contentRating: ContentRating + ) { + // Initially update the state without ID to show the UI immediately + _appContentRatingState.update { contentRating } + + val ratingWithId = playStoreRepository.updateContentRatingWithId(packageName, contentRating) + + // Later, update with a new rating; no visual change in the UI + _appContentRatingState.update { contentRating.copy(id = ratingWithId.id) } } private fun updateShareVisibilityState(shareUri: String) {