From a20322a50a693118a200eb7a902048243735065d Mon Sep 17 00:00:00 2001 From: dev-12 Date: Tue, 2 Dec 2025 13:09:00 +0530 Subject: [PATCH 1/4] chore: bump gplayapi lib --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 128d18a1e..ef6c7852d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ hiltCompiler = "1.2.0" hiltWork = "1.2.0" lifecycleExtensions = "1.1.1" fragmentKtx = "1.8.5" -gplayapi = "95ec4f28" +gplayapi = "1316264d" gson = "2.11.0" jacksonDataformatYaml = "2.17.0" jsoup = "1.17.2" -- GitLab From be46e8ea4b7a35b491a70612b109e9f7af2e69ae Mon Sep 17 00:00:00 2001 From: dev-12 Date: Thu, 4 Dec 2025 17:04:58 +0530 Subject: [PATCH 2/4] refactor: rename `getUserType` to `getUser` mostly because it returns user and not user type and we have a property userType which indirectly have getter with same name (`getUserType`) --- .../foundation/e/apps/data/login/AuthenticatorRepository.kt | 2 +- .../foundation/e/apps/data/login/CleanApkAuthenticator.kt | 2 +- app/src/main/java/foundation/e/apps/data/login/LoginCommon.kt | 2 +- .../foundation/e/apps/data/login/PlayStoreAuthenticator.kt | 4 ++-- .../foundation/e/apps/data/preference/AppLoungeDataStore.kt | 2 +- .../foundation/e/apps/data/preference/AppLoungePreference.kt | 4 ++-- .../java/foundation/e/apps/install/updates/UpdatesWorker.kt | 2 +- .../e/apps/install/workmanager/AppInstallProcessor.kt | 2 +- .../main/java/foundation/e/apps/provider/AgeRatingProvider.kt | 2 +- .../main/java/foundation/e/apps/ui/MainActivityViewModel.kt | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/login/AuthenticatorRepository.kt b/app/src/main/java/foundation/e/apps/data/login/AuthenticatorRepository.kt index c8efb84fc..6c98a3801 100644 --- a/app/src/main/java/foundation/e/apps/data/login/AuthenticatorRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/login/AuthenticatorRepository.kt @@ -37,7 +37,7 @@ class AuthenticatorRepository @Inject constructor( return kotlin.runCatching { appLoungeDataStore.getAuthData() }.getOrElse { - throw GPlayLoginException(false, "AuthData is not available", appLoungeDataStore.getUserType()) + throw GPlayLoginException(false, "AuthData is not available", appLoungeDataStore.getUser()) } } diff --git a/app/src/main/java/foundation/e/apps/data/login/CleanApkAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/CleanApkAuthenticator.kt index aec68bcbd..8dac6ac1a 100644 --- a/app/src/main/java/foundation/e/apps/data/login/CleanApkAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/CleanApkAuthenticator.kt @@ -35,7 +35,7 @@ class CleanApkAuthenticator @Inject constructor( ) : StoreAuthenticator { private val user: User - get() = appLoungeDataStore.getUserType() + get() = appLoungeDataStore.getUser() override fun isStoreActive(): Boolean { if (user == User.UNAVAILABLE) { diff --git a/app/src/main/java/foundation/e/apps/data/login/LoginCommon.kt b/app/src/main/java/foundation/e/apps/data/login/LoginCommon.kt index 8ba19f5ed..f2ef1fe12 100644 --- a/app/src/main/java/foundation/e/apps/data/login/LoginCommon.kt +++ b/app/src/main/java/foundation/e/apps/data/login/LoginCommon.kt @@ -40,7 +40,7 @@ class LoginCommon @Inject constructor( } fun getUserType(): User { - return appLoungeDataStore.getUserType() + return appLoungeDataStore.getUser() } suspend fun saveGoogleLogin(email: String, oauth: String) { diff --git a/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt index f2493cbd1..8de91e299 100644 --- a/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt @@ -56,7 +56,7 @@ class PlayStoreAuthenticator @Inject constructor( lateinit var loginManagerFactory: PlayStoreLoginManagerFactory private val user: User - get() = appLoungeDataStore.getUserType() + get() = appLoungeDataStore.getUser() private val loginManager: PlayStoreLoginManager get() = loginManagerFactory.createLoginManager(user) @@ -137,7 +137,7 @@ class PlayStoreAuthenticator @Inject constructor( * Generate new AuthData based on the user type. */ private suspend fun generateAuthData(): ResultSupreme { - return when (appLoungeDataStore.getUserType()) { + return when (appLoungeDataStore.getUser()) { User.ANONYMOUS -> getAuthDataAnonymously() User.GOOGLE -> getAuthDataWithGoogleAccount() else -> ResultSupreme.Error("User type not ANONYMOUS or GOOGLE") diff --git a/app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt b/app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt index 7b4dfccae..871f9fae3 100644 --- a/app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt +++ b/app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt @@ -134,7 +134,7 @@ class AppLoungeDataStore @Inject constructor( } } - fun getUserType(): User { // TODO: Rename this to getUser() + fun getUser(): User { return runBlocking { userType.first().run { val userStrings = User.values().map { it.name } diff --git a/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt b/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt index 66c62314b..c750ce7e1 100644 --- a/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt +++ b/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt @@ -66,7 +66,7 @@ class AppLoungePreference @Inject constructor( fun enablePwa() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_PWA, true) } fun getUpdateInterval(): Long { - val currentUser = appLoungeDataStore.getUserType() + val currentUser = appLoungeDataStore.getUser() return when (currentUser) { User.ANONYMOUS -> preferenceManager.getString( context.getString(R.string.update_check_intervals_anonymous), @@ -96,7 +96,7 @@ class AppLoungePreference @Inject constructor( if (migrationCompleted) return - if (appLoungeDataStore.getUserType() == User.ANONYMOUS) { + if (appLoungeDataStore.getUser() == User.ANONYMOUS) { val currentInterval = preferenceManager.getString( context.getString(R.string.update_check_intervals), null diff --git a/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt index 1bd7deded..f2907200f 100644 --- a/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt +++ b/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt @@ -101,7 +101,7 @@ class UpdatesWorker @AssistedInject constructor( } private fun getUser(): User { - return appLoungeDataStore.getUserType() + return appLoungeDataStore.getUser() } private suspend fun checkForUpdates() { diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index 76d139825..4f911c68b 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -126,7 +126,7 @@ class AppInstallProcessor @Inject constructor( isSystemApp: Boolean = false ) { try { - val user = appLoungeDataStore.getUserType() + val user = appLoungeDataStore.getUser() if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { val authData = appLoungeDataStore.getAuthData() if (!appInstall.isFree && authData.isAnonymous) { diff --git a/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt b/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt index efd1c7ed7..39f0ea550 100644 --- a/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt +++ b/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt @@ -126,7 +126,7 @@ class AgeRatingProvider : ContentProvider() { private fun getLoginType(): Cursor { val cursor = MatrixCursor(arrayOf(COLUMN_LOGIN_TYPE)) - cursor.addRow(arrayOf(appLoungeDataStore.getUserType())) + cursor.addRow(arrayOf(appLoungeDataStore.getUser())) return cursor } diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index 79dfb4982..520bf59e7 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -104,7 +104,7 @@ class MainActivityViewModel @Inject constructor( } fun getUser(): User { - return appLoungeDataStore.getUserType() + return appLoungeDataStore.getUser() } fun getUserEmail(): String { -- GitLab From af0bb5d1d7ddfcde9df9801423049944c0adf41a Mon Sep 17 00:00:00 2001 From: dev-12 Date: Tue, 2 Dec 2025 13:38:58 +0530 Subject: [PATCH 3/4] fix: handle oss and gplay update correctly --- .../e/apps/ui/updates/UpdatesViewModel.kt | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/ui/updates/UpdatesViewModel.kt b/app/src/main/java/foundation/e/apps/ui/updates/UpdatesViewModel.kt index 0d05577e4..839d50972 100644 --- a/app/src/main/java/foundation/e/apps/ui/updates/UpdatesViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/updates/UpdatesViewModel.kt @@ -30,8 +30,11 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.exceptions.CleanApkException import foundation.e.apps.data.login.exceptions.GPlayException +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.updates.UpdatesManagerRepository import kotlinx.coroutines.launch import javax.inject.Inject @@ -40,6 +43,8 @@ import javax.inject.Inject class UpdatesViewModel @Inject constructor( private val updatesManagerRepository: UpdatesManagerRepository, private val applicationRepository: ApplicationRepository, + private val appLoungeDataStore: AppLoungeDataStore, + private val preference: AppLoungePreference, private val stores: Stores ) : ViewModel() { @@ -59,40 +64,33 @@ class UpdatesViewModel @Inject constructor( return true } - fun loadUpdates() { - viewModelScope.launch { - exceptionsList.clear() - val updatesResult = updatesManagerRepository.getUpdates() - val ossUpdatesResult = updatesManagerRepository.getUpdatesOSS() - - updatesList.postValue( - mutableListOf().apply { - addAll(updatesResult.first) - addAll(ossUpdatesResult.first) - }.toList() - ) - - if (updatesResult.second != ResultStatus.OK) { - val status = updatesResult.second - exceptionsList.add( - GPlayException( - updatesResult.second == ResultStatus.TIMEOUT, - status.message.ifBlank { "Data load error" } - ) - ) - } - if (ossUpdatesResult.second != ResultStatus.OK) { - val status = ossUpdatesResult.second - exceptionsList.add( - CleanApkException( - updatesResult.second == ResultStatus.TIMEOUT, - status.message.ifBlank { "Data load error" } - ) - ) - } + fun loadUpdates() = viewModelScope.launch { + exceptionsList.clear() + val currentUser = appLoungeDataStore.getUser() + val isOssOnly = !preference.isPlayStoreSelected() || + (currentUser == User.UNAVAILABLE || currentUser == User.NO_GOOGLE) + val updates = if (isOssOnly) { + updatesManagerRepository.getUpdatesOSS() + } else { + updatesManagerRepository.getUpdates() + } + if (updates.second == ResultStatus.OK) { + updatesList.postValue(updates.first) exceptionsLiveData.postValue(exceptionsList) + return@launch + } + + val isTimeout = updates.second == ResultStatus.TIMEOUT + val errorMessage = updates.second.message.ifBlank { "Data load error" } + val exception = if (isOssOnly) { + CleanApkException(isTimeout, errorMessage) + } else { + GPlayException(isTimeout, errorMessage) } + exceptionsList.add(exception) + updatesList.postValue(updates.first) + exceptionsLiveData.postValue(exceptionsList) } fun checkWorkInfoListHasAnyUpdatableWork(workInfoList: List): Boolean { -- GitLab From 4619ab0dc5db8b26448766244585ea80ac41d4c7 Mon Sep 17 00:00:00 2001 From: dev-12 Date: Thu, 4 Dec 2025 17:06:35 +0530 Subject: [PATCH 4/4] test: add unit test for UpdatesViewModel --- .../e/apps/ui/updates/UpdatesViewModelTest.kt | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt diff --git a/app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt b/app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt new file mode 100644 index 000000000..eb7f0fd34 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt @@ -0,0 +1,168 @@ +package foundation.e.apps.ui.updates + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import foundation.e.apps.data.Stores +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.login.exceptions.CleanApkException +import foundation.e.apps.data.login.exceptions.GPlayException +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.data.updates.UpdatesManagerImpl +import foundation.e.apps.data.updates.UpdatesManagerRepository +import foundation.e.apps.util.getOrAwaitValue +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class UpdatesViewModelTest { + + companion object { + + val enabledStore = listOf( + Source.PLAY_STORE, + Source.SYSTEM_APP, + Source.OPEN_SOURCE, + Source.PWA + ) + + val ossUpdates = listOf( + Application(name = "Oss update one", package_name = "io.murena.oss.one"), + Application(name = "Oss update two", package_name = "io.murena.oss.two"), + Application(name = "Oss update three", package_name = "io.murena.oss.three") + ) + + val gplayUpdates = listOf( + Application(name = "Gplay update one", package_name = "io.murena.oss.one"), + Application(name = "Gplay update two", package_name = "io.murena.oss.two"), + Application(name = "Gplay update three", package_name = "io.murena.oss.three"), + Application(name = "Gplay update four", package_name = "io.murena.oss.four"), + Application(name = "Gplay update five", package_name = "io.murena.oss.five"), + Application(name = "Gplay update six", package_name = "io.murena.oss.six") + ) + + val allUpdates = mutableListOf().apply { + addAll(ossUpdates) + addAll(gplayUpdates) + } + + } + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private val applicationRepository by lazy { mock() } + private val updatesManagerImpl by lazy { mock() } + private val appLoungeDataStore by lazy { mock() } + private val stores by lazy { mock() } + private val appLoungePreference by lazy { mock() } + + private lateinit var updatesManagerRepository: UpdatesManagerRepository + private lateinit var updatesViewModel: UpdatesViewModel + + @Before + fun setup() = runBlocking { + MockitoAnnotations.openMocks(this) + + whenever(updatesManagerImpl.getUpdates()) + .thenReturn(Pair(allUpdates, ResultStatus.OK)) + + whenever(updatesManagerImpl.getUpdatesOSS()) + .thenReturn(Pair(ossUpdates, ResultStatus.OK)) + + whenever(updatesManagerImpl.getApplicationCategoryPreference()) + .thenReturn(enabledStore.map { it.name }) + + whenever(appLoungeDataStore.getUser()) + .thenReturn(User.GOOGLE) + whenever(appLoungePreference.isPlayStoreSelected()) + .thenReturn(true) + + updatesManagerRepository = UpdatesManagerRepository(updatesManagerImpl) + + updatesViewModel = UpdatesViewModel( + updatesManagerRepository = updatesManagerRepository, + applicationRepository = applicationRepository, + appLoungeDataStore = appLoungeDataStore, + stores = stores, + preference = appLoungePreference + ) + } + + @Test + fun `insure updates are not empty`() = runBlocking { + updatesViewModel.loadUpdates() + val updates = updatesViewModel.updatesList.getOrAwaitValue() + assert(updates.isNotEmpty()) + } + + @Test + fun `insure getUpdates include all updates`() = runBlocking { + updatesViewModel.loadUpdates() + val getUpdates = updatesViewModel.updatesList.getOrAwaitValue() + assert(getUpdates.size == allUpdates.size) + } + + @Test + fun `insure error are shown properly`() = runBlocking { + whenever(updatesManagerImpl.getUpdates()) + .thenReturn(Pair(emptyList(), ResultStatus.TIMEOUT)) + + updatesViewModel.loadUpdates() + val updates = updatesViewModel.updatesList.getOrAwaitValue() + val errors = updatesViewModel.exceptionsList + + assert(updates.isEmpty()) + assert(errors.isNotEmpty()) + assert(errors.size == 1) + assert(errors.firstOrNull() is GPlayException) + } + + @Test + fun `insure cleanApk error is shown when gPlay is disabled`() = runBlocking { + + whenever(appLoungeDataStore.getUser()) + .thenReturn(User.NO_GOOGLE) + whenever(appLoungePreference.isPlayStoreSelected()) + .thenReturn(false) + + whenever(updatesManagerImpl.getUpdatesOSS()) + .thenReturn(Pair(emptyList(), ResultStatus.UNKNOWN)) + + + updatesViewModel.loadUpdates() + val updates = updatesViewModel.updatesList.getOrAwaitValue() + val errors = updatesViewModel.exceptionsList + + assert(updates.isEmpty()) + assert(errors.isNotEmpty()) + assert(errors.size == 1) + assert(errors.firstOrNull() is CleanApkException) + } + + @Test + fun `insure oss update are shown when gPlay is disabled`() = runBlocking { + whenever(appLoungeDataStore.getUser()) + .thenReturn(User.NO_GOOGLE) + + whenever(updatesManagerImpl.getUpdatesOSS()) + .thenReturn(Pair(ossUpdates, ResultStatus.OK)) + + updatesViewModel.loadUpdates() + val updates = updatesViewModel.updatesList.getOrAwaitValue() + val errors = updatesViewModel.exceptionsList + + assert(updates.isNotEmpty()) + assert(errors.isEmpty()) + assert(updates.size == ossUpdates.size) + assert(updates.containsAll(ossUpdates)) + } + +} \ No newline at end of file -- GitLab