From 2d3fef541671eac3b397c5a283118ee084f2cb32 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Thu, 5 Mar 2026 18:38:23 +0530 Subject: [PATCH 1/6] Apps: Remove stray signing key --- keystore/platform.keystore | Bin 2899 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 keystore/platform.keystore diff --git a/keystore/platform.keystore b/keystore/platform.keystore deleted file mode 100644 index 574b2203d6dc78b3853cf899c3c7a1f60c773507..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2899 zcmXqL;`V1^WHxBx=3wL0YV&CO&dbQoxS)yaCrcC8SA!<5j|NSwxhPVsaV$-&Q3g${ zVFpdCL2TSmT|8WjObeP=ej7Bgd^gZ!h6VK*c|q}j!lj&ZTWBSb*`fFj)Hnc zaPN-E?J@h)kETTV91GG&e0*ZP{L=j2{f3#n?2S*8ta847?_4*@Oz^hI%de^XZC8aG zPS|34x%T_y``I@2Th?#rKhIP4s(k-#qg5#jLt`q8ikoM3t~z@AeqnpmTG@}!QrCUf za+EB3lC-zFu<7uMxab^#mE1=+f6mW-U2{E;pV4%>QTcYkOD|J9W<)HEo+@YPmpb9y zlK6O!YK4dXIlK0^#jtv*Y>t!=+Wbv=b;37>;;+wsP5C#yu`Xr$u8xgDmv81jeO_{Y zwe^Y*t~2*uaasB%Qk-p*Vt}N_#cN+SbNT#>^7*}YV*jLtg(dRW^fPob9lUWB!YjWCVBpGS+qj26ISH`_* zrK{^ouW>NUm|$0HGHJ58uTCKdqUleiS8kH zf~Un!slCK&5Ik8$%zwV!&38K%3U7U`(Rsggt#j|`?HNJ&`_{HCj$10ara;@~)$Ib$ zuG9HhlX8-S->g{>q`B+lgG9w?kL?>3*QeOE&(SnvF-bbZX#4xC|3WpzGgEiCQv>=Pe z-~4Bn9(^bNtuAoNocG=mRw6H!KmGGL?^)!LpS-?H4^F-K{Qa6P-DPDCcNn%aWz^of zUcc+^E~Rb!$5>obbOIV(Op^cGD9x7IHt*B1KJmjcF1;`6{)edPCkN#^U!D_oe%>A1 zf<@Qv90=s}?zzYG$!71jvMR#7S{z&7j8j@_XO>3J;0x^|*BmG$b+XdD#Xsneu)Mj&;;@u}K33Ppcg zyzHuKthW9W_ptR|5ZfQ^1%}awBjc85oR(drGi6J2-~N8Kz)ThCW$P~qPtfN%Zt*+W zUDGasf46I5@{CJt3MPEoU!oSJL?}M&R%y`{J(jmN_3w!X9H(C%@1DMR$&UrE7gn6= zd89PQG~nI=1>>g&rR3OGb?>QMdbas4`=js9!XCLlzb0ZKoOxp)KG*)C?qpCRl(5A(89>T z!obwb($L7DiM0l=4|$=K*?B7 zKE~9fx`ToaGyT3DIUxN-QuczJ;^%|k93I}#D&qCsW52spVA16Dh1a;2^NYK@On157 zxAd6btdkOV?~AK%jTKm)y>-Xz-0ABa4hMg&b?XfKJ5yJ|^;Y5Xji)}`joN9JlP}ShX?fWt{;{X5!(4?A5*&M6 zb0)00y!xnB*o|ueTUgHDXDYl|`FdLLf(6rh879gwi3aaXdc(M!Yu$Q2t9`!nCf>bGXSVb*etL7F=c1t7){UdSf_R2Z zs@CTz#z7aH0yjjrCoh}$eADM;Mb+G4h0KR9s^0VBUzhI~zo#m@kY7=F`#IlVSqJVF z1+r;W`y{cldzwr-a=q(&oX`!f{?}%AO*)Dc1n#Qc4>^7HkL7=}EXg}}*UQg%zVor{ z-6|=TgUFHx$AzD>3z(`g>TpN z6jV+$7ddmi?9KAvJ+El=WoF=z5`ZRz>SC6YPO!QYsdS>9p%U6uUp zo3U1<#FttXTxAZY{_Iik`V`&&gst+?xr@s;>wa6P zU9|14;@(`9M)Y|@i<@c>wO;E&ul?mR;Rh_r}K5qE0pKG=pV15xoI;Cce>L{cTb-gan+Km zJ14BrN&cA@H1&2})W=8%F1-(b1%LW!&(L0;d_=EUGs8P{UvSH&XTPJh~EIwM|A)a2J@e@^*)uY@mao_F=Ru`R9T zh-Y%%l6HFk^Z!fD6aQZP@lV@zbIh^@x4-c}+uc#g#kC+^E5qTW<4jF0^Wamm43P$# z=ko6Tyh7%ei1v$K#u5%;!%1RGv@#5zwQpoh>h+U&KSeuI`=^7NxDju`L-kYX(oAB% z9zWVJ>5meJXydxhO=-_Pwk~`^Ix_gK5KX3w%&cQNAMt*Kfc z Date: Fri, 6 Mar 2026 17:39:32 +0530 Subject: [PATCH 2/6] Applounge: Properly Inject broadcast reciever --- .../foundation/e/apps/AppLoungeApplication.kt | 5 +++-- .../e/apps/data/install/pkg/PkgManagerBR.kt | 22 +++++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index 98b7056d4..fd75a26dd 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -77,6 +77,9 @@ class AppLoungeApplication : Application(), Configuration.Provider { @IoCoroutineScope lateinit var coroutineScope: CoroutineScope + @Inject + lateinit var pkgManagerBR: PkgManagerBR + @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun onCreate() { super.onCreate() @@ -84,8 +87,6 @@ class AppLoungeApplication : Application(), Configuration.Provider { 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() } 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 8c85381fd..f24559004 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) { -- GitLab From e19b6df68b85e11f13e5e1c6126e898d8276fe73 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Thu, 5 Mar 2026 19:09:14 +0530 Subject: [PATCH 3/6] Applounge: Proper pass context to worker --- .../main/java/foundation/e/apps/AppLoungeApplication.kt | 2 -- .../foundation/e/apps/data/install/AppManagerWrapper.kt | 3 +++ .../apps/data/install/workmanager/AppInstallProcessor.kt | 2 +- .../apps/data/install/workmanager/InstallWorkManager.kt | 9 ++++----- .../e/apps/data/install/AppManagerWrapperProgressTest.kt | 4 +++- .../e/apps/fusedManager/AppManagerWrapperTest.kt | 5 ++--- .../e/apps/installProcessor/AppInstallProcessorTest.kt | 2 +- .../e/apps/installProcessor/FakeAppManagerWrapper.kt | 4 +++- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index fd75a26dd..d917ebb94 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -42,7 +42,6 @@ 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 @@ -86,7 +85,6 @@ class AppLoungeApplication : Application(), Configuration.Provider { Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler) - InstallWorkManager.context = this registerReceiver(pkgManagerBR, appLoungePackageManager.getFilter(), RECEIVER_EXPORTED) val currentVersion = runBlocking { appLoungeDataStore.tosVersion.first() } 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 54b801429..346b9a082 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/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/AppInstallProcessor.kt index 0e968c35b..6f4acc875 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 @@ -141,7 +141,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( 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 e3a453b6e..50edbef59 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/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt b/app/src/test/java/foundation/e/apps/data/install/AppManagerWrapperProgressTest.kt index 277cb7d3b..008b80108 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/fusedManager/AppManagerWrapperTest.kt b/app/src/test/java/foundation/e/apps/fusedManager/AppManagerWrapperTest.kt index 9cf3b5152..a7aad096a 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 ade2dc588..cd79e54d1 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -103,7 +103,7 @@ class AppInstallProcessorTest { fakeFusedDownloadDAO = FakeAppInstallDAO() appInstallRepository = AppInstallRepository(fakeFusedDownloadDAO) fakeFusedManagerRepository = - FakeAppManagerWrapper(fakeFusedDownloadDAO, fakeFusedManager, fakeFDroidRepository) + FakeAppManagerWrapper(fakeFusedDownloadDAO, context, fakeFusedManager, fakeFDroidRepository) val appInstallComponents = AppInstallComponents(appInstallRepository, fakeFusedManagerRepository) 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 9a52b1365..ff8309949 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 -- GitLab From 0ddc70ad79584239af94539cdc4d45345d2f0492 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Fri, 6 Mar 2026 16:31:45 +0530 Subject: [PATCH 4/6] Applounge: Introduce domain and data modules and move preferences to it --- .gitignore | 1 + app/build.gradle | 2 + .../foundation/e/apps/AppLoungeApplication.kt | 46 +-- .../foundation/e/apps/data/DownloadManager.kt | 6 +- .../java/foundation/e/apps/data/Stores.kt | 26 +- .../di/bindings/PreferenceBindingModule.kt | 61 ++++ .../apps/data/di/system/PreferencesModule.kt | 40 +++ .../java/foundation/e/apps/data/enums/User.kt | 7 - .../foundation/e/apps/data/event/AppEvent.kt | 3 +- .../e/apps/data/install/AppManagerImpl.kt | 6 +- .../updates/UpdatesBroadcastReceiver.kt | 27 +- .../data/install/updates/UpdatesWorker.kt | 48 ++- .../workmanager/AppInstallProcessor.kt | 19 +- .../login/cleanapk/CleanApkAuthenticator.kt | 18 +- .../data/login/core/AuthFailureFactory.kt | 5 +- .../e/apps/data/login/core/AuthObject.kt | 2 +- .../login/exceptions/GPlayLoginException.kt | 2 +- .../exceptions/GPlayValidationException.kt | 2 +- .../data/login/microg/MicrogLoginManager.kt | 12 +- .../data/login/playstore/AuthDataCache.kt | 8 +- .../login/playstore/GoogleLoginManager.kt | 10 +- .../login/playstore/OauthAuthDataBuilder.kt | 6 +- .../playstore/OauthToAasTokenConverter.kt | 2 +- .../login/playstore/PlayStoreAuthenticator.kt | 39 ++- .../data/login/playstore/PlayStoreSession.kt | 2 +- .../repository/AuthenticatorRepository.kt | 16 +- .../apps/data/login/state/LoginCoordinator.kt | 20 +- .../login/state/LoginPreferencesManager.kt | 8 +- .../e/apps/data/login/state/LoginState.kt | 6 - .../e/apps/data/microg/AuthDataProvider.kt | 12 +- .../data/preference/AppLoungeDataStore.kt | 194 ------------- .../data/preference/AppLoungePreference.kt | 131 --------- .../e/apps/data/provider/AgeRatingProvider.kt | 12 +- .../e/apps/data/receivers/DumpAuthData.kt | 28 +- .../e/apps/data/updates/UpdatesManagerImpl.kt | 10 +- .../login/InitialAnonymousLoginUseCase.kt | 2 +- .../domain/login/InitialGoogleLoginUseCase.kt | 4 +- .../domain/login/InitialMicrogLoginUseCase.kt | 4 +- .../search/FetchSearchSuggestionsUseCase.kt | 6 +- .../java/foundation/e/apps/ui/MainActivity.kt | 2 +- .../e/apps/ui/MainActivityViewModel.kt | 20 +- .../ui/application/ApplicationFragment.kt | 4 +- .../ApplicationListRVAdapter.kt | 4 +- .../compose/state/InstallButtonStateInput.kt | 2 +- .../compose/state/InstallButtonStateMapper.kt | 2 +- .../apps/ui/home/model/HomeChildRVAdapter.kt | 4 +- .../apps/ui/parentFragment/TimeoutFragment.kt | 2 +- .../e/apps/ui/search/v2/SearchFragmentV2.kt | 2 +- .../e/apps/ui/search/v2/SearchViewModelV2.kt | 8 +- .../e/apps/ui/settings/SettingsFragment.kt | 29 +- .../signin/LocaleChangedBroadcastReceiver.kt | 15 +- .../e/apps/ui/setup/signin/SignInViewModel.kt | 7 +- .../e/apps/ui/setup/tos/TOSViewModel.kt | 8 +- .../e/apps/ui/updates/UpdatesViewModel.kt | 18 +- app/src/main/res/values/strings.xml | 1 - .../e/apps/FakeAppLoungePreference.kt | 48 ++- .../e/apps/UpdateManagerImptTest.kt | 6 +- .../foundation/e/apps/data/StoreConfigTest.kt | 4 +- .../java/foundation/e/apps/data/StoresTest.kt | 4 +- .../e/apps/data/event/AppEventTest.kt | 2 +- .../data/install/updates/UpdatesWorkerTest.kt | 162 ++++++++--- .../data/login/LoginPreferencesManagerTest.kt | 18 +- .../apps/data/login/MicrogLoginManagerTest.kt | 39 ++- .../playstore/OauthAuthDataBuilderTest.kt | 8 +- .../playstore/OauthToAasTokenConverterTest.kt | 2 +- .../playstore/PlayStoreAuthenticatorTest.kt | 40 +-- .../login/playstore/PlayStoreSessionTest.kt | 2 +- .../repository/AuthenticatorRepositoryTest.kt | 14 +- .../data/playstore/PlayStoreRepositoryTest.kt | 3 +- .../data/preference/AppLoungeDataStoreTest.kt | 65 ----- .../login/FetchMicrogAccountUseCaseTest.kt | 6 +- .../login/HasMicrogAccountUseCaseTest.kt | 6 +- .../login/InitialAnonymousLoginUseCaseTest.kt | 2 +- .../login/InitialGoogleLoginUseCaseTest.kt | 4 +- .../login/InitialMicrogLoginUseCaseTest.kt | 4 +- .../login/ReportFaultyTokenUseCaseTest.kt | 2 +- .../FetchSearchSuggestionsUseCaseTest.kt | 14 +- .../e/apps/fused/SearchRepositoryImplTest.kt | 6 +- .../AppInstallProcessorTest.kt | 26 +- .../e/apps/login/LoginViewModelTest.kt | 2 +- .../state/InstallButtonStateMapperTest.kt | 2 +- .../ui/search/v2/SearchViewModelV2Test.kt | 11 +- .../e/apps/ui/updates/UpdatesViewModelTest.kt | 33 ++- data/build.gradle | 51 ++++ data/src/main/AndroidManifest.xml | 2 + .../data/di/qualifiers/IoCoroutineScope.kt | 0 .../data/preference/AppLoungePreference.kt | 96 ++++++ .../data/preference/PlayStoreAuthStore.kt | 37 +++ .../apps/data/preference/SessionDataStore.kt | 274 ++++++++++++++++++ .../UpdatePreferencesRepositoryImpl.kt | 79 +++++ .../data/preference/AppLoungeDataStoreTest.kt | 89 ++++++ .../preference/AppLoungePreferenceTest.kt | 40 ++- .../UpdatePreferencesRepositoryImplTest.kt | 92 ++++++ detekt.yml | 1 + domain/build.gradle | 34 +++ domain/src/main/AndroidManifest.xml | 2 + .../e/apps/domain/model/LoginState.kt | 24 ++ .../apps/domain/model}/PlayStoreAuthSource.kt | 2 +- .../foundation/e/apps/domain/model/User.kt | 25 ++ .../preferences/AppPreferencesRepository.kt | 36 +++ .../domain/preferences/SessionRepository.kt | 54 ++++ .../GetUpdateIntervalUseCase.kt | 32 ++ ...grateAnonymousUserUpdateIntervalUseCase.kt | 32 ++ .../UpdatePreferencesRepository.kt | 25 ++ .../GetUpdateIntervalUseCaseTest.kt | 56 ++++ ...eAnonymousUserUpdateIntervalUseCaseTest.kt | 54 ++++ gradle/libs.versions.toml | 4 + settings.gradle | 2 + 108 files changed, 1818 insertions(+), 841 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/data/di/bindings/PreferenceBindingModule.kt create mode 100644 app/src/main/java/foundation/e/apps/data/di/system/PreferencesModule.kt delete mode 100644 app/src/main/java/foundation/e/apps/data/enums/User.kt delete mode 100644 app/src/main/java/foundation/e/apps/data/login/state/LoginState.kt delete mode 100644 app/src/main/java/foundation/e/apps/data/preference/AppLoungeDataStore.kt delete mode 100644 app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt delete mode 100644 app/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt create mode 100644 data/build.gradle create mode 100644 data/src/main/AndroidManifest.xml rename {app => data}/src/main/java/foundation/e/apps/data/di/qualifiers/IoCoroutineScope.kt (100%) create mode 100644 data/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt create mode 100644 data/src/main/java/foundation/e/apps/data/preference/PlayStoreAuthStore.kt create mode 100644 data/src/main/java/foundation/e/apps/data/preference/SessionDataStore.kt create mode 100644 data/src/main/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImpl.kt create mode 100644 data/src/test/java/foundation/e/apps/data/preference/AppLoungeDataStoreTest.kt rename {app => data}/src/test/java/foundation/e/apps/data/preference/AppLoungePreferenceTest.kt (59%) create mode 100644 data/src/test/java/foundation/e/apps/data/preference/updateinterval/UpdatePreferencesRepositoryImplTest.kt create mode 100644 domain/build.gradle create mode 100644 domain/src/main/AndroidManifest.xml create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/model/LoginState.kt rename {app/src/main/java/foundation/e/apps/data/login/playstore => domain/src/main/kotlin/foundation/e/apps/domain/model}/PlayStoreAuthSource.kt (94%) create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/model/User.kt create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/preferences/AppPreferencesRepository.kt create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/preferences/SessionRepository.kt create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCase.kt create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCase.kt create mode 100644 domain/src/main/kotlin/foundation/e/apps/domain/preferences/updateinterval/UpdatePreferencesRepository.kt create mode 100644 domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/GetUpdateIntervalUseCaseTest.kt create mode 100644 domain/src/test/java/foundation/e/apps/domain/preferences/updateinterval/MigrateAnonymousUserUpdateIntervalUseCaseTest.kt diff --git a/.gitignore b/.gitignore index 10cfdbfaf..2fef324f5 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 8d65015a3..62bf58911 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,6 +195,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) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index d917ebb94..90a1b1436 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -34,17 +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.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 @@ -60,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 @@ -76,6 +68,15 @@ 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 @@ -87,10 +88,10 @@ class AppLoungeApplication : Application(), Configuration.Provider { 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, "") } } @@ -113,12 +114,15 @@ 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() } 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 b20650f4f..994c4265c 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 ca076e86c..40f8b9c23 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 000000000..240b766ac --- /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 000000000..24af60216 --- /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 b5dc3df67..000000000 --- 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 57806cd61..b12a12ea8 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 803c36e3b..2e354df1f 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/updates/UpdatesBroadcastReceiver.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesBroadcastReceiver.kt index 5599eced7..738770b68 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 31d0e20d2..f510a58f2 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 6f4acc875..3b214d475 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)) } } @@ -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/login/cleanapk/CleanApkAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/cleanapk/CleanApkAuthenticator.kt index f3878c94d..00575fbf7 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 c89888577..70eea68d7 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 89917bf44..3c89fa0d8 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 d6a64a7bb..355b9bd81 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 5b4322a4c..039a9959f 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 252a97489..85bccce70 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 76dcb7fc4..1fa97c91d 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 d71adcae8..112ea83da 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 d1c7cb121..eb2a49a1e 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 c511d919b..7e0b08dc5 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 4ae415f14..6fd105ee5 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 a4813eecc..cd4a5e390 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 a514d9651..9e9b51e5b 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 87d340512..29477fe6b 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 8b54d60c8..9c9fe999a 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 661d3569f..000000000 --- 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 b55665eda..3631ddd7a 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 62a1cb9b0..000000000 --- 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 98a50ef64..000000000 --- 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 fdfc89916..ae8458086 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 353029a4e..c1d2b839c 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 17fe245b4..fbd86faba 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 2c29f7a0f..f389255b1 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 55686395b..cef70d28e 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 a8a608aad..bc8d70495 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 49ed7ff37..f2ef1113a 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 6102b1d82..b34791151 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 8d41a6a95..b5deaa4ee 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 3148ea144..a2f414ca2 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 596a463d0..531a4623a 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 4c4f4727d..52dc9c4c9 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 a624bfe87..659d98a41 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 3c527119f..2d56ed0a4 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 1d2d3b1e2..08b26e96b 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 c2081b38a..bcbf1ef3c 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 b88910b5a..c0df366af 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 12f85b222..f2ca68226 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 1ebb0bb6f..7eece67a4 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 7bff8a487..0a26bc215 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 c9472443d..c767dce6c 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 0b89ff15d..cc0eb70c3 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 25c7dc228..7cb3f8fef 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 7c7adda21..3f770f904 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 f204988f3..15c4fc1ff 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 b029726c1..f60590038 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 674f97796..977a652d7 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 4f7c0ba38..c3dd00dfb 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/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt index 962863037..2df96a386 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 7301b590f..6743d7645 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 920f8a84e..2ceeb98f4 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 0a0d63217..b7776e3f1 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 afdd8fc1f..780546612 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 8b9c85966..abaa0595c 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 79107a36e..472587fe8 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 5498988c7..3b5f8f7ec 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 11113d092..9450b82dc 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 2bfc46ecf..000000000 --- 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 40ed5ae7f..f8ff61062 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 782fad6ae..442bb7868 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 bc0ad1c56..5f6c07e49 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 221272f1d..114f15958 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 f437f810b..72c8c95eb 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 3b8d567c1..e0857ecbb 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 c5c2d1cea..c1a63f402 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 38ef3b5a8..741f9fd11 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/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index cd79e54d1..a492a7377 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 @@ -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/login/LoginViewModelTest.kt b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt index e9dc6c757..d5d338eca 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 9f3c7c5f1..506434e07 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 31b27e398..8b2b48eab 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 eb7f0fd34..aad021a41 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/data/build.gradle b/data/build.gradle new file mode 100644 index 000000000..57644ef38 --- /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 000000000..8072ee00d --- /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 000000000..c3609a4e0 --- /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 000000000..f18fb4d18 --- /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 000000000..03e12e9d2 --- /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 000000000..aac8296ec --- /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 000000000..cd3268e8a --- /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 a179a69f6..d43331865 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 000000000..b674c1deb --- /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 fb999e7b1..1956ecb93 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 000000000..31d9f5892 --- /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 000000000..8072ee00d --- /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 000000000..8b7e40d82 --- /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 278fd80d9..3f4da0a48 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 000000000..6470bc9d2 --- /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 000000000..e1040a6cc --- /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 000000000..30fe01e4b --- /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 000000000..0db0f4d4e --- /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 000000000..ae7319234 --- /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 000000000..81eeacfeb --- /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 000000000..f9bc17b83 --- /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 000000000..ca620cbbb --- /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 ec1b556bc..40d40fdc7 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/settings.gradle b/settings.gradle index 0b3590379..1c8902dd3 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' -- GitLab From 8a480962e94506c444e878a6055b834a278bf062 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Mon, 9 Mar 2026 13:42:55 +0530 Subject: [PATCH 5/6] build(test): align jvm toolchain and Robolectric runtime --- app/build.gradle | 10 ++++++++++ auth-data-lib/build.gradle | 6 +++++- parental-control-data/build.gradle | 4 ++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 62bf58911..e50240472 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") @@ -264,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/auth-data-lib/build.gradle b/auth-data-lib/build.gradle index 56bd124c1..58624b38d 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/parental-control-data/build.gradle b/parental-control-data/build.gradle index 57eaabc4e..47b2e2f8a 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 -- GitLab From be5e4d93693bd66a2486a19893b7cfd9026232a5 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Mon, 9 Mar 2026 13:58:09 +0530 Subject: [PATCH 6/6] fix(install): harden file move handling and startup cleanup --- .../foundation/e/apps/AppLoungeApplication.kt | 8 +++++- .../e/apps/data/install/FileManager.kt | 27 +++++-------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index 90a1b1436..78a15188a 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -124,9 +124,15 @@ class AppLoungeApplication : Application(), Configuration.Provider { ) } - 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/install/FileManager.kt b/app/src/main/java/foundation/e/apps/data/install/FileManager.kt index a02c0fdb4..664e89491 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() } } } -- GitLab