diff --git a/.gitignore b/.gitignore index 10cfdbfaf8ed53e905f414c7ae3c4c4050d83105..2fef324f5f9d4c5826bba7568b3abda51ae7d862 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ .externalNativeBuild .cxx local.properties +**/build/ diff --git a/app/build.gradle b/app/build.gradle index 8d65015a3d0989230af383917d476f3be3426f3f..e5024047229024c04acef1afc80de5ef679a2751 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,6 +96,15 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + testOptions { + unitTests { + includeAndroidResources = true + all { + systemProperty 'robolectric.usePreinstrumentedJars', 'false' + } + } + } + signingConfigs { platformConfig { storeFile file("keystore/platform.jks") @@ -195,6 +204,8 @@ dependencies { // Project dependencies implementation(project(":auth-data-lib")) implementation(project(":parental-control-data")) + implementation(project(":domain")) + implementation(project(":data")) // eFoundation libraries implementation(libs.telemetry) @@ -262,6 +273,7 @@ dependencies { testImplementation(libs.core.testing) testImplementation(libs.mockk) testImplementation(libs.robolectric) + testImplementation(libs.guava) // Coil and PhotoView implementation(libs.coil) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index 98b7056d47c10a915b3d18564dd14ba0165e4609..78a15188aefe06ade3e651e716c84108b042742b 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -34,18 +34,15 @@ import foundation.e.apps.data.install.AppInstallDAO import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PkgManagerBR import foundation.e.apps.data.install.updates.UpdatesWorkManager -import foundation.e.apps.data.install.workmanager.InstallWorkManager -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.system.CustomUncaughtExceptionHandler +import foundation.e.apps.domain.preferences.SessionRepository +import foundation.e.apps.domain.preferences.updateinterval.GetUpdateIntervalUseCase +import foundation.e.apps.domain.preferences.updateinterval.MigrateAnonymousUserUpdateIntervalUseCase import foundation.e.apps.ui.setup.tos.TOS_VERSION import foundation.e.lib.telemetry.Telemetry import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import timber.log.Timber import timber.log.Timber.Forest.plant import java.util.concurrent.Executors @@ -61,12 +58,6 @@ class AppLoungeApplication : Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory - @Inject - lateinit var appLoungeDataStore: AppLoungeDataStore - - @Inject - lateinit var appLoungePreference: AppLoungePreference - @Inject lateinit var appInstallDao: AppInstallDAO @@ -77,21 +68,30 @@ class AppLoungeApplication : Application(), Configuration.Provider { @IoCoroutineScope lateinit var coroutineScope: CoroutineScope + @Inject + lateinit var sessionRepository: SessionRepository + + @Inject + lateinit var getUpdateIntervalUseCase: GetUpdateIntervalUseCase + + @Inject + lateinit var migrateAnonymousUserUpdateIntervalUseCase: MigrateAnonymousUserUpdateIntervalUseCase + + @Inject + lateinit var pkgManagerBR: PkgManagerBR + @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun onCreate() { super.onCreate() Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler) - InstallWorkManager.context = this - // Register broadcast receiver for package manager - val pkgManagerBR = object : PkgManagerBR() {} registerReceiver(pkgManagerBR, appLoungePackageManager.getFilter(), RECEIVER_EXPORTED) - val currentVersion = runBlocking { appLoungeDataStore.tosVersion.first() } - if (!currentVersion.contentEquals(TOS_VERSION)) { - MainScope().launch { - appLoungeDataStore.saveTOCStatus(false, "") + coroutineScope.launch { + val currentVersion = sessionRepository.awaitTosVersion() + if (!currentVersion.contentEquals(TOS_VERSION)) { + sessionRepository.saveTocStatus(false, "") } } @@ -114,16 +114,25 @@ class AppLoungeApplication : Application(), Configuration.Provider { }) } - appLoungePreference.migrateAnonymousUserUpdateInterval() - UpdatesWorkManager.enqueueWork( - this, - appLoungePreference.getUpdateInterval(), - ExistingPeriodicWorkPolicy.KEEP - ) + coroutineScope.launch { + migrateAnonymousUserUpdateIntervalUseCase() + val updateInterval = getUpdateIntervalUseCase() + UpdatesWorkManager.enqueueWork( + this@AppLoungeApplication, + updateInterval, + ExistingPeriodicWorkPolicy.KEEP + ) + } - removeStalledInstallationFromDb() + // Robolectric eagerly creates the real Application for many unrelated tests. + // Skip install DB cleanup there to avoid background Room work leaking across tests. + if (!isRunningUnderRobolectric()) { + removeStalledInstallationFromDb() + } } + private fun isRunningUnderRobolectric(): Boolean = Build.FINGERPRINT == "robolectric" + private fun removeStalledInstallationFromDb() = coroutineScope.launch { val existingInstallations = appInstallDao.getItemInInstallation().toMutableList() if (existingInstallations.isEmpty()) { diff --git a/app/src/main/java/foundation/e/apps/data/DownloadManager.kt b/app/src/main/java/foundation/e/apps/data/DownloadManager.kt index b20650f4f9d1273ea3f1c3ce80f438ea3f936efd..994c4265c46d88b7880a8c11d5132120eebee60a 100644 --- a/app/src/main/java/foundation/e/apps/data/DownloadManager.kt +++ b/app/src/main/java/foundation/e/apps/data/DownloadManager.kt @@ -25,7 +25,7 @@ import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting import foundation.e.apps.R -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -48,7 +48,7 @@ class DownloadManager @Inject constructor( private val downloadManager: DownloadManager, @Named("cacheDir") private val cacheDir: String, private val downloadManagerQuery: DownloadManager.Query, - private val appLoungePreference: AppLoungePreference, + private val appPreferencesRepository: AppPreferencesRepository, ) { private val downloadsMaps = HashMap() @@ -82,7 +82,7 @@ class DownloadManager @Inject constructor( val request = DownloadManager.Request(url.toUri()) .setTitle(context.getString(R.string.downloading)) .setDestinationUri(Uri.fromFile(downloadFile)) - if (appLoungePreference.isOnlyUnmeteredNetworkEnabled()) { + if (appPreferencesRepository.isOnlyUnmeteredNetworkEnabled()) { // Set to true by default for Download requests request.setAllowedOverMetered(false) } diff --git a/app/src/main/java/foundation/e/apps/data/Stores.kt b/app/src/main/java/foundation/e/apps/data/Stores.kt index ca076e86cf386adb2a1ad317712434ecdd56656b..40f8b9c23958783e833941cf87ad9f7e10182107 100644 --- a/app/src/main/java/foundation/e/apps/data/Stores.kt +++ b/app/src/main/java/foundation/e/apps/data/Stores.kt @@ -26,7 +26,7 @@ import foundation.e.apps.data.enums.Source.OPEN_SOURCE import foundation.e.apps.data.enums.Source.PLAY_STORE import foundation.e.apps.data.enums.Source.PWA import foundation.e.apps.data.playstore.PlayStoreRepository -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -39,14 +39,14 @@ class Stores @Inject constructor( playStoreRepository: PlayStoreRepository, cleanApkAppsRepository: CleanApkAppsRepository, cleanApkPwaRepository: CleanApkPwaRepository, - appLoungePreference: AppLoungePreference + appPreferencesRepository: AppPreferencesRepository ) { private val storeConfigs: Map = buildStoreConfigs( playStoreRepository, cleanApkAppsRepository, cleanApkPwaRepository, - appLoungePreference + appPreferencesRepository ) private val searchEligibleSources = storeConfigs.keys @@ -107,24 +107,24 @@ internal fun buildStoreConfigs( playStoreRepository: PlayStoreRepository, cleanApkAppsRepository: CleanApkAppsRepository, cleanApkPwaRepository: CleanApkPwaRepository, - appLoungePreference: AppLoungePreference + appPreferencesRepository: AppPreferencesRepository ): Map = mapOf( PLAY_STORE to StoreConfig( repository = playStoreRepository, - isEnabled = { appLoungePreference.isPlayStoreSelected() }, - enable = { appLoungePreference.enablePlayStore() }, - disable = { appLoungePreference.disablePlayStore() }, + isEnabled = { appPreferencesRepository.isPlayStoreSelected() }, + enable = { appPreferencesRepository.enablePlayStore() }, + disable = { appPreferencesRepository.disablePlayStore() }, ), OPEN_SOURCE to StoreConfig( repository = cleanApkAppsRepository, - isEnabled = { appLoungePreference.isOpenSourceSelected() }, - enable = { appLoungePreference.enableOpenSource() }, - disable = { appLoungePreference.disableOpenSource() }, + isEnabled = { appPreferencesRepository.isOpenSourceSelected() }, + enable = { appPreferencesRepository.enableOpenSource() }, + disable = { appPreferencesRepository.disableOpenSource() }, ), PWA to StoreConfig( repository = cleanApkPwaRepository, - isEnabled = { appLoungePreference.isPWASelected() }, - enable = { appLoungePreference.enablePwa() }, - disable = { appLoungePreference.disablePwa() }, + isEnabled = { appPreferencesRepository.isPWASelected() }, + enable = { appPreferencesRepository.enablePwa() }, + disable = { appPreferencesRepository.disablePwa() }, ), ) diff --git a/app/src/main/java/foundation/e/apps/data/di/bindings/PreferenceBindingModule.kt b/app/src/main/java/foundation/e/apps/data/di/bindings/PreferenceBindingModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..240b766ac16736dc2554abd692ee10a7caa54586 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/di/bindings/PreferenceBindingModule.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021-2026 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.data.di.bindings + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.data.preference.PlayStoreAuthStore +import foundation.e.apps.data.preference.SessionDataStore +import foundation.e.apps.data.preference.updateinterval.UpdatePreferencesRepositoryImpl +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository +import foundation.e.apps.domain.preferences.updateinterval.UpdatePreferencesRepository +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface PreferenceBindingModule { + + @Binds + @Singleton + fun bindAppPreferencesRepository( + appLoungePreference: AppLoungePreference + ): AppPreferencesRepository + + @Binds + @Singleton + fun bindSessionRepository( + sessionDataStore: SessionDataStore + ): SessionRepository + + @Binds + @Singleton + fun bindUpdatePreferencesRepository( + updatePreferencesRepositoryImpl: UpdatePreferencesRepositoryImpl + ): UpdatePreferencesRepository + + @Binds + @Singleton + fun bindPlayStoreAuthStore( + sessionDataStore: SessionDataStore + ): PlayStoreAuthStore +} diff --git a/app/src/main/java/foundation/e/apps/data/di/system/PreferencesModule.kt b/app/src/main/java/foundation/e/apps/data/di/system/PreferencesModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..24af602164305b9cc286b618c3625bd277425073 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/di/system/PreferencesModule.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.di.system + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PreferencesModule { + + @Provides + @Singleton + fun provideDefaultSharedPreferences( + @ApplicationContext context: Context + ): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) +} diff --git a/app/src/main/java/foundation/e/apps/data/enums/User.kt b/app/src/main/java/foundation/e/apps/data/enums/User.kt deleted file mode 100644 index b5dc3df6726a615f30d82ddc529bf1c3b1cba335..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/apps/data/enums/User.kt +++ /dev/null @@ -1,7 +0,0 @@ -package foundation.e.apps.data.enums - -enum class User { - NO_GOOGLE, - ANONYMOUS, - GOOGLE -} diff --git a/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt b/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt index 57806cd6188cce1e02012ced39a4e7e304bbe870..b12a12ea81131fdc98c0cfbf819508621060be88 100644 --- a/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/data/event/AppEvent.kt @@ -22,8 +22,8 @@ package foundation.e.apps.data.event import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.User import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.domain.model.User import kotlinx.coroutines.CompletableDeferred sealed class AppEvent(val data: Any) { @@ -41,6 +41,7 @@ sealed class AppEvent(val data: Any) { type: String, val onClose: CompletableDeferred? = null ) : AppEvent(type) + class SuccessfulLogin(user: User) : AppEvent(user) class AutoRedirectHome(val message: Int? = null) : AppEvent(message ?: Unit) } diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt index 803c36e3b36cfe4e31ede10e5d45ec29b5cb38ed..2e354df1f1f3877e9a9eaba7ec69b2679c74baac 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerImpl.kt @@ -36,7 +36,7 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -64,7 +64,7 @@ class AppManagerImpl @Inject constructor( lateinit var contentRatingDao: ContentRatingDao @Inject - lateinit var appLoungePreference: AppLoungePreference + lateinit var appPreferencesRepository: AppPreferencesRepository private val mutex = Mutex() @@ -241,7 +241,7 @@ class AppManagerImpl @Inject constructor( .setTitle(requestTitle) .setDestinationUri(Uri.fromFile(packagePath)) - if (isUpdate && appLoungePreference.isOnlyUnmeteredNetworkEnabled()) { + if (isUpdate && appPreferencesRepository.isOnlyUnmeteredNetworkEnabled()) { // Set to true by default request.setAllowedOverMetered(false) } diff --git a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt index 54b80142990eb180e1fc3cf649fa193b3fa8842d..346b9a082fee9d62353de766067cbe82b983e42d 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppManagerWrapper.kt @@ -20,6 +20,7 @@ package foundation.e.apps.data.install import android.content.Context import androidx.lifecycle.LiveData +import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting import foundation.e.apps.data.Constants.MIN_VALID_RATING import foundation.e.apps.data.application.data.Application @@ -36,6 +37,7 @@ private const val PERCENTAGE_MULTIPLIER = 100 @Singleton @OpenForTesting class AppManagerWrapper @Inject constructor( + @ApplicationContext private val context: Context, private val appManager: AppManager, private val fDroidRepository: FDroidRepository ) { @@ -79,6 +81,7 @@ class AppManagerWrapper @Inject constructor( appInstall: AppInstall ) = existingAppInstall != null && InstallWorkManager.checkWorkIsAlreadyAvailable( + context, appInstall.id ) diff --git a/app/src/main/java/foundation/e/apps/data/install/FileManager.kt b/app/src/main/java/foundation/e/apps/data/install/FileManager.kt index a02c0fdb4e8a97c407e1dc8250f44dcbb9024d22..664e894918af480b7782d3b10f8dcd0be416b439 100644 --- a/app/src/main/java/foundation/e/apps/data/install/FileManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/FileManager.kt @@ -2,42 +2,27 @@ package foundation.e.apps.data.install import timber.log.Timber import java.io.File -import java.io.FileInputStream import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.InputStream -import java.io.OutputStream object FileManager { const val BUFFER_SIZE = 1024 fun moveFile(inputPath: String, inputFile: String, outputPath: String) { - var inputStream: InputStream? = null - var outputStream: OutputStream? = null try { - // create output directory if it doesn't exist val dir = File(outputPath) if (!dir.exists()) { dir.mkdirs() } - inputStream = FileInputStream(inputPath + inputFile) - outputStream = FileOutputStream(outputPath + inputFile) - val buffer = ByteArray(BUFFER_SIZE) - var read: Int - while (inputStream.read(buffer).also { read = it } != -1) { - outputStream.write(buffer, 0, read) - } - // write the output file - outputStream.flush() - // delete the original file - File(inputPath + inputFile).delete() + + val sourceFile = File(inputPath + inputFile) + val targetFile = File(outputPath + inputFile) + + sourceFile.copyTo(targetFile, overwrite = true, bufferSize = BUFFER_SIZE) + sourceFile.delete() } catch (e: FileNotFoundException) { Timber.e(e.stackTraceToString()) } catch (e: Exception) { Timber.e(e.stackTraceToString()) - } finally { - inputStream?.close() - outputStream?.close() } } } diff --git a/app/src/main/java/foundation/e/apps/data/install/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/data/install/pkg/PkgManagerBR.kt index 8c85381fd0ffa2ce8dbc90073a5efffdf3d6e778..f245590048af645784fa6e993e041285dc7710ee 100644 --- a/app/src/main/java/foundation/e/apps/data/install/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/data/install/pkg/PkgManagerBR.kt @@ -22,7 +22,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller -import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.di.qualifiers.IoCoroutineScope import foundation.e.apps.data.enums.Status import foundation.e.apps.data.faultyApps.FaultyAppRepository @@ -32,26 +31,17 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -@AndroidEntryPoint -open class PkgManagerBR : BroadcastReceiver() { +class PkgManagerBR @Inject constructor( + private val appManagerWrapper: AppManagerWrapper, + private val appLoungePackageManager: AppLoungePackageManager, + private val faultyAppRepository: FaultyAppRepository, + @IoCoroutineScope private val coroutineScope: CoroutineScope +) : BroadcastReceiver() { companion object { private const val DEFAULT_INSTALL_STATUS = -69 } - @Inject - lateinit var appManagerWrapper: AppManagerWrapper - - @Inject - lateinit var appLoungePackageManager: AppLoungePackageManager - - @Inject - lateinit var faultyAppRepository: FaultyAppRepository - - @Inject - @IoCoroutineScope - lateinit var coroutineScope: CoroutineScope - override fun onReceive(context: Context?, intent: Intent?) { val action = intent?.action if (context != null && action != null) { diff --git a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesBroadcastReceiver.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesBroadcastReceiver.kt index 5599eced76320dcdd228ac9802822fc5e61d42f0..738770b68722e159138ad16457219199f0d03f3f 100644 --- a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesBroadcastReceiver.kt +++ b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesBroadcastReceiver.kt @@ -22,7 +22,10 @@ import android.content.Context import android.content.Intent import androidx.work.ExistingPeriodicWorkPolicy import dagger.hilt.android.AndroidEntryPoint -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.data.di.qualifiers.IoCoroutineScope +import foundation.e.apps.domain.preferences.updateinterval.GetUpdateIntervalUseCase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -30,13 +33,29 @@ import javax.inject.Inject class UpdatesBroadcastReceiver : BroadcastReceiver() { @Inject - lateinit var appLoungePreference: AppLoungePreference + lateinit var getUpdateIntervalUseCase: GetUpdateIntervalUseCase + + @Inject + @IoCoroutineScope + lateinit var coroutineScope: CoroutineScope override fun onReceive(context: Context, intent: Intent) { Timber.d("onReceive: ${intent.action}") if (intent.action in listOf(Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED)) { - val interval = appLoungePreference.getUpdateInterval() - UpdatesWorkManager.enqueueWork(context, interval, ExistingPeriodicWorkPolicy.UPDATE) + // This receiver is manifest-registered, so field injection remains the correct pattern. + val pendingResult = goAsync() + coroutineScope.launch { + try { + val interval = getUpdateIntervalUseCase() + UpdatesWorkManager.enqueueWork( + context, + interval, + ExistingPeriodicWorkPolicy.UPDATE + ) + } finally { + pendingResult.finish() + } + } } } } diff --git a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt index 31d0e20d26a901ada9b5f77381e7e80b6578c471..f510a58f2e5eb578c77633452b4ae018741d61ef 100644 --- a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt +++ b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorker.kt @@ -7,37 +7,38 @@ import android.net.ConnectivityManager import android.net.NetworkCapabilities import androidx.annotation.VisibleForTesting import androidx.hilt.work.HiltWorker -import androidx.preference.PreferenceManager import androidx.work.CoroutineWorker import androidx.work.WorkInfo.State import androidx.work.WorkManager import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.User import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.data.login.repository.AuthenticatorRepository -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.updates.UpdatesManagerRepository +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import timber.log.Timber +@Suppress("LongParameterList") @HiltWorker class UpdatesWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val updatesManagerRepository: UpdatesManagerRepository, - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, + private val appPreferencesRepository: AppPreferencesRepository, private val authenticatorRepository: AuthenticatorRepository, private val appInstallProcessor: AppInstallProcessor, private val blockedAppRepository: BlockedAppRepository, @@ -71,7 +72,8 @@ class UpdatesWorker @AssistedInject constructor( } } - val systemAppsUpdateTask = systemAppsUpdatesRepository.fetchUpdatableSystemApps(forceRefresh = true) + val systemAppsUpdateTask = + systemAppsUpdatesRepository.fetchUpdatableSystemApps(forceRefresh = true) check(systemAppsUpdateTask.isSuccess()) { "failed to fetch system apps update!" } val enqueueInstall = checkForUpdates() check(enqueueInstall == ResultStatus.OK) { @@ -106,8 +108,8 @@ class UpdatesWorker @AssistedInject constructor( return false } - private fun getUser(): User { - return appLoungeDataStore.getUser() + private suspend fun getUser(): User { + return sessionRepository.awaitUser() } private suspend fun checkForUpdates(): ResultStatus { @@ -124,7 +126,8 @@ class UpdatesWorker @AssistedInject constructor( handleNotification(appsNeededToUpdate.size, isConnectedToUnMeteredNetwork) } - val jobsEnqueued = triggerUpdateProcessOnSettings(isConnectedToUnMeteredNetwork, appsNeededToUpdate) + val jobsEnqueued = + triggerUpdateProcessOnSettings(isConnectedToUnMeteredNetwork, appsNeededToUpdate) return if (jobsEnqueued.all { it.second }) ResultStatus.OK else ResultStatus.UNKNOWN } @@ -224,7 +227,8 @@ class UpdatesWorker @AssistedInject constructor( suspend fun startUpdateProcess(appsNeededToUpdate: List): List> { val response = mutableListOf>() val authData = authenticatorRepository.getValidatedAuthData() - val isNotLoggedIntoPersonalAccount = !authData.isValidData() || authData.data?.isAnonymous == true + val isNotLoggedIntoPersonalAccount = + !authData.isValidData() || authData.data?.isAnonymous == true for (fusedApp in appsNeededToUpdate) { val shouldSkip = (!fusedApp.isFree && isNotLoggedIntoPersonalAccount) if (shouldSkip.or(isStopped)) { // respect the stop signal as well @@ -238,27 +242,9 @@ class UpdatesWorker @AssistedInject constructor( } private fun loadSettings() { - val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext) - shouldShowNotification = - preferences.getBoolean( - applicationContext.getString( - R.string.updateNotify - ), - true - ) - automaticInstallEnabled = preferences.getBoolean( - applicationContext.getString( - R.string.auto_install_enabled - ), - true - ) - - onlyOnUnmeteredNetwork = preferences.getBoolean( - applicationContext.getString( - R.string.only_unmetered_network - ), - true - ) + shouldShowNotification = appPreferencesRepository.shouldShowUpdateNotification() + automaticInstallEnabled = appPreferencesRepository.isAutomaticInstallEnabled() + onlyOnUnmeteredNetwork = appPreferencesRepository.isOnlyUnmeteredNetworkEnabled() } /** diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt index 0e968c35b212aca9e5324e9fae8c596ee457997a..3b214d475b3005c203a91edcb78c3155046a6480 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt @@ -31,7 +31,6 @@ 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.Type -import foundation.e.apps.data.enums.User import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.AppInstallComponents @@ -41,13 +40,15 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.updates.UpdatesNotifier import foundation.e.apps.data.playstore.utils.GplayHttpRequestException -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.system.ParentalControlAuthenticator import foundation.e.apps.data.system.StorageComputer import foundation.e.apps.data.system.isNetworkAvailable import foundation.e.apps.data.utils.getFormattedString import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile @@ -56,12 +57,14 @@ import java.text.NumberFormat import java.util.Date import javax.inject.Inject +@Suppress("LongParameterList") class AppInstallProcessor @Inject constructor( @ApplicationContext private val context: Context, private val appInstallComponents: AppInstallComponents, private val applicationRepository: ApplicationRepository, private val validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase, - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, + private val playStoreAuthStore: PlayStoreAuthStore, private val storageNotificationManager: StorageNotificationManager, ) { @Inject @@ -130,10 +133,10 @@ class AppInstallProcessor @Inject constructor( isSystemApp: Boolean = false ): Boolean { return try { - val user = appLoungeDataStore.getUser() + val user = sessionRepository.awaitUser() if (!isSystemApp && (user == User.GOOGLE || user == User.ANONYMOUS)) { - val authData = appLoungeDataStore.getAuthData() - if (!appInstall.isFree && authData.isAnonymous) { + val authData = playStoreAuthStore.awaitAuthData() + if (!appInstall.isFree && authData?.isAnonymous == true) { EventBus.invokeEvent(AppEvent.ErrorMessageEvent(R.string.paid_app_anonymous_message)) } } @@ -141,7 +144,7 @@ class AppInstallProcessor @Inject constructor( if (!canEnqueue(appInstall)) return false appInstallComponents.appManagerWrapper.updateAwaiting(appInstall) - InstallWorkManager.enqueueWork(appInstall, isAnUpdate) + InstallWorkManager.enqueueWork(context, appInstall, isAnUpdate) true } catch (e: Exception) { Timber.e( @@ -383,8 +386,8 @@ class AppInstallProcessor @Inject constructor( return UpdatesDao.successfulUpdatedApps.isNotEmpty() && downloadListWithoutAnyIssue.isEmpty() } - private fun showNotificationOnUpdateEnded() { - val locale = appLoungeDataStore.getAuthData().locale + private suspend fun showNotificationOnUpdateEnded() { + val locale = playStoreAuthStore.awaitAuthData()?.locale ?: java.util.Locale.getDefault() val date = Date().getFormattedString(DATE_FORMAT, locale) val numberOfUpdatedApps = NumberFormat.getNumberInstance(locale).format(UpdatesDao.successfulUpdatedApps.size) diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt index e3a453b6e4c6acadca1461c10c0995e471791c81..50edbef592005462a2fd62a6f2125d05488c4c9b 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt @@ -1,6 +1,6 @@ package foundation.e.apps.data.install.workmanager -import android.app.Application +import android.content.Context import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder @@ -11,9 +11,8 @@ import java.lang.Exception object InstallWorkManager { const val INSTALL_WORK_NAME = "APP_LOUNGE_INSTALL_APP" - lateinit var context: Application - fun enqueueWork(appInstall: AppInstall, isUpdateWork: Boolean = false) { + fun enqueueWork(context: Context, appInstall: AppInstall, isUpdateWork: Boolean = false) { WorkManager.getInstance(context).enqueueUniqueWork( INSTALL_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, @@ -27,11 +26,11 @@ object InstallWorkManager { ) } - fun cancelWork(tag: String) { + fun cancelWork(context: Context, tag: String) { WorkManager.getInstance(context).cancelAllWorkByTag(tag) } - fun checkWorkIsAlreadyAvailable(tag: String): Boolean { + fun checkWorkIsAlreadyAvailable(context: Context, tag: String): Boolean { val works = WorkManager.getInstance(context).getWorkInfosByTag(tag) try { works.get().forEach { diff --git a/app/src/main/java/foundation/e/apps/data/login/cleanapk/CleanApkAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/cleanapk/CleanApkAuthenticator.kt index f3878c94da5ecd1559644eb4df0c47cb7706b3a3..00575fbf7b13a0a68ed8820509b2ef7d7a95ad00 100644 --- a/app/src/main/java/foundation/e/apps/data/login/cleanapk/CleanApkAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/cleanapk/CleanApkAuthenticator.kt @@ -18,14 +18,14 @@ package foundation.e.apps.data.login.cleanapk import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreAuthResult import foundation.e.apps.data.login.core.StoreAuthenticator import foundation.e.apps.data.login.core.StoreType -import foundation.e.apps.data.login.state.LoginState -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import javax.inject.Inject import javax.inject.Singleton @@ -35,22 +35,22 @@ import javax.inject.Singleton */ @Singleton class CleanApkAuthenticator @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, - private val appLoungePreference: AppLoungePreference, + private val sessionRepository: SessionRepository, + private val appPreferencesRepository: AppPreferencesRepository, ) : StoreAuthenticator { override val storeType: StoreType = StoreType.CLEAN_APK private val user: User - get() = appLoungeDataStore.getUser() + get() = sessionRepository.getUser() override fun isStoreActive(): Boolean { - if (appLoungeDataStore.getLoginState() == LoginState.UNAVAILABLE) { + if (sessionRepository.getLoginState() == LoginState.UNAVAILABLE) { /* * UNAVAILABLE login state means first login is not completed. */ return false } - return appLoungePreference.isOpenSourceSelected() || appLoungePreference.isPWASelected() + return appPreferencesRepository.isOpenSourceSelected() || appPreferencesRepository.isPWASelected() } override suspend fun login(): StoreAuthResult { diff --git a/app/src/main/java/foundation/e/apps/data/login/core/AuthFailureFactory.kt b/app/src/main/java/foundation/e/apps/data/login/core/AuthFailureFactory.kt index c89888577c0c12e1f45120545c7ce6a440cf5901..70eea68d7efec4a9e8351675fb3a4b2b5c2bb8e9 100644 --- a/app/src/main/java/foundation/e/apps/data/login/core/AuthFailureFactory.kt +++ b/app/src/main/java/foundation/e/apps/data/login/core/AuthFailureFactory.kt @@ -2,14 +2,15 @@ package foundation.e.apps.data.login.core import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.exceptions.CleanApkException import foundation.e.apps.data.login.exceptions.GPlayValidationException +import foundation.e.apps.domain.model.User import java.net.HttpURLConnection object AuthFailureFactory { fun createGplayValidationFailure(user: User, otherPayload: Any?): AuthObject { - val message = "Validating AuthData failed.\nNetwork code: ${HttpURLConnection.HTTP_UNAUTHORIZED}" + val message = + "Validating AuthData failed.\nNetwork code: ${HttpURLConnection.HTTP_UNAUTHORIZED}" return AuthObject.GPlayAuth( ResultSupreme.Error( message = message, diff --git a/app/src/main/java/foundation/e/apps/data/login/core/AuthObject.kt b/app/src/main/java/foundation/e/apps/data/login/core/AuthObject.kt index 89917bf44f23851ef6b2d0213d55d33b7c58fbba..3c89fa0d81d03564ef0abe8ec86e4a1bbdcd6ba9 100644 --- a/app/src/main/java/foundation/e/apps/data/login/core/AuthObject.kt +++ b/app/src/main/java/foundation/e/apps/data/login/core/AuthObject.kt @@ -19,7 +19,7 @@ package foundation.e.apps.data.login.core import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User /** * Auth objects define which sources data is to be loaded from, for each source, also provides diff --git a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayLoginException.kt b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayLoginException.kt index d6a64a7bb3d20c52243c9f30434122b431642716..355b9bd81ed33d1fe2a3abd3f6b5a6433ba43de8 100644 --- a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayLoginException.kt +++ b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayLoginException.kt @@ -17,7 +17,7 @@ package foundation.e.apps.data.login.exceptions -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User /** * Parent class for all GPlay login related errors. diff --git a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayValidationException.kt b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayValidationException.kt index 5b4322a4cc955487b91ee65578af872f7be67876..039a9959f748e16dad42f02e5a9594de01639e50 100644 --- a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayValidationException.kt +++ b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayValidationException.kt @@ -17,7 +17,7 @@ package foundation.e.apps.data.login.exceptions -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User /** * This exception is specifically used when a GPlay auth data could not be validated. diff --git a/app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt b/app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt index 252a97489a3ec1dd50e36f467cb2164c72b2e692..85bccce7039370ad954976113548aadd720d78f2 100644 --- a/app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt +++ b/app/src/main/java/foundation/e/apps/data/login/microg/MicrogLoginManager.kt @@ -31,8 +31,8 @@ import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject @@ -48,7 +48,7 @@ class MicrogLoginManager @Inject constructor( @ApplicationContext val context: Context, private val accountManager: AccountManager, private val oauthAuthDataBuilder: OauthAuthDataBuilder, - private val appLoungeDataStore: AppLoungeDataStore + private val sessionRepository: SessionRepository ) : LoginManager { sealed interface FetchResult { data class Success(val microgAccount: MicrogAccount) : FetchResult @@ -61,7 +61,7 @@ class MicrogLoginManager @Inject constructor( } override suspend fun login(): AuthData? { - val oldToken = appLoungeDataStore.oauthToken.getSync() + val oldToken = sessionRepository.oauthToken.getSync() val shouldRefresh = hasMicrogAccount() && oldToken.startsWith(MICROG_TOKEN_PREFIX) val oauthToken = if (shouldRefresh) { fetchRefreshedToken(oldToken) @@ -133,17 +133,17 @@ class MicrogLoginManager @Inject constructor( } private suspend fun fetchRefreshedToken(oldToken: String): String { - val accountName = appLoungeDataStore.emailData.getSync().ifBlank { "" } + val accountName = sessionRepository.emailData.getSync().ifBlank { "" } invalidateAuthToken(accountName, oldToken) val result = fetchMicrogAccount(accountName) return when (result) { is FetchResult.Success -> { - appLoungeDataStore.saveGoogleLogin( + sessionRepository.saveGoogleLogin( result.microgAccount.account.name, result.microgAccount.oauthToken ) - appLoungeDataStore.saveAasToken("") + sessionRepository.saveAasToken("") result.microgAccount.oauthToken } is FetchResult.RequiresUserAction -> error(MICROG_TOKEN_REFRESH_FAILURE) diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataCache.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataCache.kt index 76dcb7fc49db3788a1a07cfa51a34f93cb95030e..1fa97c91d54929115ac83952930c42c640506d95 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataCache.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/AuthDataCache.kt @@ -19,19 +19,19 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import timber.log.Timber import javax.inject.Inject class AuthDataCache @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, private val json: Json, ) { fun getSavedAuthData(): AuthData? { - val authJson = appLoungeDataStore.authData.getSync() + val authJson = sessionRepository.authData.getSync() return if (authJson.isBlank()) { null } else { @@ -45,7 +45,7 @@ class AuthDataCache @Inject constructor( } suspend fun saveAuthData(authData: AuthData?) { - appLoungeDataStore.saveAuthData(authData) + sessionRepository.saveAuthData(authData) } fun formatAuthData(authData: AuthData): AuthData { diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt index d71adcae8e0eb13ecfc4b95b01a11d047f20bcbf..112ea83da927b743d450354a2f922766014cac0c 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/GoogleLoginManager.kt @@ -20,8 +20,8 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.helpers.AuthHelper import foundation.e.apps.data.login.api.LoginManager -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Properties @@ -29,7 +29,7 @@ import javax.inject.Inject class GoogleLoginManager @Inject constructor( private val nativeDeviceProperty: Properties, - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, private val oauthAuthDataBuilder: OauthAuthDataBuilder ) : LoginManager { @@ -39,9 +39,9 @@ class GoogleLoginManager @Inject constructor( * @return authData: authentication data */ override suspend fun login(): AuthData? { - val email = appLoungeDataStore.emailData.getSync() - val aasToken = appLoungeDataStore.aasToken.getSync() - val oauthToken = appLoungeDataStore.oauthToken.getSync() + val email = sessionRepository.emailData.getSync() + val aasToken = sessionRepository.aasToken.getSync() + val oauthToken = sessionRepository.oauthToken.getSync() var authData: AuthData? withContext(Dispatchers.IO) { diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilder.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilder.kt index d1c7cb1212c7476362982cd60b6b3f5c80541879..eb2a49a1e89400382f8e5dc2d7b6ab552e3575c3 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilder.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilder.kt @@ -19,19 +19,19 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.helpers.AuthHelper -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Properties import javax.inject.Inject class OauthAuthDataBuilder @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, private val nativeDeviceProperty: Properties ) { suspend fun build(oauthToken: String): AuthData? { - val email = appLoungeDataStore.emailData.getSync() + val email = sessionRepository.emailData.getSync() return withContext(Dispatchers.IO) { AuthHelper.build( email = email, diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverter.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverter.kt index c511d919b9cb6399873f86adf3aa46369f498ec5..7e0b08dc5010b39d2de4845496baec82e188da29 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverter.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverter.kt @@ -19,11 +19,11 @@ package foundation.e.apps.data.login.playstore import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.playstore.utils.AC2DMTask import foundation.e.apps.data.playstore.utils.AC2DMUtil +import foundation.e.apps.domain.model.User import javax.inject.Inject class OauthToAasTokenConverter @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt index 4ae415f142a7ae198d2034c698dfeb214c5450f3..6fd105ee56add976d2eb385f56410f9e2d0f880e 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt @@ -23,17 +23,18 @@ import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreAuthResult import foundation.e.apps.data.login.core.StoreAuthenticator import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.microg.MicrogLoginManager -import foundation.e.apps.data.login.state.LoginState -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.preference.getSync import foundation.e.apps.data.retryWithBackoff +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -48,8 +49,8 @@ import javax.inject.Singleton @Suppress("LongParameterList") class PlayStoreAuthenticator @Inject constructor( @ApplicationContext private val context: Context, - private val appLoungeDataStore: AppLoungeDataStore, - private val appLoungePreference: AppLoungePreference, + private val sessionRepository: SessionRepository, + private val appPreferencesRepository: AppPreferencesRepository, private val authDataCache: AuthDataCache, private val googleLoginManager: GoogleLoginManager, private val microgLoginManager: MicrogLoginManager, @@ -74,20 +75,20 @@ class PlayStoreAuthenticator @Inject constructor( } override fun isStoreActive(): Boolean { - if (appLoungeDataStore.getLoginState() == LoginState.UNAVAILABLE) { + if (sessionRepository.getLoginState() == LoginState.UNAVAILABLE) { /* * UNAVAILABLE login state means first login is not completed. */ return false } - return appLoungePreference.isPlayStoreSelected() + return appPreferencesRepository.isPlayStoreSelected() } /** * Main entry point to get GPlay auth data. */ override suspend fun login(): StoreAuthResult { - val user = appLoungeDataStore.getUser() + val user = sessionRepository.getUser() val validSavedAuth = authDataCache.getSavedAuthData() val authDataResult = if (validSavedAuth == null) { retryWithBackoff { loginForUserType(user) } @@ -97,7 +98,11 @@ class PlayStoreAuthenticator @Inject constructor( val authResult = when { validSavedAuth != null -> buildAuthResult(user, validSavedAuth, null) - authDataResult != null && !authDataResult.isSuccess() -> buildErrorAuthResult(user, authDataResult) + authDataResult != null && !authDataResult.isSuccess() -> buildErrorAuthResult( + user, + authDataResult + ) + else -> buildAuthResult(user, authDataResult?.data, null) } return authResult @@ -130,7 +135,7 @@ class PlayStoreAuthenticator @Inject constructor( } private suspend fun loginWithGoogleBackend(): ResultSupreme { - val aasToken = appLoungeDataStore.aasToken.getSync() + val aasToken = sessionRepository.aasToken.getSync() return if (aasToken.isNotBlank()) { loginWithAasToken() } else { @@ -139,8 +144,8 @@ class PlayStoreAuthenticator @Inject constructor( } private suspend fun loginWithAasTokenConversion(): ResultSupreme { - val email = appLoungeDataStore.emailData.getSync() - val oauthToken = appLoungeDataStore.oauthToken.getSync() + val email = sessionRepository.emailData.getSync() + val oauthToken = sessionRepository.oauthToken.getSync() val aasTokenResponse = aasTokenConverter.convert(email, oauthToken) return if (aasTokenResponse.isSuccess()) { loginWithAasTokenFromConversion(aasTokenResponse.data.orEmpty()) @@ -164,7 +169,7 @@ class PlayStoreAuthenticator @Inject constructor( return ResultSupreme.Error("Fetched AAS Token is blank") } - appLoungeDataStore.saveAasToken(aasToken) + sessionRepository.saveAasToken(aasToken) return loginWithAasToken() } @@ -190,14 +195,16 @@ class PlayStoreAuthenticator @Inject constructor( } private fun resolveAuthSource(): PlayStoreAuthSource { - val authSource = appLoungeDataStore.playStoreAuthSource.getSync() + val authSource = sessionRepository.playStoreAuthSource.getSync() val storedAuthSource = PlayStoreAuthSource.entries .firstOrNull { it.name == authSource } return when { storedAuthSource != null -> storedAuthSource - appLoungeDataStore.oauthToken.getSync().startsWith(MicrogLoginManager.MICROG_TOKEN_PREFIX) -> + sessionRepository.oauthToken.getSync() + .startsWith(MicrogLoginManager.MICROG_TOKEN_PREFIX) -> PlayStoreAuthSource.MICROG + microgLoginManager.hasMicrogAccount() -> PlayStoreAuthSource.MICROG else -> PlayStoreAuthSource.GOOGLE } diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreSession.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreSession.kt index a4813eeccb0561a1f6eb01d14f10d2b4eb020ce2..cd4a5e3908e8f195e0056a0055e0e61440d5b657 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreSession.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreSession.kt @@ -20,12 +20,12 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.exceptions.GPlayLoginException +import foundation.e.apps.domain.model.User import java.util.Locale class PlayStoreSession( diff --git a/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt b/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt index a514d96519e7ac4657497d0a23f64eca7891c856..9e9b51e5b79ac746bb1e61db3e0aacffc2f44154 100644 --- a/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt @@ -23,7 +23,7 @@ import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreAuthenticator import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.exceptions.GPlayLoginException -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.domain.preferences.SessionRepository import javax.inject.Inject import javax.inject.Singleton @@ -31,18 +31,18 @@ import javax.inject.Singleton @Singleton class AuthenticatorRepository @Inject constructor( private val authenticators: List, - private val appLoungeDataStore: AppLoungeDataStore + private val sessionRepository: SessionRepository ) { fun getGPlayAuthOrThrow(): AuthData { return kotlin.runCatching { - appLoungeDataStore.getAuthData() + sessionRepository.getAuthData() }.getOrElse { - throw GPlayLoginException(false, "AuthData is not available", appLoungeDataStore.getUser()) + throw GPlayLoginException(false, "AuthData is not available", sessionRepository.getUser()) } } suspend fun setGPlayAuth(auth: AuthData) { - appLoungeDataStore.saveAuthData(auth) + sessionRepository.saveAuthData(auth) } suspend fun fetchAuthObjects(authTypes: List = listOf()): List { @@ -56,14 +56,14 @@ class AuthenticatorRepository @Inject constructor( val authResult = authenticator.login() authObjectsLocal.add(authResult.authObject) - authResult.authDataToPersist?.let { appLoungeDataStore.saveAuthData(it) } + authResult.authDataToPersist?.let { sessionRepository.saveAuthData(it) } } return authObjectsLocal } suspend fun saveAasToken(aasToken: String) { - appLoungeDataStore.saveAasToken(aasToken) + sessionRepository.saveAasToken(aasToken) } suspend fun getValidatedAuthData(): ResultSupreme { @@ -71,7 +71,7 @@ class AuthenticatorRepository @Inject constructor( ?: return ResultSupreme.Error("Play Store authenticator not available") authenticator.logout() val authResult = authenticator.login() - authResult.authDataToPersist?.let { appLoungeDataStore.saveAuthData(it) } + authResult.authDataToPersist?.let { sessionRepository.saveAuthData(it) } val authObject = authResult.authObject as? AuthObject.GPlayAuth return authObject?.result ?: ResultSupreme.Error("Play Store auth unavailable") } diff --git a/app/src/main/java/foundation/e/apps/data/login/state/LoginCoordinator.kt b/app/src/main/java/foundation/e/apps/data/login/state/LoginCoordinator.kt index 87d34051252545ee159ad4d84689e665e4b436b8..29477fe6b54b34983f452fb78e59f56aca67103a 100644 --- a/app/src/main/java/foundation/e/apps/data/login/state/LoginCoordinator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/state/LoginCoordinator.kt @@ -1,36 +1,36 @@ package foundation.e.apps.data.login.state -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class LoginCoordinator @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, private val loginPreferencesManager: LoginPreferencesManager, ) { suspend fun saveUserType(user: User) { - appLoungeDataStore.saveUserType(user) + sessionRepository.saveUserType(user) } suspend fun saveGoogleLogin(email: String, oauth: String) { - appLoungeDataStore.saveGoogleLogin(email, oauth) + sessionRepository.saveGoogleLogin(email, oauth) } suspend fun savePlayStoreAuthSource(authSource: PlayStoreAuthSource) { - appLoungeDataStore.savePlayStoreAuthSource(authSource) + sessionRepository.savePlayStoreAuthSource(authSource) } suspend fun setNoGoogleMode() { loginPreferencesManager.applyNoGoogleMode() - appLoungeDataStore.saveUserType(User.NO_GOOGLE) + sessionRepository.saveUserType(User.NO_GOOGLE) } suspend fun logout() { - appLoungeDataStore.destroyCredentials() - appLoungeDataStore.saveUserType(null) + sessionRepository.destroyCredentials() + sessionRepository.saveUserType(null) loginPreferencesManager.resetAfterLogout() } } diff --git a/app/src/main/java/foundation/e/apps/data/login/state/LoginPreferencesManager.kt b/app/src/main/java/foundation/e/apps/data/login/state/LoginPreferencesManager.kt index 8b54d60c81bbe8b8293f26f7ec900cfd481249f7..9c9fe999a69ed0703664212a0bf48acbc6f2a6c9 100644 --- a/app/src/main/java/foundation/e/apps/data/login/state/LoginPreferencesManager.kt +++ b/app/src/main/java/foundation/e/apps/data/login/state/LoginPreferencesManager.kt @@ -1,15 +1,15 @@ package foundation.e.apps.data.login.state -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class LoginPreferencesManager @Inject constructor( - private val appLoungePreference: AppLoungePreference + private val appPreferencesRepository: AppPreferencesRepository, ) { fun applyNoGoogleMode() { - appLoungePreference.run { + appPreferencesRepository.run { disablePlayStore() enableOpenSource() enablePwa() @@ -17,7 +17,7 @@ class LoginPreferencesManager @Inject constructor( } fun resetAfterLogout() { - appLoungePreference.run { + appPreferencesRepository.run { enableOpenSource() enablePwa() enablePlayStore() diff --git a/app/src/main/java/foundation/e/apps/data/login/state/LoginState.kt b/app/src/main/java/foundation/e/apps/data/login/state/LoginState.kt deleted file mode 100644 index 661d3569fb7fe02bb2834564f9d8b7cb5136eba7..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/apps/data/login/state/LoginState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package foundation.e.apps.data.login.state - -enum class LoginState { - AVAILABLE, - UNAVAILABLE -} diff --git a/app/src/main/java/foundation/e/apps/data/microg/AuthDataProvider.kt b/app/src/main/java/foundation/e/apps/data/microg/AuthDataProvider.kt index b55665eda5612726f2134980db234e2dad3768f9..3631ddd7a8ae8e7208b698e6eb605793b25a1145 100644 --- a/app/src/main/java/foundation/e/apps/data/microg/AuthDataProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/microg/AuthDataProvider.kt @@ -29,7 +29,8 @@ import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import foundation.e.apps.authdata.AuthDataContract -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.PlayStoreAuthStore +import kotlinx.coroutines.runBlocking /** * Content provider dedicated to share the Google auth data with @@ -40,10 +41,10 @@ class AuthDataProvider : ContentProvider() { @EntryPoint @InstallIn(SingletonComponent::class) interface DataStoreProvider { - fun provideAppLoungeDataStore(): AppLoungeDataStore + fun providePlayStoreAuthStore(): PlayStoreAuthStore } - private lateinit var appLoungeDataStore: AppLoungeDataStore + private lateinit var playStoreAuthStore: PlayStoreAuthStore override fun onCreate(): Boolean { val context = context ?: return false @@ -53,7 +54,7 @@ class AuthDataProvider : ContentProvider() { DataStoreProvider::class.java ) - appLoungeDataStore = dataStoreEntryPoint.provideAppLoungeDataStore() + playStoreAuthStore = dataStoreEntryPoint.providePlayStoreAuthStore() return true } @@ -85,7 +86,8 @@ class AuthDataProvider : ContentProvider() { ) val row = cursor.newRow() - appLoungeDataStore.getAuthData().let { + val authData = runBlocking { playStoreAuthStore.awaitAuthData() } + authData?.let { row.add(AuthDataContract.EMAIL_KEY, it.email) row.add(AuthDataContract.AUTH_TOKEN_KEY, it.authToken) row.add(AuthDataContract.GSF_ID_KEY, it.gsfId) 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 deleted file mode 100644 index 62a1cb9b0a5b064683fba965d3d5e272a432c3de..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2021-2025 e Foundation - * - * 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.data.preference - -import android.content.Context -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.aurora.gplayapi.data.models.AuthData -import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource -import foundation.e.apps.data.login.state.LoginState -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Difference between [OAUTHTOKEN] and [AASTOKEN]: - * - * These two are used only for Google login, not for Anonymous login. - * OAuthToken is obtained from the Google Login web page, from the cookies. - * This OAuthToken is then used by AC2DMTask in GPlayAPIImpl class - * to generate AasToken. - * - * To get Google Play Store data, we need to create an AuthData instance. - * For Google user, this can only be done using AasToken, not OAuthToken. - * - * Very important: AasToken can be generated only ONCE from one OAuthToken. - * We cannot get AasToken again from the same OAuthToken. Thus it is - * important to safely store the AasToken to regenerate AuthData if needed. - * If AasToken is not stored, user has to logout and login again. - */ - -@Singleton -class AppLoungeDataStore @Inject constructor( - @ApplicationContext - private val context: Context, - private val json: Json, -) { - - companion object { - private const val preferenceDataStoreName = "Settings" - val Context.dataStore by preferencesDataStore(preferenceDataStoreName) - } - - private val AUTHDATA = stringPreferencesKey("authData") - private val EMAIL = stringPreferencesKey("email") - private val OAUTHTOKEN = stringPreferencesKey("oauthtoken") - private val AASTOKEN = stringPreferencesKey("aasToken") - private val USERTYPE = stringPreferencesKey("userType") - private val PLAY_STORE_AUTH_SOURCE = stringPreferencesKey("playStoreAuthSource") - private val TOCSTATUS = booleanPreferencesKey("tocStatus") - private val TOSVERSION = stringPreferencesKey("tosversion") - - val authData = context.dataStore.data.map { it[AUTHDATA] ?: "" } - val emailData = context.dataStore.data.map { it[EMAIL] ?: "" } - val oauthToken = context.dataStore.data.map { it[OAUTHTOKEN] ?: "" } - val aasToken = context.dataStore.data.map { it[AASTOKEN] ?: "" } - val userType = context.dataStore.data.map { it[USERTYPE] ?: "" } - val playStoreAuthSource = context.dataStore.data.map { it[PLAY_STORE_AUTH_SOURCE] ?: "" } - val tocStatus = context.dataStore.data.map { it[TOCSTATUS] ?: false } - val tosVersion = context.dataStore.data.map { it[TOSVERSION] ?: "" } - - /** - * Allows to save gplay API token data into datastore - */ - suspend fun saveAuthData(authData: AuthData?) { - context.dataStore.edit { - if (authData == null) { - it.remove(AUTHDATA) - } else { - it[AUTHDATA] = json.encodeToString(authData) - } - } - } - - fun getAuthData(): AuthData { - val authData = authData.getSync() - return if (authData.isEmpty()) { - AuthData("", "") - } else { - json.decodeFromString(authData) - } - } - - /** - * Destroy auth credentials if they are no longer valid. - * - * Modification for issue: https://gitlab.e.foundation/e/backlog/-/issues/5168 - * Previously this method would also remove [USERTYPE]. - * To clear this value, call [saveUserType] with null. - */ - suspend fun destroyCredentials() { - context.dataStore.edit { - it.remove(AUTHDATA) - it.remove(EMAIL) - it.remove(OAUTHTOKEN) - it.remove(AASTOKEN) - it.remove(PLAY_STORE_AUTH_SOURCE) - } - } - - /** - * TOC status - */ - suspend fun saveTOCStatus(status: Boolean, tosVersion: String) { - context.dataStore.edit { - it[TOCSTATUS] = status - it[TOSVERSION] = tosVersion - } - } - - /** - * User auth type - */ - suspend fun saveUserType(user: User?) { - context.dataStore.edit { - if (user == null) { - it.remove(USERTYPE) - } else { - it[USERTYPE] = user.name - } - } - } - - fun getUser(): User { - return runBlocking { - val type = userType.first() - User.values().firstOrNull { it.name == type } ?: User.NO_GOOGLE - } - } - - fun getLoginState(): LoginState { - return runBlocking { - val type = userType.first() - if (User.values().firstOrNull { it.name == type } == null) { - LoginState.UNAVAILABLE - } else { - LoginState.AVAILABLE - } - } - } - - suspend fun saveAasToken(aasToken: String) { - context.dataStore.edit { - it[AASTOKEN] = aasToken - } - } - - suspend fun saveGoogleLogin(email: String, token: String) { - context.dataStore.edit { - it[EMAIL] = email - it[OAUTHTOKEN] = token - } - } - - suspend fun savePlayStoreAuthSource(authSource: PlayStoreAuthSource?) { - context.dataStore.edit { - if (authSource == null) { - it.remove(PLAY_STORE_AUTH_SOURCE) - } else { - it[PLAY_STORE_AUTH_SOURCE] = authSource.name - } - } - } -} - -fun Flow.getSync(): T { - return runBlocking { - this@getSync.first() - } -} 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 deleted file mode 100644 index 98a50ef647b1235851bf0337e06840451ff4c3aa..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2025 e Foundation - * Copyright (C) 2021-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.data.preference - -import android.content.Context -import androidx.core.content.edit -import androidx.preference.PreferenceManager -import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.OpenForTesting -import foundation.e.apps.R -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA -import foundation.e.apps.data.enums.User -import javax.inject.Inject -import javax.inject.Singleton - -@Suppress("TooManyFunctions") -@Singleton -@OpenForTesting -class AppLoungePreference @Inject constructor( - @ApplicationContext private val context: Context, - private val appLoungeDataStore: AppLoungeDataStore -) { - - private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(context) - - fun preferredApplicationType(): String { - val showFOSSApplications = preferenceManager.getBoolean(PREFERENCE_SHOW_FOSS, false) - val showPWAApplications = preferenceManager.getBoolean(PREFERENCE_SHOW_PWA, false) - - return when { - showFOSSApplications -> "open" - showPWAApplications -> "pwa" - else -> "any" - } - } - - fun isOpenSourceSelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_FOSS, true) - fun isPWASelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_PWA, true) - fun isPlayStoreSelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_GPLAY, true) - - fun disablePlayStore() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_GPLAY, false) } - fun disableOpenSource() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_FOSS, false) } - fun disablePwa() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_PWA, false) } - - fun enablePlayStore() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_GPLAY, true) } - fun enableOpenSource() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_FOSS, true) } - fun enablePwa() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_PWA, true) } - - fun getUpdateInterval(): Long { - val currentUser = appLoungeDataStore.getUser() - return when (currentUser) { - User.ANONYMOUS -> preferenceManager.getString( - context.getString(R.string.update_check_intervals_anonymous), - context.getString(R.string.preference_update_interval_default_anonymous) - )!!.toLong() - - else -> preferenceManager.getString( - context.getString(R.string.update_check_intervals), - context.getString(R.string.preference_update_interval_default) - )!!.toLong() - } - } - - fun shouldUpdateAppsFromOtherStores() = preferenceManager.getBoolean( - context.getString(R.string.update_apps_from_other_stores), - true - ) - - fun migrateAnonymousUserUpdateInterval() { - val updateIntervals = context.resources.getStringArray(R.array.update_interval_values) - val daily = updateIntervals[0] // 24 - val weekly = updateIntervals[1] // 168 - val monthly = updateIntervals[2] // 720 - val migrationCompleted = preferenceManager.getBoolean( - context.getString(R.string.anonymous_update_migration_completed), - false - ) - - if (migrationCompleted) return - - if (appLoungeDataStore.getUser() == User.ANONYMOUS) { - val currentInterval = preferenceManager.getString( - context.getString(R.string.update_check_intervals), - null - ) - currentInterval?.let { interval -> - val newVal = when (interval) { - daily -> weekly - weekly, monthly -> interval - else -> context.getString(R.string.preference_update_interval_default_anonymous) - } - preferenceManager.edit { - putString( - context.getString(R.string.update_check_intervals_anonymous), - newVal - ) - } - } - } - - preferenceManager.edit { - putBoolean(context.getString(R.string.anonymous_update_migration_completed), true) - } - } - - fun isOnlyUnmeteredNetworkEnabled(): Boolean { - return preferenceManager.getBoolean( - context.getString(R.string.only_unmetered_network), - true - ) - } -} diff --git a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt index fdfc8991668ffdbcd1b3f033e2c942bed61154f6..ae8458086370db65e25d5ea475d8c0c8da3fc71c 100644 --- a/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/provider/AgeRatingProvider.kt @@ -53,10 +53,10 @@ import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ContentRatingEntity import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.system.isNetworkAvailable import foundation.e.apps.domain.ValidateAppAgeLimitUseCase import foundation.e.apps.domain.model.ContentRatingValidity +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -73,7 +73,7 @@ class AgeRatingProvider : ContentProvider() { fun provideGPlayContentRatingsRepository(): GPlayContentRatingRepository fun provideFDroidAntiFeatureRepository(): FDroidAntiFeatureRepository fun provideValidateAppAgeLimitUseCase(): ValidateAppAgeLimitUseCase - fun provideAppLoungeDataStore(): AppLoungeDataStore + fun provideSessionRepository(): SessionRepository fun provideNotificationManager(): NotificationManager fun provideContentRatingDao(): ContentRatingDao fun provideBlockedAppRepository(): BlockedAppRepository @@ -90,7 +90,7 @@ class AgeRatingProvider : ContentProvider() { private lateinit var gPlayContentRatingRepository: GPlayContentRatingRepository private lateinit var fDroidAntiFeatureRepository: FDroidAntiFeatureRepository private lateinit var validateAppAgeLimitUseCase: ValidateAppAgeLimitUseCase - private lateinit var appLoungeDataStore: AppLoungeDataStore + private lateinit var sessionRepository: SessionRepository private lateinit var notificationManager: NotificationManager private lateinit var contentRatingDao: ContentRatingDao private lateinit var blockedAppRepository: BlockedAppRepository @@ -124,7 +124,7 @@ class AgeRatingProvider : ContentProvider() { private fun getLoginType(): Cursor { val cursor = MatrixCursor(arrayOf(COLUMN_LOGIN_TYPE)) - cursor.addRow(arrayOf(appLoungeDataStore.getUser())) + cursor.addRow(arrayOf(sessionRepository.getUser())) return cursor } @@ -212,7 +212,7 @@ class AgeRatingProvider : ContentProvider() { * if user has logged in with Google or Anonymous mode. */ private suspend fun initAuthData() { - val authData = appLoungeDataStore.getAuthData() + val authData = sessionRepository.getAuthData() if (authData.email.isNotBlank() && authData.authToken.isNotBlank()) { authenticatorRepository.setGPlayAuth(authData) } @@ -320,7 +320,7 @@ class AgeRatingProvider : ContentProvider() { gPlayContentRatingRepository = hiltEntryPoint.provideGPlayContentRatingsRepository() fDroidAntiFeatureRepository = hiltEntryPoint.provideFDroidAntiFeatureRepository() validateAppAgeLimitUseCase = hiltEntryPoint.provideValidateAppAgeLimitUseCase() - appLoungeDataStore = hiltEntryPoint.provideAppLoungeDataStore() + sessionRepository = hiltEntryPoint.provideSessionRepository() notificationManager = hiltEntryPoint.provideNotificationManager() contentRatingDao = hiltEntryPoint.provideContentRatingDao() blockedAppRepository = hiltEntryPoint.provideBlockedAppRepository() diff --git a/app/src/main/java/foundation/e/apps/data/receivers/DumpAuthData.kt b/app/src/main/java/foundation/e/apps/data/receivers/DumpAuthData.kt index 353029a4e173ab739594755e4fc2b56fc5b3d193..c1d2b839ccc3e202b645fc1fe86f51bb487ed187 100644 --- a/app/src/main/java/foundation/e/apps/data/receivers/DumpAuthData.kt +++ b/app/src/main/java/foundation/e/apps/data/receivers/DumpAuthData.kt @@ -22,12 +22,14 @@ package foundation.e.apps.data.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.aurora.gplayapi.data.models.AuthData +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent import foundation.e.apps.data.Constants.ACTION_AUTHDATA_DUMP import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.getSync -import kotlinx.serialization.json.Json +import foundation.e.apps.data.preference.PlayStoreAuthStore +import kotlinx.coroutines.runBlocking import org.json.JSONObject import timber.log.Timber @@ -39,6 +41,12 @@ import timber.log.Timber * adb shell am broadcast -a foundation.e.apps.action.DUMP_GACCOUNT_INFO --receiver-include-background */ class DumpAuthData : BroadcastReceiver() { + @EntryPoint + @InstallIn(SingletonComponent::class) + interface SessionRepositoryProvider { + fun providePlayStoreAuthStore(): PlayStoreAuthStore + } + override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action != ACTION_AUTHDATA_DUMP || context == null) { return @@ -49,11 +57,13 @@ class DumpAuthData : BroadcastReceiver() { } private fun getAuthDataDump(context: Context): String { - val json = Json - // TODO: replace with context.configuration - val authData = AppLoungeDataStore(context, json).authData.getSync().let { - json.decodeFromString(it) - } + val playStoreAuthStore = EntryPointAccessors.fromApplication( + context.applicationContext, + SessionRepositoryProvider::class.java + ).providePlayStoreAuthStore() + + val authData = runBlocking { playStoreAuthStore.awaitAuthData() } ?: return "{}" + val filteredData = JSONObject().apply { put("email", authData.email) put("authToken", authData.authToken) diff --git a/app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt b/app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt index 17fe245b405232b3a77f5a60dcaca30a54cda77f..fbd86faba5a0338526679ce3b1a59fd4824d0b88 100644 --- a/app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/updates/UpdatesManagerImpl.kt @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - package foundation.e.apps.data.updates import android.content.Context @@ -34,18 +33,19 @@ import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.install.pkg.AppLoungePackageManager -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") class UpdatesManagerImpl @Inject constructor( @ApplicationContext private val context: Context, private val appLoungePackageManager: AppLoungePackageManager, private val applicationRepository: ApplicationRepository, private val faultyAppRepository: FaultyAppRepository, - private val appLoungePreference: AppLoungePreference, + private val appPreferencesRepository: AppPreferencesRepository, private val fDroidRepository: FDroidRepository, private val blockedAppRepository: BlockedAppRepository, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, @@ -66,7 +66,7 @@ class UpdatesManagerImpl @Inject constructor( val openSourceInstalledApps = getOpenSourceInstalledApps().toMutableList() val gPlayInstalledApps = getGPlayInstalledApps().toMutableList() - if (appLoungePreference.shouldUpdateAppsFromOtherStores()) { + if (appPreferencesRepository.shouldUpdateAppsFromOtherStores()) { withContext(Dispatchers.IO) { val otherStoresInstalledApps = getAppsFromOtherStores().toMutableList() @@ -129,7 +129,7 @@ class UpdatesManagerImpl @Inject constructor( val openSourceInstalledApps = getOpenSourceInstalledApps().toMutableList() - if (appLoungePreference.shouldUpdateAppsFromOtherStores()) { + if (appPreferencesRepository.shouldUpdateAppsFromOtherStores()) { val otherStoresInstalledApps = getAppsFromOtherStores().toMutableList() // This list is based on app signatures diff --git a/app/src/main/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCase.kt index 2c29f7a0f4531bb3f559e5be129d0e70fa5db954..f389255b174160ea17caee810708de74f5bb72e2 100644 --- a/app/src/main/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCase.kt @@ -2,8 +2,8 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.state.LoginCoordinator +import foundation.e.apps.domain.model.User import javax.inject.Inject class InitialAnonymousLoginUseCase @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCase.kt index 55686395bd41db63b1c023dda316ff06a81cfb7a..cef70d28e1d3f9211c09270fedba78df9e500f2a 100644 --- a/app/src/main/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCase.kt @@ -2,9 +2,9 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource import foundation.e.apps.data.login.state.LoginCoordinator +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User import javax.inject.Inject class InitialGoogleLoginUseCase @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt index a8a608aad756c7f7f6ceb2d8cebde13e70df9d37..bc8d7049502d149f6fd39e6a716a3c7910cc8dc5 100644 --- a/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt @@ -2,11 +2,11 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.microg.MicrogAccount -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource import foundation.e.apps.data.login.repository.AuthenticatorRepository import foundation.e.apps.data.login.state.LoginCoordinator +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User import javax.inject.Inject class InitialMicrogLoginUseCase @Inject constructor( diff --git a/app/src/main/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCase.kt b/app/src/main/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCase.kt index 49ed7ff372459bc81662ba0a99ca0a6ce73f5206..f2ef1113a4761290caca106c27ea3e1235f32b3c 100644 --- a/app/src/main/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCase.kt @@ -18,16 +18,16 @@ package foundation.e.apps.domain.search -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.search.SuggestionSource +import foundation.e.apps.domain.preferences.AppPreferencesRepository import javax.inject.Inject class FetchSearchSuggestionsUseCase @Inject constructor( private val suggestionSource: SuggestionSource, - private val appLoungePreference: AppLoungePreference, + private val appPreferencesRepository: AppPreferencesRepository, ) { suspend operator fun invoke(query: String): List { - if (query.isBlank() || !appLoungePreference.isPlayStoreSelected()) { + if (query.isBlank() || !appPreferencesRepository.isPlayStoreSelected()) { return emptyList() } return suggestionSource.suggest(query) diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index 6102b1d8241d1fab932af464f2f8a92a1b735022..b34791151876e56c86674825a4dd039e0f0db3b2 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -47,7 +47,6 @@ import foundation.e.apps.BuildConfig import foundation.e.apps.R import foundation.e.apps.contract.ParentalControlContract.COLUMN_LOGIN_TYPE import foundation.e.apps.data.Constants -import foundation.e.apps.data.enums.User import foundation.e.apps.data.event.AppEvent import foundation.e.apps.data.event.EventBus import foundation.e.apps.data.install.models.AppInstall @@ -55,6 +54,7 @@ import foundation.e.apps.data.install.updates.UpdatesNotifier import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.system.ParentalControlAuthenticator import foundation.e.apps.databinding.ActivityMainBinding +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment import foundation.e.apps.ui.error.AppUnavailableDialogDirections import foundation.e.apps.ui.purchase.AppPurchaseFragmentDirections 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 8d41a6a959f7e87208500ba814a4f4d83717e946..b5deaa4eecc27ba67149370d09118ddff63b0b7d 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -32,7 +32,6 @@ import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.mapper.toApplication import foundation.e.apps.data.blockedApps.BlockedAppRepository -import foundation.e.apps.data.enums.User import foundation.e.apps.data.enums.isInitialized import foundation.e.apps.data.enums.isUnFiltered import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository @@ -42,21 +41,22 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.install.workmanager.AppInstallProcessor import foundation.e.apps.data.login.core.AuthObject -import foundation.e.apps.data.login.state.LoginState import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync import foundation.e.apps.data.system.NetworkStatusManager import foundation.e.apps.domain.application.ApplicationDomain import foundation.e.apps.domain.login.ReportFaultyTokenUseCase +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class MainActivityViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, private val applicationRepository: ApplicationRepository, private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, @@ -75,7 +75,7 @@ class MainActivityViewModel @Inject constructor( fetchUpdatableSystemAppsList() } - val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() + val tocStatus: LiveData = sessionRepository.tocStatus.asLiveData() private val _purchaseAppLiveData: MutableLiveData = MutableLiveData() val purchaseAppLiveData: LiveData = _purchaseAppLiveData @@ -107,15 +107,15 @@ class MainActivityViewModel @Inject constructor( var shouldIgnoreSessionError = false fun getTocStatus(): Boolean { - return appLoungeDataStore.tocStatus.getSync() + return sessionRepository.tocStatus.getSync() } fun getUser(): User { - return appLoungeDataStore.getUser() + return sessionRepository.getUser() } fun getLoginState(): LoginState { - return appLoungeDataStore.getLoginState() + return sessionRepository.getLoginState() } fun handleAuthObjects(authObjects: List?) { @@ -184,7 +184,7 @@ class MainActivityViewModel @Inject constructor( * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/266 */ fun shouldShowPaidAppsSnackBar(app: Application): Boolean { - val authData = appLoungeDataStore.getAuthData() + val authData = sessionRepository.getAuthData() if (!app.isFree && authData.isAnonymous) { _errorMessageStringResource.value = R.string.paid_app_anonymous_message return true @@ -281,7 +281,7 @@ class MainActivityViewModel @Inject constructor( suspend fun updateAwaitingForPurchasedApp(packageName: String): AppInstall? { val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) - val authData = appLoungeDataStore.getAuthData() + val authData = sessionRepository.getAuthData() if (!authData.isAnonymous) { appInstallProcessor.enqueueFusedDownload(fusedDownload) return fusedDownload 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 3148ea144ed4480d01108090957690b60559551c..a2f414ca250b5db4a21c60ba4af21a1305c1657d 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 @@ -56,7 +56,6 @@ import foundation.e.apps.data.application.data.shareUri 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.enums.isInitialized import foundation.e.apps.data.exodus.ExodusUriGenerator import foundation.e.apps.data.install.download.data.DownloadProgress @@ -64,10 +63,11 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.exceptions.GPlayLoginException -import foundation.e.apps.data.login.state.LoginState import foundation.e.apps.data.utils.isValid import foundation.e.apps.databinding.FragmentApplicationBinding import foundation.e.apps.domain.ValidateAppAgeLimitUseCase.Companion.KEY_ANTI_FEATURES_NSFW +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.MainActivity import foundation.e.apps.ui.MainActivityViewModel diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt index 596a463d0423d900bbfb4fb76e2873d1661386e4..531a4623a790be17e8fcc8faec681fec5ca14c17 100644 --- a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt @@ -44,10 +44,10 @@ import foundation.e.apps.data.application.ApplicationInstaller import foundation.e.apps.data.application.data.Application 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.install.pkg.InstallerService -import foundation.e.apps.data.login.state.LoginState import foundation.e.apps.databinding.ApplicationListItemBinding +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.MainActivityViewModel import foundation.e.apps.ui.PrivacyInfoViewModel diff --git a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateInput.kt b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateInput.kt index 4c4f4727da60a9f6b9b5f46dde7c1449b68281fd..52dc9c4c979ff94ca7fb12fe7179cf04ffa40b2b 100644 --- a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateInput.kt +++ b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateInput.kt @@ -20,8 +20,8 @@ package foundation.e.apps.ui.compose.state import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.User import foundation.e.apps.data.install.pkg.InstallerService +import foundation.e.apps.domain.model.User data class InstallationFault( val isFaulty: Boolean, diff --git a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt index a624bfe87672e8944ba2c8f5b9c67090447d2811..659d98a416764347517443948c9078eb50b0405c 100644 --- a/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt +++ b/app/src/main/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapper.kt @@ -21,7 +21,7 @@ package foundation.e.apps.ui.compose.state import foundation.e.apps.R import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User /* * Map raw application + contextual signals into a single button state. diff --git a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt index 3c527119f75c82904dbaeb9f1f5b192b145956db..2d56ed0a4813f8890403a9f9ce4c601d72585ad1 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt @@ -33,10 +33,10 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.snackbar.Snackbar import foundation.e.apps.R import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.state.LoginState import foundation.e.apps.databinding.HomeChildListItemBinding import foundation.e.apps.domain.application.ApplicationDomain +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.MainActivityViewModel import foundation.e.apps.ui.home.HomeFragmentDirections diff --git a/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt b/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt index 1d2d3b1e2dcd714795c83017fbe255a6ecbf628d..08b26e96b15fbf7b1c656fc5eab83e281e01fa8f 100644 --- a/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt @@ -32,7 +32,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import foundation.e.apps.R -import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.exceptions.CleanApkException @@ -43,6 +42,7 @@ import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.login.exceptions.GPlayValidationException import foundation.e.apps.data.login.exceptions.UnknownSourceException import foundation.e.apps.databinding.DialogErrorLogBinding +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.LoginViewModel import foundation.e.apps.ui.MainActivityViewModel import timber.log.Timber diff --git a/app/src/main/java/foundation/e/apps/ui/search/v2/SearchFragmentV2.kt b/app/src/main/java/foundation/e/apps/ui/search/v2/SearchFragmentV2.kt index c2081b38a1f105d10e3a45e51fde73a099fcd5e4..bcbf1ef3c7775b9bc95b216a0a13bd88394b84f1 100644 --- a/app/src/main/java/foundation/e/apps/ui/search/v2/SearchFragmentV2.kt +++ b/app/src/main/java/foundation/e/apps/ui/search/v2/SearchFragmentV2.kt @@ -39,10 +39,10 @@ import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.R import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.User import foundation.e.apps.data.enums.isInitialized import foundation.e.apps.data.enums.isUnFiltered import foundation.e.apps.data.install.download.data.DownloadProgress +import foundation.e.apps.domain.model.User import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.AppProgressViewModel import foundation.e.apps.ui.MainActivityViewModel diff --git a/app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt b/app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt index b88910b5af2a9786df448d6933f6ad2a13084303..c0df366afb1fe87e3bb8926e991f68cb1c273d64 100644 --- a/app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt +++ b/app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt @@ -30,7 +30,7 @@ import foundation.e.apps.data.cleanapk.CleanApkRetrofit import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.data.install.download.data.DownloadProgress -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import foundation.e.apps.domain.search.CleanApkSearchPagingUseCase import foundation.e.apps.domain.search.FetchSearchSuggestionsUseCase import foundation.e.apps.domain.search.PlayStoreSearchPagingUseCase @@ -81,9 +81,9 @@ data class ScrollPosition( @HiltViewModel @Suppress("LongParameterList", "TooManyFunctions") class SearchViewModelV2 @Inject constructor( - private val appLoungePreference: AppLoungePreference, cleanApkSearchPagingUseCase: CleanApkSearchPagingUseCase, playStoreSearchPagingUseCase: PlayStoreSearchPagingUseCase, + private val appPreferencesRepository: AppPreferencesRepository, private val fetchSearchSuggestionsUseCase: FetchSearchSuggestionsUseCase, private val prepareSearchSubmissionUseCase: PrepareSearchSubmissionUseCase, private val stores: Stores, @@ -160,7 +160,7 @@ class SearchViewModelV2 @Inject constructor( return } - if (!appLoungePreference.isPlayStoreSelected()) { + if (!appPreferencesRepository.isPlayStoreSelected()) { _uiState.update { current -> current.copy( suggestions = emptyList(), @@ -261,7 +261,7 @@ class SearchViewModelV2 @Inject constructor( else -> null } - val areSuggestionsEnabled = appLoungePreference.isPlayStoreSelected() + val areSuggestionsEnabled = appPreferencesRepository.isPlayStoreSelected() _uiState.update { current -> val updatedSuggestions = if (areSuggestionsEnabled) current.suggestions else emptyList() diff --git a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt index 12f85b222bdd714d3710514123469d78536bae75..f2ca68226c998ac93cc958e65cedc8aa92a2e71b 100644 --- a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt @@ -27,6 +27,7 @@ import android.widget.Toast import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.preference.CheckBoxPreference import androidx.preference.ListPreference @@ -44,13 +45,14 @@ import foundation.e.apps.data.Constants import foundation.e.apps.data.Stores import foundation.e.apps.data.application.UpdatesDao import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User import foundation.e.apps.data.install.updates.UpdatesWorkManager -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.getSync +import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.system.SystemInfoProvider import foundation.e.apps.databinding.CustomPreferenceBinding +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository import foundation.e.apps.ui.LoginViewModel +import kotlinx.coroutines.launch import timber.log.Timber import java.util.Locale import javax.inject.Inject @@ -77,11 +79,10 @@ class SettingsFragment : PreferenceFragmentCompat() { lateinit var clipboardManager: ClipboardManager @Inject - lateinit var appLoungeDataStore: AppLoungeDataStore + lateinit var sessionRepository: SessionRepository - private val user by lazy { - appLoungeDataStore.getUser() - } + @Inject + lateinit var playStoreAuthStore: PlayStoreAuthStore private val allSourceCheckboxes by lazy { listOf(showAllApplications, showFOSSApplications, showPWAApplications) @@ -210,9 +211,11 @@ class SettingsFragment : PreferenceFragmentCompat() { // This is useful if a user from older App Lounge updates to this version disableDependentCheckbox(onlyUnmeteredNetwork, autoInstallUpdate) - appLoungeDataStore.getAuthData().let { authData -> - appLoungeDataStore.getUser().name.let { user -> - handleUser(user, authData) + viewLifecycleOwner.lifecycleScope.launch { + val authData = playStoreAuthStore.awaitAuthData() + val user = sessionRepository.awaitUser() + authData?.let { + handleUser(user.name, it) } } @@ -224,7 +227,7 @@ class SettingsFragment : PreferenceFragmentCompat() { loginViewModel.logout() } - if (user == User.NO_GOOGLE) { + if (sessionRepository.user.value == User.NO_GOOGLE) { /* * For No-Google mode, do not allow the user to click * on the option to show GPlay apps. @@ -270,7 +273,7 @@ class SettingsFragment : PreferenceFragmentCompat() { User.GOOGLE.name -> { if (!authData.isAnonymous) { binding.accountType.text = authData.userProfile?.name - binding.email.text = appLoungeDataStore.emailData.getSync() + binding.email.text = playStoreAuthStore.email.value binding.avatar.load(authData.userProfile?.artwork?.url) } } @@ -349,7 +352,7 @@ class SettingsFragment : PreferenceFragmentCompat() { updateCheckInterval: ListPreference?, updateCheckIntervalAnonymous: ListPreference? ) { - when (user) { + when (sessionRepository.user.value) { User.ANONYMOUS -> { updateCheckInterval?.isVisible = false updateCheckIntervalAnonymous?.isVisible = true diff --git a/app/src/main/java/foundation/e/apps/ui/setup/signin/LocaleChangedBroadcastReceiver.kt b/app/src/main/java/foundation/e/apps/ui/setup/signin/LocaleChangedBroadcastReceiver.kt index 1ebb0bb6f220a1576daa2c5f0af2c52893b830e1..7eece67a468b039c20ba2edd0294fada428db432 100644 --- a/app/src/main/java/foundation/e/apps/ui/setup/signin/LocaleChangedBroadcastReceiver.kt +++ b/app/src/main/java/foundation/e/apps/ui/setup/signin/LocaleChangedBroadcastReceiver.kt @@ -21,14 +21,11 @@ package foundation.e.apps.ui.setup.signin import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.di.qualifiers.IoCoroutineScope -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.getSync +import foundation.e.apps.data.preference.PlayStoreAuthStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json import okhttp3.Cache import timber.log.Timber import javax.inject.Inject @@ -37,10 +34,7 @@ import javax.inject.Inject class LocaleChangedBroadcastReceiver : BroadcastReceiver() { @Inject - lateinit var appLoungeDataStore: AppLoungeDataStore - - @Inject - lateinit var json: Json + lateinit var playStoreAuthStore: PlayStoreAuthStore @Inject lateinit var cache: Cache @@ -56,10 +50,9 @@ class LocaleChangedBroadcastReceiver : BroadcastReceiver() { } coroutineScope.launch { try { - val authDataJson = appLoungeDataStore.authData.getSync() - val authData = json.decodeFromString(authDataJson) + val authData = playStoreAuthStore.awaitAuthData() ?: return@launch authData.locale = context.resources.configuration.locales[0] - appLoungeDataStore.saveAuthData(authData) + playStoreAuthStore.saveAuthData(authData) cache.evictAll() } catch (ex: Exception) { Timber.e(ex.message.toString()) diff --git a/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt b/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt index 7bff8a487aa9c55aee12925e668422034741cff3..0a26bc215dbf4657b151af516ae5e46547a43053 100644 --- a/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt @@ -6,15 +6,16 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository import javax.inject.Inject @HiltViewModel class SignInViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, + private val sessionRepository: SessionRepository, ) : ViewModel() { - val userType: LiveData = appLoungeDataStore.userType.asLiveData() + val userType: LiveData = sessionRepository.user.asLiveData() private val _authLiveData: MutableLiveData = MutableLiveData() val authLiveData: LiveData = _authLiveData diff --git a/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt b/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt index c9472443dc9059d3b74ab36fb6d3816498eb9e65..c767dce6c41e9be0917d1a97c133440e82d6f99c 100644 --- a/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt @@ -5,20 +5,20 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class TOSViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore + private val sessionRepository: SessionRepository, ) : ViewModel() { - val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() + val tocStatus: LiveData = sessionRepository.tocStatus.asLiveData() fun saveTOCStatus(status: Boolean) { viewModelScope.launch { - appLoungeDataStore.saveTOCStatus(status, TOS_VERSION) + sessionRepository.saveTocStatus(status, TOS_VERSION) } } } 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 0b89ff15db2b8499d4c18345df169b2a360d741f..cc0eb70c34b9c78a01c0320274fddd3f0342f198 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,13 +30,13 @@ 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.login.state.LoginState -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.updates.UpdatesManagerRepository +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,8 +44,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 sessionRepository: SessionRepository, + private val appPreferencesRepository: AppPreferencesRepository, private val stores: Stores ) : ViewModel() { @@ -67,9 +67,9 @@ class UpdatesViewModel @Inject constructor( fun loadUpdates() = viewModelScope.launch { exceptionsList.clear() - val currentUser = appLoungeDataStore.getUser() - val loginState = appLoungeDataStore.getLoginState() - val isOssOnly = !preference.isPlayStoreSelected() || + val currentUser = sessionRepository.awaitUser() + val loginState = sessionRepository.awaitLoginState() + val isOssOnly = !appPreferencesRepository.isPlayStoreSelected() || (loginState == LoginState.UNAVAILABLE || currentUser == User.NO_GOOGLE) val updates = if (isOssOnly) { updatesManagerRepository.getUpdatesOSS() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25c7dc2283994512f71602ce47798a129266cca1..7cb3f8fef116b49009311ca329f323cd6a5265d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,7 +190,6 @@ updateCheckIntervals updateCheckIntervalsAnonymous - anonymousUpdateMigrationCompleted updateNotify updateInstallAuto updateUnmeteredOnly diff --git a/app/src/test/java/foundation/e/apps/FakeAppLoungePreference.kt b/app/src/test/java/foundation/e/apps/FakeAppLoungePreference.kt index 7c7adda218901468009ba0f9046a565ca792829e..3f770f90486ee427f340b2bb073eed86e11a0352 100644 --- a/app/src/test/java/foundation/e/apps/FakeAppLoungePreference.kt +++ b/app/src/test/java/foundation/e/apps/FakeAppLoungePreference.kt @@ -18,19 +18,17 @@ package foundation.e.apps -import android.content.Context -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository -class FakeAppLoungePreference( - private val context: Context, - appLoungeDataStore: AppLoungeDataStore, -) : AppLoungePreference(context, appLoungeDataStore) { +class FakeAppLoungePreference : AppPreferencesRepository { var isPWASelectedFake = false var isOpenSourceelectedFake = false var isGplaySelectedFake = false + var shouldShowUpdateNotificationFake = true + var automaticInstallEnabledFake = true var shouldUpdateFromOtherStores = true + var onlyUnmeteredNetworkEnabled = true override fun isPWASelected(): Boolean { return isPWASelectedFake @@ -56,9 +54,39 @@ class FakeAppLoungePreference( return shouldUpdateFromOtherStores } - override fun getUpdateInterval(): Long { - val updateIntervals = context.resources.getStringArray(R.array.update_interval_values) + override fun shouldShowUpdateNotification(): Boolean { + return shouldShowUpdateNotificationFake + } + + override fun isAutomaticInstallEnabled(): Boolean { + return automaticInstallEnabledFake + } + + override fun isOnlyUnmeteredNetworkEnabled(): Boolean { + return onlyUnmeteredNetworkEnabled + } + + override fun disablePlayStore() { + isGplaySelectedFake = false + } + + override fun disableOpenSource() { + isOpenSourceelectedFake = false + } + + override fun disablePwa() { + isPWASelectedFake = false + } + + override fun enablePlayStore() { + isGplaySelectedFake = true + } + + override fun enableOpenSource() { + isOpenSourceelectedFake = true + } - return updateIntervals[1].toLong() // 168 (Weekly Interval) + override fun enablePwa() { + isPWASelectedFake = true } } diff --git a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt index f204988f37c0099dd3af2662c88618cceea572b0..15c4fc1ff71c6866760f7521cffb5b9a112e3ece 100644 --- a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt +++ b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt @@ -30,7 +30,6 @@ import foundation.e.apps.data.enums.Status import foundation.e.apps.data.faultyApps.FaultyAppRepository import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.updates.UpdatesManagerImpl import foundation.e.apps.util.MainCoroutineRule import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -82,16 +81,13 @@ class UpdateManagerImptTest { @Mock private lateinit var systemAppsUpdatesRepository: SystemAppsUpdatesRepository - @Mock - private lateinit var appLoungeDataStore: AppLoungeDataStore - val authData = AuthData("e@e.email", "AtadyMsIAtadyM") @Before fun setup() { MockitoAnnotations.openMocks(this) faultyAppRepository = FaultyAppRepository(FakeFaultyAppDao()) - preferenceModule = FakeAppLoungePreference(context, appLoungeDataStore) + preferenceModule = FakeAppLoungePreference() pkgManagerModule = FakeAppLoungePackageManager(context, getGplayApps()) updatesManagerImpl = UpdatesManagerImpl( context, diff --git a/app/src/test/java/foundation/e/apps/data/StoreConfigTest.kt b/app/src/test/java/foundation/e/apps/data/StoreConfigTest.kt index b029726c1c3557007e1f58b849878b4e55e15291..f60590038708914eea1aa86ddccbceeb722e8ccb 100644 --- a/app/src/test/java/foundation/e/apps/data/StoreConfigTest.kt +++ b/app/src/test/java/foundation/e/apps/data/StoreConfigTest.kt @@ -5,7 +5,7 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkAppsRepository import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.playstore.PlayStoreRepository -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -17,7 +17,7 @@ class StoreConfigTest { private val playStoreRepository: PlayStoreRepository = mockk(relaxed = true) private val cleanApkAppsRepository: CleanApkAppsRepository = mockk(relaxed = true) private val cleanApkPwaRepository: CleanApkPwaRepository = mockk(relaxed = true) - private val preference: AppLoungePreference = mockk(relaxed = true) + private val preference: AppPreferencesRepository = mockk(relaxed = true) @Before fun setup() { diff --git a/app/src/test/java/foundation/e/apps/data/StoresTest.kt b/app/src/test/java/foundation/e/apps/data/StoresTest.kt index 674f977965facc37d1dc12b65c910f24719bdada..977a652d7f651da764c5a8662307c93d690a8dad 100644 --- a/app/src/test/java/foundation/e/apps/data/StoresTest.kt +++ b/app/src/test/java/foundation/e/apps/data/StoresTest.kt @@ -5,7 +5,7 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkAppsRepository import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.playstore.PlayStoreRepository -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -17,7 +17,7 @@ class StoresTest { private val playStoreRepository: PlayStoreRepository = mockk(relaxed = true) private val cleanApkAppsRepository: CleanApkAppsRepository = mockk(relaxed = true) private val cleanApkPwaRepository: CleanApkPwaRepository = mockk(relaxed = true) - private lateinit var preference: AppLoungePreference + private lateinit var preference: AppPreferencesRepository private lateinit var stores: Stores private var playStoreSelected = true private var openSourceSelected = true diff --git a/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt b/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt index 4f7c0ba385dabbed595ee0423b767fe7d417b2ec..c3dd00dfb82e678820342c7e7de4b2059a843fad 100644 --- a/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt +++ b/app/src/test/java/foundation/e/apps/data/event/AppEventTest.kt @@ -2,7 +2,7 @@ package foundation.e.apps.data.event import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.ResultSupreme import kotlinx.coroutines.CompletableDeferred diff --git a/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt b/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt index 277cb7d3bab325ad1711554da5b997d6c0156176..008b80108e5b825708f6dd2266f0faafc149e2d1 100644 --- a/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt @@ -17,6 +17,7 @@ package foundation.e.apps.data.install +import android.content.Context import foundation.e.apps.data.enums.Status import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.models.AppInstall @@ -28,9 +29,10 @@ import org.junit.Test class AppManagerWrapperProgressTest { + private val context = mockk(relaxed = true) private val appManager = mockk(relaxed = true) private val fdroidRepository = mockk(relaxed = true) - private val appManagerWrapper = AppManagerWrapper(appManager, fdroidRepository) + private val appManagerWrapper = AppManagerWrapper(context, appManager, fdroidRepository) @Test fun calculateProgress_emptyDownloadIds_returnsZero() = runTest { diff --git a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt index 962863037d67f8d86b81db25891659a5e5412f38..2df96a386e448886600fe3a370ecf03fd529bfc1 100644 --- a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt @@ -42,23 +42,27 @@ import foundation.e.apps.data.enums.FilterLevel 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.gitlab.SystemAppsUpdatesRepository import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreAuthResult import foundation.e.apps.data.login.core.StoreAuthenticator import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.repository.AuthenticatorRepository -import foundation.e.apps.data.login.state.LoginState -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import foundation.e.apps.data.updates.UpdatesManagerRepository import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.spyk +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any @@ -75,7 +79,7 @@ import org.robolectric.annotation.Config import kotlin.test.assertFalse @RunWith(RobolectricTestRunner::class) -@Config(sdk = [Build.VERSION_CODES.N]) +@Config(sdk = [Build.VERSION_CODES.R]) class UpdatesWorkerTest { @Test @@ -117,8 +121,8 @@ class UpdatesWorkerTest { appLoungeDataStore.saveUserType(User.GOOGLE) appLoungeDataStore.saveAuthData(authData) - val user = appLoungeDataStore.getUser() - val loginState = appLoungeDataStore.getLoginState() + val user = appLoungeDataStore.awaitUser() + val loginState = appLoungeDataStore.awaitLoginState() assertThat(user).isEqualTo(User.GOOGLE) assertThat(loginState).isEqualTo(LoginState.AVAILABLE) @@ -141,7 +145,7 @@ class UpdatesWorkerTest { whenever(systemAppsUpdatesRepository.fetchUpdatableSystemApps(true)) .thenReturn(ResultSupreme.Success(Unit)) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -227,7 +231,7 @@ class UpdatesWorkerTest { authData ) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -269,7 +273,7 @@ class UpdatesWorkerTest { whenever(params.inputData).thenReturn(inputData) val worker = spyk( - UpdatesWorker( + createWorker( workerContext, params, updatesManagerRepository, @@ -306,7 +310,7 @@ class UpdatesWorkerTest { val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() - val worker = UpdatesWorker( + val worker = createWorker( appContext, params, updatesManagerRepository, @@ -334,7 +338,7 @@ class UpdatesWorkerTest { val appInstallProcessor = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -365,7 +369,7 @@ class UpdatesWorkerTest { val appInstallProcessor = mock() val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -414,7 +418,7 @@ class UpdatesWorkerTest { val blockedAppRepository = mock() val systemAppsUpdatesRepository = mock() - val worker = UpdatesWorker( + val worker = createWorker( appContext, params, updatesManagerRepository, @@ -440,7 +444,7 @@ class UpdatesWorkerTest { val sharedPreferences = mock() val params = mock() val updatesManagerRepository = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val authenticatorRepository = mock() val appInstallProcessor = mock() val blockedAppRepository = mock() @@ -451,18 +455,23 @@ class UpdatesWorkerTest { whenever(workerContext.getString(any())).thenReturn("key") whenever(sharedPreferences.getBoolean(any(), any())).thenReturn(true) - whenever(appLoungeDataStore.getUser()).thenReturn(User.GOOGLE) + whenever(sessionDataStore.awaitUser()).thenReturn(User.GOOGLE) whenever(authenticatorRepository.getValidatedAuthData()).thenReturn(ResultSupreme.Error("no auth")) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, - appLoungeDataStore, + sessionDataStore, authenticatorRepository, appInstallProcessor, blockedAppRepository, - systemAppsUpdatesRepository + systemAppsUpdatesRepository, + createAppPreferencesRepository( + shouldShowUpdateNotification = true, + isAutomaticInstallEnabled = true, + isOnlyUnmeteredNetworkEnabled = false + ) ) val result = worker.getAvailableUpdates() @@ -478,7 +487,7 @@ class UpdatesWorkerTest { val sharedPreferences = mock() val params = mock() val updatesManagerRepository = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val authenticatorRepository = mock() val appInstallProcessor = mock() val blockedAppRepository = mock() @@ -489,19 +498,24 @@ class UpdatesWorkerTest { whenever(workerContext.getString(any())).thenReturn("key") whenever(sharedPreferences.getBoolean(any(), any())).thenReturn(true) - whenever(appLoungeDataStore.getUser()).thenReturn(User.NO_GOOGLE) + whenever(sessionDataStore.awaitUser()).thenReturn(User.NO_GOOGLE) whenever(authenticatorRepository.getValidatedAuthData()).thenReturn(ResultSupreme.Error("no auth")) whenever(updatesManagerRepository.getUpdatesOSS()).thenReturn(Pair(emptyList(), ResultStatus.TIMEOUT)) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, - appLoungeDataStore, + sessionDataStore, authenticatorRepository, appInstallProcessor, blockedAppRepository, - systemAppsUpdatesRepository + systemAppsUpdatesRepository, + createAppPreferencesRepository( + shouldShowUpdateNotification = true, + isAutomaticInstallEnabled = true, + isOnlyUnmeteredNetworkEnabled = true + ) ) val result = worker.getAvailableUpdates() @@ -521,7 +535,7 @@ class UpdatesWorkerTest { val notificationManager = mock() val params = mock() val updatesManagerRepository = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val authenticatorRepository = mock() val appInstallProcessor = mock() val blockedAppRepository = mock() @@ -548,7 +562,7 @@ class UpdatesWorkerTest { whenever(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)).thenReturn(false) whenever(params.inputData).thenReturn(inputData) - whenever(appLoungeDataStore.getUser()).thenReturn(User.NO_GOOGLE) + whenever(sessionDataStore.awaitUser()).thenReturn(User.NO_GOOGLE) whenever(authenticatorRepository.getValidatedAuthData()).thenReturn(ResultSupreme.Error("no auth")) whenever(updatesManagerRepository.getUpdatesOSS()).thenReturn( Pair( @@ -570,15 +584,20 @@ class UpdatesWorkerTest { .thenReturn(ResultSupreme.Success(Unit)) whenever(appInstallProcessor.initAppInstall(any(), any())).thenReturn(true) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, - appLoungeDataStore, + sessionDataStore, authenticatorRepository, appInstallProcessor, blockedAppRepository, - systemAppsUpdatesRepository + systemAppsUpdatesRepository, + createAppPreferencesRepository( + shouldShowUpdateNotification = true, + isAutomaticInstallEnabled = true, + isOnlyUnmeteredNetworkEnabled = false + ) ) val result = worker.doWork() @@ -597,7 +616,7 @@ class UpdatesWorkerTest { val notificationManager = mock() val params = mock() val updatesManagerRepository = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val authenticatorRepository = mock() val appInstallProcessor = mock() val blockedAppRepository = mock() @@ -624,7 +643,7 @@ class UpdatesWorkerTest { whenever(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)).thenReturn(false) whenever(params.inputData).thenReturn(inputData) - whenever(appLoungeDataStore.getUser()).thenReturn(User.NO_GOOGLE) + whenever(sessionDataStore.awaitUser()).thenReturn(User.NO_GOOGLE) whenever(authenticatorRepository.getValidatedAuthData()).thenReturn(ResultSupreme.Error("no auth")) whenever(updatesManagerRepository.getUpdatesOSS()).thenReturn( Pair( @@ -645,11 +664,11 @@ class UpdatesWorkerTest { whenever(systemAppsUpdatesRepository.fetchUpdatableSystemApps(true)) .thenReturn(ResultSupreme.Success(Unit)) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, - appLoungeDataStore, + sessionDataStore, authenticatorRepository, appInstallProcessor, blockedAppRepository, @@ -700,7 +719,7 @@ class UpdatesWorkerTest { whenever(sharedPreferences.getBoolean(any(), any())).thenReturn(true) val worker = spyk( - UpdatesWorker( + createWorker( workerContext, params, updatesManagerRepository, @@ -757,7 +776,7 @@ class UpdatesWorkerTest { whenever(sharedPreferences.getBoolean(any(), any())).thenReturn(true) val worker = spyk( - UpdatesWorker( + createWorker( workerContext, params, updatesManagerRepository, @@ -799,7 +818,7 @@ class UpdatesWorkerTest { null ) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -846,7 +865,7 @@ class UpdatesWorkerTest { null ) - val worker = UpdatesWorker( + val worker = createWorker( workerContext, params, updatesManagerRepository, @@ -884,7 +903,7 @@ class UpdatesWorkerTest { .thenReturn(android.content.pm.PackageManager.PERMISSION_GRANTED) val worker = spyk( - UpdatesWorker( + createWorker( workerContext, params, updatesManagerRepository, @@ -934,7 +953,7 @@ class UpdatesWorkerTest { .thenReturn(android.content.pm.PackageManager.PERMISSION_GRANTED) val worker = spyk( - UpdatesWorker( + createWorker( workerContext, params, updatesManagerRepository, @@ -969,9 +988,72 @@ class UpdatesWorkerTest { coVerify(exactly = 1) { worker.startUpdateProcess(apps) } } + private fun createWorker( + context: Context, + params: WorkerParameters, + updatesManagerRepository: UpdatesManagerRepository, + sessionRepository: SessionRepository, + authenticatorRepository: AuthenticatorRepository, + appInstallProcessor: AppInstallProcessor, + blockedAppRepository: BlockedAppRepository, + systemAppsUpdatesRepository: SystemAppsUpdatesRepository, + appPreferencesRepository: AppPreferencesRepository = createAppPreferencesRepository(), + ): UpdatesWorker { + return UpdatesWorker( + context, + params, + updatesManagerRepository, + sessionRepository, + appPreferencesRepository, + authenticatorRepository, + appInstallProcessor, + blockedAppRepository, + systemAppsUpdatesRepository + ) + } - private fun createDataStore(context: Context): AppLoungeDataStore { + private fun createAppPreferencesRepository( + shouldShowUpdateNotification: Boolean = true, + isAutomaticInstallEnabled: Boolean = true, + isOnlyUnmeteredNetworkEnabled: Boolean = true, + ): AppPreferencesRepository { + return object : AppPreferencesRepository { + override fun preferredApplicationType(): String = "any" + + override fun isOpenSourceSelected(): Boolean = true + + override fun isPWASelected(): Boolean = true + + override fun isPlayStoreSelected(): Boolean = true + + override fun shouldShowUpdateNotification(): Boolean = shouldShowUpdateNotification + + override fun isAutomaticInstallEnabled(): Boolean = isAutomaticInstallEnabled + + override fun disablePlayStore() = Unit + + override fun disableOpenSource() = Unit + + override fun disablePwa() = Unit + + override fun enablePlayStore() = Unit + + override fun enableOpenSource() = Unit + + override fun enablePwa() = Unit + + override fun shouldUpdateAppsFromOtherStores(): Boolean = true + + override fun isOnlyUnmeteredNetworkEnabled(): Boolean = isOnlyUnmeteredNetworkEnabled + } + } + + private fun createDataStore(context: Context): SessionDataStore { val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } - return AppLoungeDataStore(context, json) + return SessionDataStore( + context, + json, + CoroutineScope(UnconfinedTestDispatcher()) + ) } } diff --git a/app/src/test/java/foundation/e/apps/data/login/LoginPreferencesManagerTest.kt b/app/src/test/java/foundation/e/apps/data/login/LoginPreferencesManagerTest.kt index 7301b590f4f81dd05de8a043af362bfdebd91d0b..6743d7645923dcc281ca14fb0b7cd5728ccba709 100644 --- a/app/src/test/java/foundation/e/apps/data/login/LoginPreferencesManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/LoginPreferencesManagerTest.kt @@ -1,31 +1,31 @@ package foundation.e.apps.data.login import foundation.e.apps.data.login.state.LoginPreferencesManager -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository import io.mockk.mockk import io.mockk.verify import org.junit.Test class LoginPreferencesManagerTest { - private val preference: AppLoungePreference = mockk(relaxed = true) - private val manager = LoginPreferencesManager(preference) + private val appPreferencesRepository: AppPreferencesRepository = mockk(relaxed = true) + private val manager = LoginPreferencesManager(appPreferencesRepository) @Test fun applyNoGoogleMode_updatesPreferences() { manager.applyNoGoogleMode() - verify { preference.disablePlayStore() } - verify { preference.enableOpenSource() } - verify { preference.enablePwa() } + verify { appPreferencesRepository.disablePlayStore() } + verify { appPreferencesRepository.enableOpenSource() } + verify { appPreferencesRepository.enablePwa() } } @Test fun resetAfterLogout_resetsPreferences() { manager.resetAfterLogout() - verify { preference.enableOpenSource() } - verify { preference.enablePwa() } - verify { preference.enablePlayStore() } + verify { appPreferencesRepository.enableOpenSource() } + verify { appPreferencesRepository.enablePwa() } + verify { appPreferencesRepository.enablePlayStore() } } } diff --git a/app/src/test/java/foundation/e/apps/data/login/MicrogLoginManagerTest.kt b/app/src/test/java/foundation/e/apps/data/login/MicrogLoginManagerTest.kt index 920f8a84eb51e2645b6f47499763ae3deb0cf6ad..2ceeb98f4bf5ee8c71336586812c9cbe321c2499 100644 --- a/app/src/test/java/foundation/e/apps/data/login/MicrogLoginManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/MicrogLoginManagerTest.kt @@ -21,13 +21,12 @@ package foundation.e.apps.data.login import android.accounts.Account import android.accounts.AccountManager import android.accounts.AccountManagerFuture -import android.app.Application import android.content.Intent import android.os.Bundle import foundation.e.apps.data.login.microg.MicrogCertUtil import foundation.e.apps.data.login.microg.MicrogLoginManager import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking @@ -202,12 +201,12 @@ class MicrogLoginManagerTest { whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE))) .thenReturn(emptyArray()) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() - whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("oauth")) + val sessionDataStore = mock() + whenever(sessionDataStore.oauthToken).thenReturn(flowOf("oauth")) val authData = com.aurora.gplayapi.data.models.AuthData(email = "email") whenever(oauthAuthDataBuilder.build("oauth")).thenReturn(authData) - val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login() + val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, sessionDataStore).login() assertEquals(authData, result) } @@ -231,18 +230,18 @@ class MicrogLoginManagerTest { putString(AccountManager.KEY_AUTHTOKEN, "token123") })) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() - whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("ya29.old")) - whenever(appLoungeDataStore.emailData).thenReturn(flowOf(account.name)) + val sessionDataStore = mock() + whenever(sessionDataStore.oauthToken).thenReturn(flowOf("ya29.old")) + whenever(sessionDataStore.emailData).thenReturn(flowOf(account.name)) val authData = com.aurora.gplayapi.data.models.AuthData(email = account.name) whenever(oauthAuthDataBuilder.build("token123")).thenReturn(authData) - val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login() + val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, sessionDataStore).login() assertEquals(authData, result) org.mockito.kotlin.verify(accountManager).invalidateAuthToken(account.type, "ya29.old") - org.mockito.kotlin.verify(appLoungeDataStore).saveGoogleLogin(account.name, "token123") - org.mockito.kotlin.verify(appLoungeDataStore).saveAasToken("") + org.mockito.kotlin.verify(sessionDataStore).saveGoogleLogin(account.name, "token123") + org.mockito.kotlin.verify(sessionDataStore).saveAasToken("") } @Test @@ -251,12 +250,12 @@ class MicrogLoginManagerTest { whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE))) .thenReturn(emptyArray()) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() - whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("")) + val sessionDataStore = mock() + whenever(sessionDataStore.oauthToken).thenReturn(flowOf("")) val exception = assertThrows(IllegalStateException::class.java) { runBlocking { - buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login() + buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, sessionDataStore).login() } } @@ -285,13 +284,13 @@ class MicrogLoginManagerTest { ) ).thenReturn(ImmediateAccountManagerFuture(resultBundle)) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() - whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("ya29.old")) - whenever(appLoungeDataStore.emailData).thenReturn(flowOf(account.name)) + val sessionDataStore = mock() + whenever(sessionDataStore.oauthToken).thenReturn(flowOf("ya29.old")) + whenever(sessionDataStore.emailData).thenReturn(flowOf(account.name)) val exception = assertThrows(IllegalStateException::class.java) { runBlocking { - buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login() + buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, sessionDataStore).login() } } @@ -301,9 +300,9 @@ class MicrogLoginManagerTest { private fun buildMicrogLoginManager( accountManager: AccountManager, oauthAuthDataBuilder: OauthAuthDataBuilder = mock(), - appLoungeDataStore: AppLoungeDataStore = mock() + sessionDataStore: SessionDataStore = mock() ): MicrogLoginManager { - return MicrogLoginManager(context, accountManager, oauthAuthDataBuilder, appLoungeDataStore) + return MicrogLoginManager(context, accountManager, oauthAuthDataBuilder, sessionDataStore) } private class ImmediateAccountManagerFuture( diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt index 0a0d632174153f1896fd70350340fccfa93bdee0..b7776e3f164aa507aecf8b792d7b0d51be7e36ee 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.helpers.AuthHelper import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import io.mockk.every @@ -29,9 +29,9 @@ class OauthAuthDataBuilderTest { @Test fun build_usesEmailAndOauthToken() = runTest { - val appLoungeDataStore = mockk() + val sessionRepository = mockk() val properties = Properties() - every { appLoungeDataStore.emailData } returns flowOf("user@gmail.com") + every { sessionRepository.emailData } returns flowOf("user@gmail.com") val expected = AuthData(email = "user@gmail.com") every { AuthHelper.build( @@ -43,7 +43,7 @@ class OauthAuthDataBuilderTest { ) } returns expected - val builder = OauthAuthDataBuilder(appLoungeDataStore, properties) + val builder = OauthAuthDataBuilder(sessionRepository, properties) val result = builder.build("oauth-token") assertThat(result).isEqualTo(expected) diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverterTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverterTest.kt index afdd8fc1f2e0674d1284ecf3876a0f90720f8303..7805466125934cec054b95a6ee956208b70411b6 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverterTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthToAasTokenConverterTest.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.PlayResponse import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.playstore.utils.AC2DMTask import io.mockk.coEvery diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt index 8b9c85966a9a8d12cd37ea9bcff38835aefd23e0..abaa0595c5da77aa86680fa30c41c308dc3cc058 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt @@ -5,11 +5,11 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.UserProfile import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.microg.MicrogLoginManager -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository import kotlinx.coroutines.flow.flowOf import io.mockk.coEvery import io.mockk.every @@ -22,8 +22,8 @@ class PlayStoreAuthenticatorTest { @Test fun login_returnsSavedAuthWhenAvailable() = runTest { val context = mockk(relaxed = true) - val appLoungeDataStore = mockk() - val appLoungePreference = mockk(relaxed = true) + val sessionRepository = mockk() + val appPreferencesRepository = mockk(relaxed = true) val authDataCache = mockk() val googleLoginManager = mockk(relaxed = true) val microgLoginManager = mockk(relaxed = true) @@ -32,14 +32,14 @@ class PlayStoreAuthenticatorTest { val userProfileFetcher = mockk(relaxed = true) val savedAuth = AuthData(email = "saved@example.com") - every { appLoungeDataStore.getUser() } returns User.ANONYMOUS + every { sessionRepository.getUser() } returns User.ANONYMOUS every { authDataCache.getSavedAuthData() } returns savedAuth every { authDataCache.formatAuthData(savedAuth) } returns savedAuth val authenticator = PlayStoreAuthenticator( context = context, - appLoungeDataStore = appLoungeDataStore, - appLoungePreference = appLoungePreference, + sessionRepository = sessionRepository, + appPreferencesRepository = appPreferencesRepository, authDataCache = authDataCache, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, @@ -60,8 +60,8 @@ class PlayStoreAuthenticatorTest { @Test fun login_usesAnonymousLoginWhenNoSavedAuth() = runTest { val context = mockk(relaxed = true) - val appLoungeDataStore = mockk() - val appLoungePreference = mockk(relaxed = true) + val sessionRepository = mockk() + val appPreferencesRepository = mockk(relaxed = true) val authDataCache = mockk() val googleLoginManager = mockk(relaxed = true) val microgLoginManager = mockk(relaxed = true) @@ -70,15 +70,15 @@ class PlayStoreAuthenticatorTest { val userProfileFetcher = mockk(relaxed = true) val authData = AuthData(email = "anon@example.com", isAnonymous = true) - every { appLoungeDataStore.getUser() } returns User.ANONYMOUS + every { sessionRepository.getUser() } returns User.ANONYMOUS every { authDataCache.getSavedAuthData() } returns null coEvery { anonymousLoginManager.login() } returns authData every { authDataCache.formatAuthData(any()) } answers { firstArg() } val authenticator = PlayStoreAuthenticator( context = context, - appLoungeDataStore = appLoungeDataStore, - appLoungePreference = appLoungePreference, + sessionRepository = sessionRepository, + appPreferencesRepository = appPreferencesRepository, authDataCache = authDataCache, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, @@ -99,8 +99,8 @@ class PlayStoreAuthenticatorTest { @Test fun login_fetchesUserProfileForGoogle() = runTest { val context = mockk(relaxed = true) - val appLoungeDataStore = mockk() - val appLoungePreference = mockk(relaxed = true) + val sessionRepository = mockk() + val appPreferencesRepository = mockk(relaxed = true) val authDataCache = mockk() val googleLoginManager = mockk() val microgLoginManager = mockk(relaxed = true) @@ -110,9 +110,9 @@ class PlayStoreAuthenticatorTest { val authData = AuthData(email = "user@gmail.com") val profile = mockk() - every { appLoungeDataStore.getUser() } returns User.GOOGLE - every { appLoungeDataStore.playStoreAuthSource } returns flowOf("GOOGLE") - every { appLoungeDataStore.aasToken } returns flowOf("aas-token") + every { sessionRepository.getUser() } returns User.GOOGLE + every { sessionRepository.playStoreAuthSource } returns flowOf("GOOGLE") + every { sessionRepository.aasToken } returns flowOf("aas-token") every { authDataCache.getSavedAuthData() } returns null coEvery { googleLoginManager.login() } returns authData every { authDataCache.formatAuthData(any()) } answers { firstArg() } @@ -120,8 +120,8 @@ class PlayStoreAuthenticatorTest { val authenticator = PlayStoreAuthenticator( context = context, - appLoungeDataStore = appLoungeDataStore, - appLoungePreference = appLoungePreference, + sessionRepository = sessionRepository, + appPreferencesRepository = appPreferencesRepository, authDataCache = authDataCache, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreSessionTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreSessionTest.kt index 79107a36e4dc57ff6fcfa401aaf84511e018923d..472587fe8bde5e4391d0329d6668b22de2aa5e00 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreSessionTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreSessionTest.kt @@ -3,7 +3,7 @@ package foundation.e.apps.data.login.playstore import com.aurora.gplayapi.data.models.AuthData import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.api.LoginManager import foundation.e.apps.data.login.exceptions.GPlayLoginException import io.mockk.coEvery diff --git a/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt b/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt index 5498988c783c9949856c7a7805a94620e9af29b8..3b5f8f7ec6c899bdf00082120cf8631cc7271295 100644 --- a/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt @@ -23,16 +23,18 @@ import androidx.test.core.app.ApplicationProvider import com.aurora.gplayapi.data.models.AuthData import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreAuthResult import foundation.e.apps.data.login.core.StoreAuthenticator import foundation.e.apps.data.login.core.StoreType -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import org.junit.Test @@ -113,9 +115,13 @@ class AuthenticatorRepositoryTest { assertThat(appLoungeDataStore.getAuthData()).isEqualTo(authData) } - private fun createDataStore(): AppLoungeDataStore { + private fun createDataStore(): SessionDataStore { val context = ApplicationProvider.getApplicationContext() val json = Json { ignoreUnknownKeys = true } - return AppLoungeDataStore(context, json) + return SessionDataStore( + context, + json, + CoroutineScope(StandardTestDispatcher()) + ) } } diff --git a/app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt b/app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt index 11113d0921653cd55f193cc348e27ac1b2486bcb..9450b82dc2371d8049571d1ac2ab2a14efadbd95 100644 --- a/app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt +++ b/app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt @@ -1,7 +1,6 @@ package foundation.e.apps.data.playstore import android.content.Context -import android.os.Build import androidx.test.core.app.ApplicationProvider import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.App @@ -44,7 +43,7 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(sdk = [Build.VERSION_CODES.N]) +@Config(sdk = [30]) class PlayStoreRepositoryTest { private val context = spyk(ApplicationProvider.getApplicationContext()) diff --git a/app/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt b/app/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt deleted file mode 100644 index 2bfc46ecfd2e5168bc3e68deca12c892efb182c1..0000000000000000000000000000000000000000 --- a/app/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package foundation.e.apps.data.preference - -import android.content.Context -import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.state.LoginState -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.Json -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment - -@RunWith(RobolectricTestRunner::class) -class AppLoungeDataStoreTest { - - private lateinit var dataStore: AppLoungeDataStore - - @Before - fun setUp() { - val context: Context = RuntimeEnvironment.getApplication() - dataStore = AppLoungeDataStore(context, Json { ignoreUnknownKeys = true }) - runBlocking { - dataStore.destroyCredentials() - dataStore.saveUserType(null) - } - } - - @Test - fun getUserDefaultsToNoGoogle() { - assertThat(dataStore.getUser()).isEqualTo(User.NO_GOOGLE) - } - - @Test - fun loginStateDefaultsToUnavailable() { - assertThat(dataStore.getLoginState()).isEqualTo(LoginState.UNAVAILABLE) - } - - @Test - fun saveUserTypePersistsValue() = runTest { - dataStore.saveUserType(User.ANONYMOUS) - - assertThat(dataStore.getUser()).isEqualTo(User.ANONYMOUS) - } - - @Test - fun saveAasTokenStoresInFlow() = runTest { - dataStore.saveAasToken("token-123") - - assertThat(dataStore.aasToken.first()).isEqualTo("token-123") - dataStore.destroyCredentials() - assertThat(dataStore.aasToken.first()).isEqualTo("") - } - - @Test - fun saveTOCStatusStoresVersion() = runTest { - dataStore.saveTOCStatus(true, "v2") - - assertThat(dataStore.tocStatus.first()).isTrue() - assertThat(dataStore.tosVersion.first()).isEqualTo("v2") - } -} diff --git a/app/src/test/java/foundation/e/apps/domain/login/FetchMicrogAccountUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/FetchMicrogAccountUseCaseTest.kt index 40ed5ae7f6f469e7734400a4430a41f8422d94ca..f8ff610622d318ce207ef8e6fb1537d1332f8b67 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/FetchMicrogAccountUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/FetchMicrogAccountUseCaseTest.kt @@ -8,7 +8,7 @@ import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.login.microg.MicrogCertUtil import foundation.e.apps.data.login.microg.MicrogLoginManager import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith @@ -46,12 +46,12 @@ class FetchMicrogAccountUseCaseTest { putString(AccountManager.KEY_AUTHTOKEN, "token123") })) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val microgAccountManager = MicrogLoginManager( context, accountManager, oauthAuthDataBuilder, - appLoungeDataStore + sessionDataStore ) val useCase = FetchMicrogAccountUseCase(microgAccountManager) diff --git a/app/src/test/java/foundation/e/apps/domain/login/HasMicrogAccountUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/HasMicrogAccountUseCaseTest.kt index 782fad6ae7c2af4b8485cc3abd8b9394688282f5..442bb7868153607202909df7f67adc813749f488 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/HasMicrogAccountUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/HasMicrogAccountUseCaseTest.kt @@ -7,7 +7,7 @@ import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.login.microg.MicrogCertUtil import foundation.e.apps.data.login.microg.MicrogLoginManager import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.eq @@ -28,12 +28,12 @@ class HasMicrogAccountUseCaseTest { whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE))) .thenReturn(arrayOf(Account("user@gmail.com", MicrogCertUtil.GOOGLE_ACCOUNT_TYPE))) val oauthAuthDataBuilder = mock() - val appLoungeDataStore = mock() + val sessionDataStore = mock() val microgAccountManager = MicrogLoginManager( context, accountManager, oauthAuthDataBuilder, - appLoungeDataStore + sessionDataStore ) val useCase = HasMicrogAccountUseCase(microgAccountManager) diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt index bc0ad1c5673487863de8bb23ad6049dfd9187c20..5f6c07e4982133e46ac35c3df948ca74ecaf9e2a 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt @@ -2,7 +2,7 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.state.LoginCoordinator import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt index 221272f1d5cc4f22ad98e65078821560ff0e9359..114f1595880fae9814204be179d785b467c0d48a 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt @@ -2,8 +2,8 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.model.PlayStoreAuthSource import foundation.e.apps.data.login.state.LoginCoordinator import io.mockk.coEvery import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt index f437f810b39a3172555904e56bd5ca75cda38851..72c8c95eb8c659a63c0579806b63966f0143950c 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt @@ -3,9 +3,9 @@ package foundation.e.apps.domain.login import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.microg.MicrogAccount -import foundation.e.apps.data.login.playstore.PlayStoreAuthSource +import foundation.e.apps.domain.model.PlayStoreAuthSource import foundation.e.apps.data.login.repository.AuthenticatorRepository import foundation.e.apps.data.login.state.LoginCoordinator import io.mockk.coVerify diff --git a/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt index 3b8d567c1db5d50212e0be805a3595d13c8dbe62..e0857ecbb1778ad9a99662344ddbce9714685ccc 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt @@ -2,7 +2,7 @@ package foundation.e.apps.domain.login import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.exceptions.GPlayValidationException import foundation.e.apps.data.ecloud.EcloudRepository diff --git a/app/src/test/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCaseTest.kt index c5c2d1cea06bac65ebbe65e5bd497461fb8bd535..c1a63f402f11c6324dfe6f640ace2b269bc59e6b 100644 --- a/app/src/test/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/search/FetchSearchSuggestionsUseCaseTest.kt @@ -19,8 +19,8 @@ package foundation.e.apps.domain.search import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.search.SuggestionSource +import foundation.e.apps.domain.preferences.AppPreferencesRepository import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -34,19 +34,19 @@ import org.junit.Test class FetchSearchSuggestionsUseCaseTest { private lateinit var suggestionSource: SuggestionSource - private lateinit var appLoungePreference: AppLoungePreference + private lateinit var appPreferencesRepository: AppPreferencesRepository private lateinit var useCase: FetchSearchSuggestionsUseCase @Before fun setUp() { suggestionSource = mockk() - appLoungePreference = mockk() - useCase = FetchSearchSuggestionsUseCase(suggestionSource, appLoungePreference) + appPreferencesRepository = mockk() + useCase = FetchSearchSuggestionsUseCase(suggestionSource, appPreferencesRepository) } @Test fun `blank query yields empty suggestions`() = runTest { - every { appLoungePreference.isPlayStoreSelected() } returns true + every { appPreferencesRepository.isPlayStoreSelected() } returns true val result = useCase(" ") @@ -56,7 +56,7 @@ class FetchSearchSuggestionsUseCaseTest { @Test fun `play store disabled yields empty suggestions`() = runTest { - every { appLoungePreference.isPlayStoreSelected() } returns false + every { appPreferencesRepository.isPlayStoreSelected() } returns false val result = useCase("notes") @@ -66,7 +66,7 @@ class FetchSearchSuggestionsUseCaseTest { @Test fun `eligible query returns suggestion results`() = runTest { - every { appLoungePreference.isPlayStoreSelected() } returns true + every { appPreferencesRepository.isPlayStoreSelected() } returns true coEvery { suggestionSource.suggest("notes") } returns listOf("notes app") val result = useCase("notes") diff --git a/app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt b/app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt index 38ef3b5a8082c78a577796fa5a7cd8a12b0bc51c..741f9fd11677dc79560e1ece541f203c98559e96 100644 --- a/app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt +++ b/app/src/test/java/foundation/e/apps/fused/SearchRepositoryImplTest.kt @@ -36,7 +36,7 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.data.playstore.PlayStoreRepository -import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.SessionDataStore import foundation.e.apps.data.install.pkg.AppLoungePackageManager import foundation.e.apps.data.install.pkg.PwaManager import foundation.e.apps.util.MainCoroutineRule @@ -94,7 +94,7 @@ class SearchRepositoryImplTest { private lateinit var visibilityFetcher: AppVisibilityResolver @Mock - private lateinit var appLoungeDataStore: AppLoungeDataStore + private lateinit var sessionDataStore: SessionDataStore private lateinit var appsApi: AppsApi @@ -108,7 +108,7 @@ class SearchRepositoryImplTest { fun setup() { MockitoAnnotations.openMocks(this) formatterMocked = Mockito.mockStatic(Formatter::class.java) - preferenceManagerModule = FakeAppLoungePreference(context, appLoungeDataStore) + preferenceManagerModule = FakeAppLoungePreference() stores = Stores( playStoreRepository, cleanApkAppsRepository, diff --git a/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt b/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt index 9cf3b5152a49bab5afbe2571b00a998e250925e1..a7aad096a69c79c78339ee9ad549cf38138a0ef3 100644 --- a/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt +++ b/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt @@ -65,10 +65,9 @@ class AppManagerWrapperTest { @Before fun setup() { MockitoAnnotations.openMocks(this) - InstallWorkManager.context = application appInstallDAO = FakeAppInstallDAO() fakeAppManager = FakeAppManager(appInstallDAO) - appManagerWrapper = AppManagerWrapper(fakeAppManager, fdroidRepository) + appManagerWrapper = AppManagerWrapper(application, fakeAppManager, fdroidRepository) } @Test @@ -82,7 +81,7 @@ class AppManagerWrapperTest { private fun initTest(hasAnyExistingWork: Boolean = false): AppInstall { mockkObject(InstallWorkManager) - every { InstallWorkManager.checkWorkIsAlreadyAvailable(any()) } returns hasAnyExistingWork + every { InstallWorkManager.checkWorkIsAlreadyAvailable(any(), any()) } returns hasAnyExistingWork return createFusedDownload() } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index ade2dc588663aea237a76593de4bf64f3c89f43b..a492a7377fbb7636b6d15bd605540bf2ca091bff 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -24,21 +24,22 @@ import android.net.Network import android.net.NetworkCapabilities import androidx.arch.core.executor.testing.InstantTaskExecutorRule import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.fdroid.FDroidRepository +import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.AppInstallRepository -import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.AppManager +import foundation.e.apps.data.install.AppManagerWrapper import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.domain.ValidateAppAgeLimitUseCase -import foundation.e.apps.domain.model.ContentRatingValidity -import foundation.e.apps.data.install.AppInstallComponents import foundation.e.apps.data.install.notification.StorageNotificationManager import foundation.e.apps.data.install.workmanager.AppInstallProcessor +import foundation.e.apps.data.preference.PlayStoreAuthStore import foundation.e.apps.data.system.StorageComputer +import foundation.e.apps.domain.ValidateAppAgeLimitUseCase +import foundation.e.apps.domain.model.ContentRatingValidity +import foundation.e.apps.domain.preferences.SessionRepository import foundation.e.apps.util.MainCoroutineRule import io.mockk.coEvery import io.mockk.coVerify @@ -84,7 +85,10 @@ class AppInstallProcessorTest { private lateinit var context: Context @Mock - private lateinit var appLoungeDataStore: AppLoungeDataStore + private lateinit var sessionRepository: SessionRepository + + @Mock + private lateinit var playStoreAuthStore: PlayStoreAuthStore @Mock private lateinit var applicationRepository: ApplicationRepository @@ -103,7 +107,7 @@ class AppInstallProcessorTest { fakeFusedDownloadDAO = FakeAppInstallDAO() appInstallRepository = AppInstallRepository(fakeFusedDownloadDAO) fakeFusedManagerRepository = - FakeAppManagerWrapper(fakeFusedDownloadDAO, fakeFusedManager, fakeFDroidRepository) + FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) val appInstallComponents = AppInstallComponents(appInstallRepository, fakeFusedManagerRepository) @@ -112,7 +116,8 @@ class AppInstallProcessorTest { appInstallComponents, applicationRepository, validateAppAgeRatingUseCase, - appLoungeDataStore, + sessionRepository, + playStoreAuthStore, storageNotificationManager ) } @@ -397,7 +402,8 @@ class AppInstallProcessorTest { appInstallComponents, applicationRepository, validateAppAgeRatingUseCase, - appLoungeDataStore, + sessionRepository, + playStoreAuthStore, storageNotificationManager ) } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt index 9a52b136578543c9c3ff96494cf5fcc33cf0e765..ff830994951ccc457f2bdd37f7028b6eab3f44f6 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeAppManagerWrapper.kt @@ -18,6 +18,7 @@ package foundation.e.apps.installProcessor +import android.content.Context import foundation.e.apps.data.enums.Status import foundation.e.apps.data.fdroid.FDroidRepository import foundation.e.apps.data.install.AppManagerWrapper @@ -27,9 +28,10 @@ import kotlinx.coroutines.delay class FakeAppManagerWrapper( private val fusedDownloadDAO: FakeAppInstallDAO, + context: Context, fusedManager: AppManager, fDroidRepository: FDroidRepository, -) : AppManagerWrapper(fusedManager, fDroidRepository) { +) : AppManagerWrapper(context, fusedManager, fDroidRepository) { var isAppInstalled = false var installationStatus = Status.INSTALLED var willDownloadFail = false diff --git a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt index e9dc6c757aab328239c5bae805890d718d6300ae..d5d338eca8703f3caa877f407dcb790ea589e5a3 100644 --- a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt +++ b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt @@ -25,7 +25,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User +import foundation.e.apps.domain.model.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.microg.MicrogAccount import foundation.e.apps.data.login.microg.MicrogLoginManager diff --git a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapperTest.kt b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapperTest.kt index 9f3c7c5f1b3938330f4fc58fdffbfb0106a954dd..506434e0743c871b203fa0dfb2a1579076f576ae 100644 --- a/app/src/test/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapperTest.kt +++ b/app/src/test/java/foundation/e/apps/ui/compose/state/InstallButtonStateMapperTest.kt @@ -22,7 +22,7 @@ import foundation.e.apps.R import foundation.e.apps.data.application.data.Application 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.domain.model.User import foundation.e.apps.data.install.pkg.InstallerService import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse diff --git a/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt b/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt index 31b27e398fcff5a0da23b1262d3ecdcb101801f8..8b2b48eabf3f43044c59d08581a0fd384e39e693 100644 --- a/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt +++ b/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt @@ -30,11 +30,11 @@ import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.Status import foundation.e.apps.data.playstore.PlayStoreRepository -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.search.CleanApkSearchParams import foundation.e.apps.data.search.FakeSuggestionSource import foundation.e.apps.data.search.PlayStorePagingRepository import foundation.e.apps.data.search.SearchPagingRepository +import foundation.e.apps.domain.preferences.AppPreferencesRepository import foundation.e.apps.domain.search.CleanApkSearchPagingUseCase import foundation.e.apps.domain.search.FetchSearchSuggestionsUseCase import foundation.e.apps.domain.search.PlayStoreAppMapper @@ -72,7 +72,7 @@ class SearchViewModelV2Test { val mainCoroutineRule = MainCoroutineRule() private lateinit var suggestionSource: FakeSuggestionSource - private lateinit var preference: AppLoungePreference + private lateinit var preference: AppPreferencesRepository private lateinit var searchPagingRepository: SearchPagingRepository private lateinit var playStorePagingRepository: PlayStorePagingRepository private lateinit var playStoreAppMapper: PlayStoreAppMapper @@ -810,12 +810,15 @@ class SearchViewModelV2Test { private fun buildViewModel() { stores = buildStores() - fetchSearchSuggestionsUseCase = FetchSearchSuggestionsUseCase(suggestionSource, preference) + fetchSearchSuggestionsUseCase = FetchSearchSuggestionsUseCase( + suggestionSource, + preference + ) prepareSearchSubmissionUseCase = PrepareSearchSubmissionUseCase(stores) viewModel = SearchViewModelV2( - preference, cleanApkSearchPagingUseCase, playStoreSearchPagingUseCase, + preference, fetchSearchSuggestionsUseCase, prepareSearchSubmissionUseCase, stores, 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 index eb7f0fd3408c2ee327f0f72bae84528c505592a3..aad021a41a0ae58660ec0cb597fd7c3d721eede3 100644 --- a/app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt +++ b/app/src/test/java/foundation/e/apps/ui/updates/UpdatesViewModelTest.kt @@ -6,13 +6,14 @@ 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.domain.model.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.domain.preferences.AppPreferencesRepository +import foundation.e.apps.domain.preferences.SessionRepository +import foundation.e.apps.domain.model.LoginState import foundation.e.apps.util.getOrAwaitValue import kotlinx.coroutines.runBlocking import org.junit.Before @@ -60,9 +61,9 @@ class UpdatesViewModelTest { private val applicationRepository by lazy { mock() } private val updatesManagerImpl by lazy { mock() } - private val appLoungeDataStore by lazy { mock() } + private val sessionRepository by lazy { mock() } private val stores by lazy { mock() } - private val appLoungePreference by lazy { mock() } + private val appPreferencesRepository by lazy { mock() } private lateinit var updatesManagerRepository: UpdatesManagerRepository private lateinit var updatesViewModel: UpdatesViewModel @@ -80,9 +81,11 @@ class UpdatesViewModelTest { whenever(updatesManagerImpl.getApplicationCategoryPreference()) .thenReturn(enabledStore.map { it.name }) - whenever(appLoungeDataStore.getUser()) + whenever(sessionRepository.awaitUser()) .thenReturn(User.GOOGLE) - whenever(appLoungePreference.isPlayStoreSelected()) + whenever(sessionRepository.awaitLoginState()) + .thenReturn(LoginState.AVAILABLE) + whenever(appPreferencesRepository.isPlayStoreSelected()) .thenReturn(true) updatesManagerRepository = UpdatesManagerRepository(updatesManagerImpl) @@ -90,9 +93,9 @@ class UpdatesViewModelTest { updatesViewModel = UpdatesViewModel( updatesManagerRepository = updatesManagerRepository, applicationRepository = applicationRepository, - appLoungeDataStore = appLoungeDataStore, + sessionRepository = sessionRepository, stores = stores, - preference = appLoungePreference + appPreferencesRepository = appPreferencesRepository ) } @@ -128,9 +131,11 @@ class UpdatesViewModelTest { @Test fun `insure cleanApk error is shown when gPlay is disabled`() = runBlocking { - whenever(appLoungeDataStore.getUser()) + whenever(sessionRepository.awaitUser()) .thenReturn(User.NO_GOOGLE) - whenever(appLoungePreference.isPlayStoreSelected()) + whenever(sessionRepository.awaitLoginState()) + .thenReturn(LoginState.UNAVAILABLE) + whenever(appPreferencesRepository.isPlayStoreSelected()) .thenReturn(false) whenever(updatesManagerImpl.getUpdatesOSS()) @@ -149,8 +154,10 @@ class UpdatesViewModelTest { @Test fun `insure oss update are shown when gPlay is disabled`() = runBlocking { - whenever(appLoungeDataStore.getUser()) + whenever(sessionRepository.awaitUser()) .thenReturn(User.NO_GOOGLE) + whenever(sessionRepository.awaitLoginState()) + .thenReturn(LoginState.UNAVAILABLE) whenever(updatesManagerImpl.getUpdatesOSS()) .thenReturn(Pair(ossUpdates, ResultStatus.OK)) @@ -165,4 +172,4 @@ class UpdatesViewModelTest { assert(updates.containsAll(ossUpdates)) } -} \ No newline at end of file +} diff --git a/auth-data-lib/build.gradle b/auth-data-lib/build.gradle index 56bd124c187b7d6f21c4d77850b46e9371c7af4c..58624b38d5c8dc6c3acb63d4a42f3325b74af0d4 100644 --- a/auth-data-lib/build.gradle +++ b/auth-data-lib/build.gradle @@ -4,6 +4,10 @@ plugins { id 'maven-publish' } +kotlin { + jvmToolchain(21) +} + java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 @@ -45,4 +49,4 @@ publishing { } } } -} \ No newline at end of file +} diff --git a/data/build.gradle b/data/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..57644ef38bd0bc7a49a02fa7c73a4fa15886fbbe --- /dev/null +++ b/data/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' +} + +android { + compileSdk = 36 + + defaultConfig { + minSdk = 30 + } + + testOptions { + unitTests { + includeAndroidResources = true + all { + systemProperty 'robolectric.usePreinstrumentedJars', 'false' + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + kotlinOptions { + jvmTarget = '21' + } + + namespace = 'foundation.e.apps.data' +} + +dependencies { + implementation project(':domain') + + implementation libs.preference.ktx + implementation libs.datastore.preferences + implementation libs.kotlinx.coroutines.core + implementation libs.kotlinx.serialization.json + implementation libs.gplayapi + implementation libs.hilt.android + + testImplementation libs.junit + testImplementation libs.truth + testImplementation libs.core + testImplementation libs.mockk + testImplementation libs.robolectric + testImplementation libs.kotlinx.coroutines.test + testImplementation libs.guava +} diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..8072ee00dbf16d9161b7464ef3d2194a7d659bcc --- /dev/null +++ b/data/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/java/foundation/e/apps/data/di/qualifiers/IoCoroutineScope.kt b/data/src/main/java/foundation/e/apps/data/di/qualifiers/IoCoroutineScope.kt similarity index 100% rename from app/src/main/java/foundation/e/apps/data/di/qualifiers/IoCoroutineScope.kt rename to data/src/main/java/foundation/e/apps/data/di/qualifiers/IoCoroutineScope.kt diff --git a/data/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt b/data/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3609a4e0eebfbfcf289dd459e22e96c6913e04c --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 e Foundation + * Copyright (C) 2021-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.data.preference + +import android.content.SharedPreferences +import androidx.core.content.edit +import foundation.e.apps.domain.preferences.AppPreferencesRepository +import javax.inject.Inject +import javax.inject.Singleton + +internal const val PREFERENCE_SHOW_FOSS = "showFOSSApplications" +internal const val PREFERENCE_SHOW_PWA = "showPWAApplications" +internal const val PREFERENCE_SHOW_GPLAY = "showAllApplications" + +private const val UPDATE_APPS_FROM_OTHER_STORES_KEY = "updateAppsFromOtherStores" +private const val UPDATE_NOTIFY_KEY = "updateNotify" +private const val AUTO_INSTALL_ENABLED_KEY = "updateInstallAuto" +private const val ONLY_UNMETERED_NETWORK_KEY = "updateUnmeteredOnly" +private const val DEFAULT_APPLICATION_SOURCE_SELECTED = true + +@Suppress("TooManyFunctions") +@Singleton +class AppLoungePreference @Inject constructor( + private val sharedPreferences: SharedPreferences, +) : AppPreferencesRepository { + + override fun preferredApplicationType(): String { + val showFOSSApplications = + sharedPreferences.getBoolean(PREFERENCE_SHOW_FOSS, DEFAULT_APPLICATION_SOURCE_SELECTED) + val showPWAApplications = + sharedPreferences.getBoolean(PREFERENCE_SHOW_PWA, DEFAULT_APPLICATION_SOURCE_SELECTED) + + return when { + showFOSSApplications && !showPWAApplications -> "open" + showPWAApplications && !showFOSSApplications -> "pwa" + else -> "any" + } + } + + override fun isOpenSourceSelected(): Boolean = + sharedPreferences.getBoolean(PREFERENCE_SHOW_FOSS, DEFAULT_APPLICATION_SOURCE_SELECTED) + + override fun isPWASelected(): Boolean = + sharedPreferences.getBoolean(PREFERENCE_SHOW_PWA, DEFAULT_APPLICATION_SOURCE_SELECTED) + + override fun isPlayStoreSelected(): Boolean = + sharedPreferences.getBoolean(PREFERENCE_SHOW_GPLAY, DEFAULT_APPLICATION_SOURCE_SELECTED) + + override fun shouldShowUpdateNotification(): Boolean = + sharedPreferences.getBoolean(UPDATE_NOTIFY_KEY, true) + + override fun isAutomaticInstallEnabled(): Boolean = + sharedPreferences.getBoolean(AUTO_INSTALL_ENABLED_KEY, true) + + override fun disablePlayStore() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_GPLAY, false) } + + override fun disableOpenSource() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_FOSS, false) } + + override fun disablePwa() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_PWA, false) } + + override fun enablePlayStore() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_GPLAY, true) } + + override fun enableOpenSource() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_FOSS, true) } + + override fun enablePwa() = + sharedPreferences.edit { putBoolean(PREFERENCE_SHOW_PWA, true) } + + override fun shouldUpdateAppsFromOtherStores(): Boolean = + sharedPreferences.getBoolean(UPDATE_APPS_FROM_OTHER_STORES_KEY, true) + + override fun isOnlyUnmeteredNetworkEnabled(): Boolean { + return sharedPreferences.getBoolean(ONLY_UNMETERED_NETWORK_KEY, true) + } +} diff --git a/data/src/main/java/foundation/e/apps/data/preference/PlayStoreAuthStore.kt b/data/src/main/java/foundation/e/apps/data/preference/PlayStoreAuthStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..f18fb4d18f81992eccf3baf1a9465981e13a1904 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/preference/PlayStoreAuthStore.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.preference + +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.domain.model.PlayStoreAuthSource +import kotlinx.coroutines.flow.StateFlow + +interface PlayStoreAuthStore { + val email: StateFlow + + suspend fun awaitAuthData(): AuthData? + suspend fun awaitEmail(): String + suspend fun awaitOauthToken(): String + suspend fun awaitAasToken(): String + suspend fun awaitPlayStoreAuthSource(): PlayStoreAuthSource? + suspend fun saveAuthData(authData: AuthData?) + suspend fun destroyCredentials() + suspend fun saveAasToken(aasToken: String) + suspend fun saveGoogleLogin(email: String, token: String) + suspend fun savePlayStoreAuthSource(authSource: PlayStoreAuthSource?) +} diff --git a/data/src/main/java/foundation/e/apps/data/preference/SessionDataStore.kt b/data/src/main/java/foundation/e/apps/data/preference/SessionDataStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..03e12e9d2734f43eb42b477b8108f05e904c9140 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/preference/SessionDataStore.kt @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2021-2025 e Foundation + * + * 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.data.preference + +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.aurora.gplayapi.data.models.AuthData +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.di.qualifiers.IoCoroutineScope +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Difference between [OAUTHTOKEN] and [AASTOKEN]: + * + * These two are used only for Google login, not for Anonymous login. + * OAuthToken is obtained from the Google Login web page, from the cookies. + * This OAuthToken is then used by AC2DMTask in GPlayAPIImpl class + * to generate AasToken. + * + * To get Google Play Store data, we need to create an AuthData instance. + * For Google user, this can only be done using AasToken, not OAuthToken. + * + * Very important: AasToken can be generated only ONCE from one OAuthToken. + * We cannot get AasToken again from the same OAuthToken. Thus it is + * important to safely store the AasToken to regenerate AuthData if needed. + * If AasToken is not stored, user has to logout and login again. + */ +@Singleton +class SessionDataStore @Inject constructor( + @ApplicationContext + private val context: Context, + private val json: Json, + @IoCoroutineScope + private val coroutineScope: CoroutineScope, +) : SessionRepository, PlayStoreAuthStore { + + companion object { + private const val preferenceDataStoreName = "Settings" + val Context.dataStore by preferencesDataStore(preferenceDataStoreName) + } + + private val AUTHDATA = stringPreferencesKey("authData") + private val EMAIL = stringPreferencesKey("email") + private val OAUTHTOKEN = stringPreferencesKey("oauthtoken") + private val AASTOKEN = stringPreferencesKey("aasToken") + private val USERTYPE = stringPreferencesKey("userType") + private val PLAY_STORE_AUTH_SOURCE = stringPreferencesKey("playStoreAuthSource") + private val TOCSTATUS = booleanPreferencesKey("tocStatus") + private val TOSVERSION = stringPreferencesKey("tosversion") + + private val preferences = context.dataStore.data + + private val authDataJsonFlow = preferences.map { it[AUTHDATA] ?: "" } + private val emailDataFlow = preferences.map { it[EMAIL] ?: "" } + private val oauthTokenFlow = preferences.map { it[OAUTHTOKEN] ?: "" } + private val aasTokenFlow = preferences.map { it[AASTOKEN] ?: "" } + private val userTypeFlow = preferences.map { it[USERTYPE] ?: "" } + private val playStoreAuthSourceRawFlow = preferences.map { it[PLAY_STORE_AUTH_SOURCE] ?: "" } + private val tocStatusFlow = preferences.map { it[TOCSTATUS] ?: false } + private val tosVersionFlow = preferences.map { it[TOSVERSION] ?: "" } + + private val authDataStateFlow = preferences.map { preferences -> + preferences[AUTHDATA] + ?.takeIf { it.isNotBlank() } + ?.let(::decodeAuthData) + }.stateIn( + coroutineScope, + SharingStarted.Eagerly, + null + ) + private val emailStateFlow = emailDataFlow.stateIn( + coroutineScope, + SharingStarted.Eagerly, + "" + ) + private val userFlow = preferences.map { preferences -> + parseUser(preferences[USERTYPE]) + }.stateIn( + coroutineScope, + SharingStarted.Eagerly, + User.NO_GOOGLE + ) + private val loginStateFlow = preferences.map { preferences -> + parseLoginState(preferences[USERTYPE]) + }.stateIn( + coroutineScope, + SharingStarted.Eagerly, + LoginState.UNAVAILABLE + ) + private val playStoreAuthSourceFlow = preferences.map { preferences -> + parseAuthSource(preferences[PLAY_STORE_AUTH_SOURCE]) + }.stateIn( + coroutineScope, + SharingStarted.Eagerly, + null + ) + + override val authData: Flow = authDataJsonFlow + override val emailData: Flow = emailDataFlow + override val oauthToken: Flow = oauthTokenFlow + override val aasToken: Flow = aasTokenFlow + override val userType: Flow = userTypeFlow + override val playStoreAuthSource: Flow = playStoreAuthSourceRawFlow + override val email: StateFlow = emailStateFlow + override val user: StateFlow = userFlow + override val loginState: StateFlow = loginStateFlow + override val tocStatus: Flow = tocStatusFlow + override val tosVersion: Flow = tosVersionFlow + + override suspend fun saveAuthData(authData: AuthData?) { + context.dataStore.edit { + if (authData == null) { + it.remove(AUTHDATA) + } else { + it[AUTHDATA] = json.encodeToString(authData) + } + } + } + + override fun getAuthData(): AuthData { + val storedAuthData = authDataJsonFlow.getSync() + return if (storedAuthData.isEmpty()) { + AuthData("", "") + } else { + decodeAuthData(storedAuthData) ?: AuthData("", "") + } + } + + override suspend fun awaitAuthData(): AuthData? = authDataStateFlow.first() + + /** + * Destroy auth credentials if they are no longer valid. + * + * Modification for issue: https://gitlab.e.foundation/e/backlog/-/issues/5168 + * Previously this method would also remove [USERTYPE]. + * To clear this value, call [saveUserType] with null. + */ + override suspend fun destroyCredentials() { + context.dataStore.edit { + it.remove(AUTHDATA) + it.remove(EMAIL) + it.remove(OAUTHTOKEN) + it.remove(AASTOKEN) + it.remove(PLAY_STORE_AUTH_SOURCE) + } + } + + override suspend fun saveTocStatus(status: Boolean, tosVersion: String) { + context.dataStore.edit { + it[TOCSTATUS] = status + it[TOSVERSION] = tosVersion + } + } + + override suspend fun saveUserType(user: User?) { + context.dataStore.edit { + if (user == null) { + it.remove(USERTYPE) + } else { + it[USERTYPE] = user.name + } + } + } + + override fun getUser(): User = runBlocking { awaitUser() } + + override fun getLoginState(): LoginState = runBlocking { awaitLoginState() } + + override fun isAnonymousUser(): Boolean = getUser() == User.ANONYMOUS + + override suspend fun awaitUser(): User = userFlow.first() + + override suspend fun awaitLoginState(): LoginState = loginStateFlow.first() + + suspend fun awaitTocStatus(): Boolean = tocStatusFlow.first() + + override suspend fun awaitTosVersion(): String = tosVersionFlow.first() + + override suspend fun saveAasToken(aasToken: String) { + context.dataStore.edit { + it[AASTOKEN] = aasToken + } + } + + override suspend fun saveGoogleLogin(email: String, token: String) { + context.dataStore.edit { + it[EMAIL] = email + it[OAUTHTOKEN] = token + } + } + + override suspend fun savePlayStoreAuthSource(authSource: PlayStoreAuthSource?) { + context.dataStore.edit { + if (authSource == null) { + it.remove(PLAY_STORE_AUTH_SOURCE) + } else { + it[PLAY_STORE_AUTH_SOURCE] = authSource.name + } + } + } + + override suspend fun awaitEmail(): String = emailDataFlow.first() + + override suspend fun awaitOauthToken(): String = oauthTokenFlow.first() + + override suspend fun awaitAasToken(): String = aasTokenFlow.first() + + override suspend fun awaitPlayStoreAuthSource(): PlayStoreAuthSource? = + playStoreAuthSourceFlow.first() + + private fun decodeAuthData(rawAuthData: String): AuthData? { + return try { + json.decodeFromString(rawAuthData) + } catch (exception: SerializationException) { + null + } + } + + private fun parseUser(userType: String?): User { + return User.entries.firstOrNull { it.name == userType } ?: User.NO_GOOGLE + } + + private fun parseLoginState(userType: String?): LoginState { + return if (User.entries.any { it.name == userType }) { + LoginState.AVAILABLE + } else { + LoginState.UNAVAILABLE + } + } + + private fun parseAuthSource(rawAuthSource: String?): PlayStoreAuthSource? { + return PlayStoreAuthSource.entries.firstOrNull { it.name == rawAuthSource } + } +} + +fun Flow.getSync(): T { + return runBlocking { + this@getSync.first() + } +} diff --git a/data/src/main/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImpl.kt b/data/src/main/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..aac8296ec0c1101356e1be538e582eb6c7d5d394 --- /dev/null +++ b/data/src/main/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImpl.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.preference.updateinterval + +import android.content.SharedPreferences +import androidx.core.content.edit +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.updateinterval.UpdatePreferencesRepository +import javax.inject.Inject +import javax.inject.Singleton + +private const val UPDATE_CHECK_INTERVALS_KEY = "updateCheckIntervals" +private const val UPDATE_CHECK_INTERVALS_ANONYMOUS_KEY = "updateCheckIntervalsAnonymous" +private const val ANONYMOUS_UPDATE_MIGRATION_COMPLETED_KEY = "anonymousUpdateMigrationCompleted" +private const val UPDATE_INTERVAL_DAILY = "24" +private const val UPDATE_INTERVAL_WEEKLY = "168" +private const val UPDATE_INTERVAL_MONTHLY = "720" + +@Singleton +class UpdatePreferencesRepositoryImpl @Inject constructor( + private val sharedPreferences: SharedPreferences, +) : UpdatePreferencesRepository { + + override fun getUpdateIntervalHours(user: User): Long { + val intervalKey = if (user == User.ANONYMOUS) { + UPDATE_CHECK_INTERVALS_ANONYMOUS_KEY + } else { + UPDATE_CHECK_INTERVALS_KEY + } + val defaultInterval = if (user == User.ANONYMOUS) { + UPDATE_INTERVAL_WEEKLY + } else { + UPDATE_INTERVAL_DAILY + } + + return sharedPreferences.getString(intervalKey, defaultInterval) + ?.toLongOrNull() + ?: defaultInterval.toLong() + } + + override fun migrateAnonymousUpdateInterval(user: User) { + if (sharedPreferences.getBoolean(ANONYMOUS_UPDATE_MIGRATION_COMPLETED_KEY, false)) { + return + } + + if (user == User.ANONYMOUS) { + val currentInterval = sharedPreferences.getString(UPDATE_CHECK_INTERVALS_KEY, null) + currentInterval?.let { interval -> + val migratedInterval = when (interval) { + UPDATE_INTERVAL_DAILY -> UPDATE_INTERVAL_WEEKLY + UPDATE_INTERVAL_WEEKLY, UPDATE_INTERVAL_MONTHLY -> interval + else -> UPDATE_INTERVAL_WEEKLY + } + sharedPreferences.edit { + putString(UPDATE_CHECK_INTERVALS_ANONYMOUS_KEY, migratedInterval) + } + } + } + + sharedPreferences.edit { + putBoolean(ANONYMOUS_UPDATE_MIGRATION_COMPLETED_KEY, true) + } + } +} diff --git a/data/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt b/data/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cd3268e8acfe360183c2fbfc8ecf6f71ffcf30c1 --- /dev/null +++ b/data/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021-2026 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.data.preference + +import android.content.Context +import com.google.common.truth.Truth.assertThat +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.User +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [30]) +class AppLoungeDataStoreTest { + + private lateinit var dataStore: SessionDataStore + + @Before + fun setUp() { + val context: Context = RuntimeEnvironment.getApplication() + dataStore = SessionDataStore( + context, + Json { ignoreUnknownKeys = true }, + CoroutineScope(UnconfinedTestDispatcher()) + ) + runTest { + dataStore.destroyCredentials() + dataStore.saveUserType(null) + } + } + + @Test + fun getUserDefaultsToNoGoogle() = runTest { + assertThat(dataStore.awaitUser()).isEqualTo(User.NO_GOOGLE) + } + + @Test + fun loginStateDefaultsToUnavailable() = runTest { + assertThat(dataStore.awaitLoginState()).isEqualTo(LoginState.UNAVAILABLE) + } + + @Test + fun saveUserTypePersistsValue() = runTest { + dataStore.saveUserType(User.ANONYMOUS) + + assertThat(dataStore.awaitUser()).isEqualTo(User.ANONYMOUS) + } + + @Test + fun saveAasTokenStoresInFlow() = runTest { + dataStore.saveAasToken("token-123") + + assertThat(dataStore.awaitAasToken()).isEqualTo("token-123") + dataStore.destroyCredentials() + assertThat(dataStore.awaitAasToken()).isEmpty() + } + + @Test + fun saveTocStatusStoresVersion() = runTest { + dataStore.saveTocStatus(true, "v2") + + assertThat(dataStore.awaitTocStatus()).isTrue() + assertThat(dataStore.awaitTosVersion()).isEqualTo("v2") + } +} diff --git a/app/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt b/data/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt similarity index 59% rename from app/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt rename to data/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt index a179a69f6d2859991f4c5e7d689ff674e7fe6c3f..d433318656e45ea3fe7fedd614e25e6bfc5ecd55 100644 --- a/app/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt +++ b/data/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt @@ -1,36 +1,47 @@ +/* + * Copyright (C) 2021-2026 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.data.preference import android.content.SharedPreferences import androidx.preference.PreferenceManager import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat -import foundation.e.apps.R -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY -import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA -import foundation.e.apps.data.enums.User -import io.mockk.every -import io.mockk.mockk import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) +@Config(sdk = [30]) class AppLoungePreferenceTest { - private val dataStore: AppLoungeDataStore = mockk(relaxed = true) private val context = ApplicationProvider.getApplicationContext() private lateinit var sharedPreferences: SharedPreferences private lateinit var preference: AppLoungePreference @Before fun setUp() { - every { dataStore.getUser() } returns User.NO_GOOGLE sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) sharedPreferences.edit().clear().commit() - preference = AppLoungePreference(context, dataStore) + preference = AppLoungePreference(sharedPreferences) } @After @@ -45,6 +56,7 @@ class AppLoungePreferenceTest { @Test fun preferredApplicationType_returnsOpenWhenFossSelected() { + sharedPreferences.edit().putBoolean(PREFERENCE_SHOW_PWA, false).commit() sharedPreferences.edit().putBoolean(PREFERENCE_SHOW_FOSS, true).commit() assertThat(preference.preferredApplicationType()).isEqualTo("open") @@ -52,6 +64,7 @@ class AppLoungePreferenceTest { @Test fun preferredApplicationType_returnsPwaWhenPwaSelected() { + sharedPreferences.edit().putBoolean(PREFERENCE_SHOW_FOSS, false).commit() sharedPreferences.edit().putBoolean(PREFERENCE_SHOW_PWA, true).commit() assertThat(preference.preferredApplicationType()).isEqualTo("pwa") @@ -67,4 +80,11 @@ class AppLoungePreferenceTest { assertThat(sharedPreferences.getBoolean(PREFERENCE_SHOW_GPLAY, false)).isTrue() } + + @Test + fun updateSettingsDefaultToEnabled() { + assertThat(preference.shouldShowUpdateNotification()).isTrue() + assertThat(preference.isAutomaticInstallEnabled()).isTrue() + assertThat(preference.isOnlyUnmeteredNetworkEnabled()).isTrue() + } } diff --git a/data/src/test/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImplTest.kt b/data/src/test/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImplTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b674c1deb6ff577fe5acd254e3ee2632371f2308 --- /dev/null +++ b/data/src/test/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImplTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.data.preference.updateinterval + +import android.content.Context +import android.os.Build +import com.google.common.truth.Truth.assertThat +import foundation.e.apps.domain.model.User +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import java.util.UUID + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.R]) +class UpdatePreferencesRepositoryImplTest { + + private val context = RuntimeEnvironment.getApplication() + + @Test + fun getUpdateIntervalHours_returnsAnonymousDefaultWhenMissing() { + val repository = createRepository() + + val interval = repository.getUpdateIntervalHours(User.ANONYMOUS) + + assertThat(interval).isEqualTo(168L) + } + + @Test + fun getUpdateIntervalHours_returnsRegularDefaultWhenMissing() { + val repository = createRepository() + + val interval = repository.getUpdateIntervalHours(User.GOOGLE) + + assertThat(interval).isEqualTo(24L) + } + + @Test + fun migrateAnonymousUpdateInterval_mapsDailyToWeekly() { + val sharedPreferences = createSharedPreferences() + sharedPreferences.edit() + .putString("updateCheckIntervals", "24") + .apply() + val repository = UpdatePreferencesRepositoryImpl(sharedPreferences) + + repository.migrateAnonymousUpdateInterval(User.ANONYMOUS) + + assertThat(sharedPreferences.getString("updateCheckIntervalsAnonymous", null)).isEqualTo("168") + assertThat(sharedPreferences.getBoolean("anonymousUpdateMigrationCompleted", false)).isTrue() + } + + @Test + fun migrateAnonymousUpdateInterval_marksMigrationCompleteForNonAnonymousUser() { + val sharedPreferences = createSharedPreferences() + sharedPreferences.edit() + .putString("updateCheckIntervals", "24") + .apply() + val repository = UpdatePreferencesRepositoryImpl(sharedPreferences) + + repository.migrateAnonymousUpdateInterval(User.GOOGLE) + + assertThat(sharedPreferences.getString("updateCheckIntervalsAnonymous", null)).isNull() + assertThat(sharedPreferences.getBoolean("anonymousUpdateMigrationCompleted", false)).isTrue() + } + + private fun createRepository(): UpdatePreferencesRepositoryImpl { + return UpdatePreferencesRepositoryImpl(createSharedPreferences()) + } + + private fun createSharedPreferences() = context.getSharedPreferences( + "UpdatePreferencesRepositoryImplTest-${UUID.randomUUID()}", + Context.MODE_PRIVATE, + ) +} diff --git a/detekt.yml b/detekt.yml index fb999e7b1c11d0a267833d87f74daa716093bba2..1956ecb93c654bac098731c6436f2424a1483511 100644 --- a/detekt.yml +++ b/detekt.yml @@ -52,6 +52,7 @@ complexity: LongParameterList: active: true + constructorThreshold: 15 ignoreAnnotated: - Composable diff --git a/domain/build.gradle b/domain/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..31d9f58920d2bbcb3c7ac436005fedefdda86b06 --- /dev/null +++ b/domain/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' +} + +android { + compileSdk = 36 + + defaultConfig { + minSdk = 30 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + kotlinOptions { + jvmTarget = '21' + } + + namespace = 'foundation.e.apps.domain' +} + +dependencies { + implementation libs.kotlinx.coroutines.core + implementation libs.gplayapi + implementation libs.javax.inject + + testImplementation libs.junit + testImplementation libs.kotlinx.coroutines.test + testImplementation libs.mockk + testImplementation libs.truth +} diff --git a/domain/src/main/AndroidManifest.xml b/domain/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..8072ee00dbf16d9161b7464ef3d2194a7d659bcc --- /dev/null +++ b/domain/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/model/LoginState.kt b/domain/src/main/kotlin/foundation/e/apps/domain/model/LoginState.kt new file mode 100644 index 0000000000000000000000000000000000000000..8b7e40d82b941307c4197a3502514b50e3dfe952 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/model/LoginState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021-2026 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.domain.model + +enum class LoginState { + AVAILABLE, + UNAVAILABLE +} diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthSource.kt b/domain/src/main/kotlin/foundation/e/apps/domain/model/PlayStoreAuthSource.kt similarity index 94% rename from app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthSource.kt rename to domain/src/main/kotlin/foundation/e/apps/domain/model/PlayStoreAuthSource.kt index 278fd80d96cc670c96d1bee9dffd79100a8f5e47..3f4da0a48de2549533230bb4765f20fa535c235f 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthSource.kt +++ b/domain/src/main/kotlin/foundation/e/apps/domain/model/PlayStoreAuthSource.kt @@ -16,7 +16,7 @@ * */ -package foundation.e.apps.data.login.playstore +package foundation.e.apps.domain.model enum class PlayStoreAuthSource { GOOGLE, diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/model/User.kt b/domain/src/main/kotlin/foundation/e/apps/domain/model/User.kt new file mode 100644 index 0000000000000000000000000000000000000000..6470bc9d2fa280923285df264750794db3f405f5 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/model/User.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021-2026 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.domain.model + +enum class User { + NO_GOOGLE, + ANONYMOUS, + GOOGLE +} diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/preferences/AppPreferencesRepository.kt b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/AppPreferencesRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..e1040a6ccb9b8dc27e4213b62b66a253443f9004 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/AppPreferencesRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021-2026 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.domain.preferences + +interface AppPreferencesRepository { + fun preferredApplicationType(): String + fun isOpenSourceSelected(): Boolean + fun isPWASelected(): Boolean + fun isPlayStoreSelected(): Boolean + fun shouldShowUpdateNotification(): Boolean + fun isAutomaticInstallEnabled(): Boolean + fun disablePlayStore() + fun disableOpenSource() + fun disablePwa() + fun enablePlayStore() + fun enableOpenSource() + fun enablePwa() + fun shouldUpdateAppsFromOtherStores(): Boolean + fun isOnlyUnmeteredNetworkEnabled(): Boolean +} diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/preferences/SessionRepository.kt b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/SessionRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..30fe01e4bf8d1e4e6c109d302dcf3a3f9938123a --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/SessionRepository.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021-2026 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.domain.preferences + +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.domain.model.LoginState +import foundation.e.apps.domain.model.PlayStoreAuthSource +import foundation.e.apps.domain.model.User +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface SessionRepository { + val authData: Flow + val emailData: Flow + val oauthToken: Flow + val aasToken: Flow + val userType: Flow + val playStoreAuthSource: Flow + val user: StateFlow + val loginState: StateFlow + val tocStatus: Flow + val tosVersion: Flow + + suspend fun saveAuthData(authData: AuthData?) + fun getAuthData(): AuthData + suspend fun destroyCredentials() + fun getUser(): User + fun getLoginState(): LoginState + fun isAnonymousUser(): Boolean + suspend fun awaitUser(): User + suspend fun awaitLoginState(): LoginState + suspend fun awaitTosVersion(): String + suspend fun saveUserType(user: User?) + suspend fun saveAasToken(aasToken: String) + suspend fun saveGoogleLogin(email: String, token: String) + suspend fun savePlayStoreAuthSource(authSource: PlayStoreAuthSource?) + suspend fun saveTocStatus(status: Boolean, tosVersion: String) +} diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCase.kt b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCase.kt new file mode 100644 index 0000000000000000000000000000000000000000..0db0f4d4e1142446e874d968bb69e14bdf6537e6 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCase.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.domain.preferences.updateinterval + +import foundation.e.apps.domain.preferences.SessionRepository +import javax.inject.Inject + +class GetUpdateIntervalUseCase @Inject constructor( + private val sessionRepository: SessionRepository, + private val updatePreferencesRepository: UpdatePreferencesRepository, +) { + suspend operator fun invoke(): Long { + return updatePreferencesRepository.getUpdateIntervalHours( + sessionRepository.awaitUser() + ) + } +} diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCase.kt b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCase.kt new file mode 100644 index 0000000000000000000000000000000000000000..ae7319234b031c925c6471fe6452cd9903765d25 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCase.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.domain.preferences.updateinterval + +import foundation.e.apps.domain.preferences.SessionRepository +import javax.inject.Inject + +class MigrateAnonymousUserUpdateIntervalUseCase @Inject constructor( + private val sessionRepository: SessionRepository, + private val updatePreferencesRepository: UpdatePreferencesRepository, +) { + suspend operator fun invoke() { + updatePreferencesRepository.migrateAnonymousUpdateInterval( + sessionRepository.awaitUser() + ) + } +} diff --git a/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/UpdatePreferencesRepository.kt b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/UpdatePreferencesRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..81eeacfebc1ef535be4be61323c649ad2ff82264 --- /dev/null +++ b/domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/UpdatePreferencesRepository.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.domain.preferences.updateinterval + +import foundation.e.apps.domain.model.User + +interface UpdatePreferencesRepository { + fun getUpdateIntervalHours(user: User): Long + fun migrateAnonymousUpdateInterval(user: User) +} diff --git a/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCaseTest.kt b/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCaseTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f9bc17b834bf22acf86ac1cde5a37e7aaac733ff --- /dev/null +++ b/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCaseTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.domain.preferences.updateinterval + +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test + +class GetUpdateIntervalUseCaseTest { + + private val sessionRepository: SessionRepository = mockk() + private val updatePreferencesRepository: UpdatePreferencesRepository = mockk() + private val useCase = GetUpdateIntervalUseCase( + sessionRepository = sessionRepository, + updatePreferencesRepository = updatePreferencesRepository, + ) + + @Test + fun invoke_returnsIntervalForCurrentUser() = runTest { + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + every { + updatePreferencesRepository.getUpdateIntervalHours(User.ANONYMOUS) + } returns 168L + + val interval = useCase() + + assertEquals(168L, interval) + coVerify(exactly = 1) { sessionRepository.awaitUser() } + verify(exactly = 1) { + updatePreferencesRepository.getUpdateIntervalHours(User.ANONYMOUS) + } + } +} diff --git a/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCaseTest.kt b/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCaseTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ca620cbbb7e83b53267c934bdd6475edc95fa3f6 --- /dev/null +++ b/domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCaseTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2026 e Foundation + * + * 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.domain.preferences.updateinterval + +import foundation.e.apps.domain.model.User +import foundation.e.apps.domain.preferences.SessionRepository +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MigrateAnonymousUserUpdateIntervalUseCaseTest { + + private val sessionRepository: SessionRepository = mockk() + private val updatePreferencesRepository: UpdatePreferencesRepository = mockk() + private val useCase = MigrateAnonymousUserUpdateIntervalUseCase( + sessionRepository = sessionRepository, + updatePreferencesRepository = updatePreferencesRepository, + ) + + @Test + fun invoke_migratesForCurrentUser() = runTest { + coEvery { sessionRepository.awaitUser() } returns User.ANONYMOUS + every { + updatePreferencesRepository.migrateAnonymousUpdateInterval(User.ANONYMOUS) + } returns Unit + + useCase() + + coVerify(exactly = 1) { sessionRepository.awaitUser() } + verify(exactly = 1) { + updatePreferencesRepository.migrateAnonymousUpdateInterval(User.ANONYMOUS) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec1b556bcb0966e2ea48c2e5f08f6718f767a079..40d40fdc77d8a888e3fe1a402e08d9d1877aa8ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,8 +16,10 @@ detektFormatting = "1.23.8" detektCompose = "0.4.23" elib = "0.0.1-alpha11" espressoCore = "3.6.1" +guava = "33.4.8-android" hiltCompiler = "1.2.0" hiltWork = "1.2.0" +javaxInject = "1" lifecycleExtensions = "1.1.1" fragmentKtx = "1.8.5" gplayapi = "3.5.8-b7878454" @@ -88,8 +90,10 @@ detekt-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "det elib = { module = "foundation.e:elib", version.ref = "elib" } ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltWork" } hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltCompiler" } +javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInject" } lifecycle-extensions = { module = "android.arch.lifecycle:extensions", version.ref = "lifecycleExtensions" } fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } gplayapi = { module = "foundation.e:gplayapi", version.ref = "gplayapi" } diff --git a/keystore/platform.keystore b/keystore/platform.keystore deleted file mode 100644 index 574b2203d6dc78b3853cf899c3c7a1f60c773507..0000000000000000000000000000000000000000 Binary files a/keystore/platform.keystore and /dev/null differ diff --git a/parental-control-data/build.gradle b/parental-control-data/build.gradle index 57eaabc4e13a6080681e693b562f5bf8f38088bb..47b2e2f8aaec61dad3b2902fab34ad659fa41866 100644 --- a/parental-control-data/build.gradle +++ b/parental-control-data/build.gradle @@ -4,6 +4,10 @@ plugins { id 'maven-publish' } +kotlin { + jvmToolchain(21) +} + java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 diff --git a/settings.gradle b/settings.gradle index 0b35903798457ed980886edba821edacc925afc5..1c8902dd320dddcd36e24459c37d59e2849f77b5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -62,5 +62,7 @@ dependencyResolutionManagement { } rootProject.name = "App Lounge" include ':app' +include ':domain' +include ':data' include ':parental-control-data' include ':auth-data-lib'