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 c8efb84fc1119f20de0af0cd7fb5979a182b6d20..6c98a3801d495ee7cefa86099333c3cd2ab8231f 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 aec68bcbdffab823aa08a1cd5407190e24ad2c45..8dac6ac1aa04b04be7c7cb0e75d810efb16f8e71 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 8ba19f5ed0b4d0e0a857e31da4fbdd1c43b7c44f..f2ef1fe12ab151638e74fa856d4d185d217492b1 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 f2493cbd1d334f8486b7c179741aecbf7fe1b292..8de91e2994c6c9d20088d55294c9948b2135d436 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 7b4dfccae864c173c86a7d732bec5ba632ce5aea..871f9fae3a242c5ac9ff4d562c23ffed0f2d6320 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 66c62314bd4d0c3931803a5a9dbc7f3df61a204d..c750ce7e1512b14b7596723b30b408775df1826a 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 1bd7deded4bfd261a34d790247feed481241abd5..f2907200fad013b69d98450d14be62fa2262d5ab 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 76d139825249d75cc7a3ed7af01b6a5215ef7d05..4f911c68bb531b9d15f1baefbe8239591ebcda1f 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 efd1c7ed7519e14e17f58ae0b4534caa72225cdf..39f0ea550f34311801115b2de0242ec1cb01f5a7 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 79dfb498239d5f4d7d546ff4d780cd89b0ff51a1..520bf59e7bee0190a29748326ea7388aac6c069a 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 { 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 0d05577e4ea0a12b79f633d6ba0339d80cf7fed1..839d50972d86f3faaed6cc79d5eeaa23c6d59a06 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 { 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 0000000000000000000000000000000000000000..eb7f0fd3408c2ee327f0f72bae84528c505592a3 --- /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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 128d18a1ed1c59cc550b7e8c8098f1012b36db0e..ef6c7852d330a7ec282ea29005a5c0702dd6934c 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"