Loading app/src/main/java/foundation/e/apps/data/StoreDescriptor.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data import foundation.e.apps.data.enums.Source /** * Describes a store integration so that it can be discovered and toggled dynamically. * * Adding a new store only requires contributing another [StoreDescriptor] via DI. */ data class StoreDescriptor( val source: Source, val repository: StoreRepository, val isEnabled: () -> Boolean, val enable: () -> Unit, val disable: () -> Unit, ) app/src/main/java/foundation/e/apps/data/Stores.kt +27 −41 Original line number Diff line number Diff line Loading @@ -19,64 +19,50 @@ package foundation.e.apps.data 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.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.data.enums.Source.SYSTEM_APP import javax.inject.Inject import javax.inject.Singleton @Singleton class Stores @Inject constructor( private val playStoreRepository: PlayStoreRepository, private val cleanApkAppsRepository: CleanApkAppsRepository, private val cleanApkPwaRepository: CleanApkPwaRepository, private val appLoungePreference: AppLoungePreference storeDescriptors: Set<StoreDescriptor> ) { private val descriptorsBySource = storeDescriptors.associateBy { it.source } /** * Retrieves a map of enabled store repositories based on user preferences. * Retrieves a map of enabled store repositories. * * @return A map where the keys are the selected [Source] types and * the values are their corresponding [StoreRepository] instances. */ fun getStores(): Map<Source, StoreRepository> { val showOpenSourceApps = appLoungePreference.isOpenSourceSelected() val showPwaApps = appLoungePreference.isPWASelected() val showPlayStoreApps = appLoungePreference.isPlayStoreSelected() fun getStores(): Map<Source, StoreRepository> = descriptorsBySource .filterValues { it.isEnabled() } .mapValues { (_, descriptor) -> descriptor.repository } return buildMap { if (showPlayStoreApps) { put(PLAY_STORE, playStoreRepository) } if (showOpenSourceApps) { put(OPEN_SOURCE, cleanApkAppsRepository) } if (showPwaApps) { put(PWA, cleanApkPwaRepository) } fun getStore(source: Source): StoreRepository? = descriptorsBySource[source]?.takeIf { it.isEnabled() }?.repository fun enableStore(source: Source) { validateToggleable(source) descriptorsBySource[source]?.enable?.invoke() ?: error("No matching Store found for $source.") } fun disableStore(source: Source) { validateToggleable(source) descriptorsBySource[source]?.disable?.invoke() ?: error("No matching Store found for $source.") } fun getStore(source: Source): StoreRepository? = getStores()[source] fun isStoreEnabled(source: Source): Boolean = descriptorsBySource[source]?.isEnabled?.invoke() ?: false fun enableStore(source: Source) = when (source) { OPEN_SOURCE -> appLoungePreference.enableOpenSource() PWA -> appLoungePreference.enablePwa() PLAY_STORE -> appLoungePreference.enablePlayStore() else -> error("No matching Store found for $source.") private fun validateToggleable(source: Source) { if (source == SYSTEM_APP) { error("System apps cannot be enabled or disabled.") } fun disableStore(source: Source) = when (source) { OPEN_SOURCE -> appLoungePreference.disableOpenSource() PWA -> appLoungePreference.disablePwa() PLAY_STORE -> appLoungePreference.disablePlayStore() else -> error("No matching Store found for $source.") } fun isStoreEnabled(source: Source): Boolean = getStores().containsKey(source) } app/src/main/java/foundation/e/apps/di/StoreModule.kt 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import foundation.e.apps.data.StoreDescriptor 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 javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object StoreModule { @Provides @IntoSet @Singleton fun providePlayStoreDescriptor( playStoreRepository: PlayStoreRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.PLAY_STORE, repository = playStoreRepository, isEnabled = { appLoungePreference.isPlayStoreSelected() }, enable = { appLoungePreference.enablePlayStore() }, disable = { appLoungePreference.disablePlayStore() }, ) @Provides @IntoSet @Singleton fun provideOpenSourceDescriptor( cleanApkAppsRepository: CleanApkAppsRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.OPEN_SOURCE, repository = cleanApkAppsRepository, isEnabled = { appLoungePreference.isOpenSourceSelected() }, enable = { appLoungePreference.enableOpenSource() }, disable = { appLoungePreference.disableOpenSource() }, ) @Provides @IntoSet @Singleton fun providePwaDescriptor( cleanApkPwaRepository: CleanApkPwaRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.PWA, repository = cleanApkPwaRepository, isEnabled = { appLoungePreference.isPWASelected() }, enable = { appLoungePreference.enablePwa() }, disable = { appLoungePreference.disablePwa() }, ) } app/src/test/java/foundation/e/apps/category/CategoryApiTest.kt +24 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.aurora.gplayapi.data.models.Category import foundation.e.apps.R import foundation.e.apps.data.AppSourcesContainer import foundation.e.apps.data.StoreDescriptor import foundation.e.apps.data.StoreRepository import foundation.e.apps.data.Stores import foundation.e.apps.data.application.ApplicationDataManager import foundation.e.apps.data.application.category.CategoryApi Loading Loading @@ -99,7 +101,13 @@ class CategoryApiTest { val applicationDataManager = ApplicationDataManager(appLoungePackageManager, pwaManager, visibilityFetcher) fakeStores = Stores(playStoreRepository, cleanApkAppsRepository, cleanApkPWARepository, appLoungePreference) fakeStores = Stores( setOf( createDescriptor(Source.PLAY_STORE, playStoreRepository, enabled = true), createDescriptor(Source.OPEN_SOURCE, cleanApkAppsRepository, enabled = true), createDescriptor(Source.PWA, cleanApkPWARepository, enabled = true), ) ) val appSourcesContainer = AppSourcesContainer(playStoreRepository, cleanApkAppsRepository, cleanApkPWARepository) Loading @@ -111,6 +119,21 @@ class CategoryApiTest { ) } private fun createDescriptor( source: Source, repository: StoreRepository, enabled: Boolean, ): StoreDescriptor { var currentState = enabled return StoreDescriptor( source = source, repository = repository, isEnabled = { currentState }, enable = { currentState = true }, disable = { currentState = false }, ) } @Test fun `getCategory when only pwa is selected`() = runTest { val categories = Loading @@ -126,8 +149,6 @@ class CategoryApiTest { Mockito.`when`(context.getString(eq(R.string.pwa))).thenReturn("PWA") Mockito.`when`(appLoungePreference.isPWASelected()).thenReturn(true) val categoryListResponse = categoryApi.getCategoriesList(CategoryType.APPLICATION).map { it.categories }.flatten() Loading @@ -146,8 +167,6 @@ class CategoryApiTest { Mockito.`when`(context.getString(eq(R.string.open_source))).thenReturn("Open source") Mockito.`when`(appLoungePreference.isOpenSourceSelected()).thenReturn(true) fakeStores.disableStore(Source.PWA) fakeStores.disableStore(Source.PLAY_STORE) Loading Loading
app/src/main/java/foundation/e/apps/data/StoreDescriptor.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data import foundation.e.apps.data.enums.Source /** * Describes a store integration so that it can be discovered and toggled dynamically. * * Adding a new store only requires contributing another [StoreDescriptor] via DI. */ data class StoreDescriptor( val source: Source, val repository: StoreRepository, val isEnabled: () -> Boolean, val enable: () -> Unit, val disable: () -> Unit, )
app/src/main/java/foundation/e/apps/data/Stores.kt +27 −41 Original line number Diff line number Diff line Loading @@ -19,64 +19,50 @@ package foundation.e.apps.data 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.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.data.enums.Source.SYSTEM_APP import javax.inject.Inject import javax.inject.Singleton @Singleton class Stores @Inject constructor( private val playStoreRepository: PlayStoreRepository, private val cleanApkAppsRepository: CleanApkAppsRepository, private val cleanApkPwaRepository: CleanApkPwaRepository, private val appLoungePreference: AppLoungePreference storeDescriptors: Set<StoreDescriptor> ) { private val descriptorsBySource = storeDescriptors.associateBy { it.source } /** * Retrieves a map of enabled store repositories based on user preferences. * Retrieves a map of enabled store repositories. * * @return A map where the keys are the selected [Source] types and * the values are their corresponding [StoreRepository] instances. */ fun getStores(): Map<Source, StoreRepository> { val showOpenSourceApps = appLoungePreference.isOpenSourceSelected() val showPwaApps = appLoungePreference.isPWASelected() val showPlayStoreApps = appLoungePreference.isPlayStoreSelected() fun getStores(): Map<Source, StoreRepository> = descriptorsBySource .filterValues { it.isEnabled() } .mapValues { (_, descriptor) -> descriptor.repository } return buildMap { if (showPlayStoreApps) { put(PLAY_STORE, playStoreRepository) } if (showOpenSourceApps) { put(OPEN_SOURCE, cleanApkAppsRepository) } if (showPwaApps) { put(PWA, cleanApkPwaRepository) } fun getStore(source: Source): StoreRepository? = descriptorsBySource[source]?.takeIf { it.isEnabled() }?.repository fun enableStore(source: Source) { validateToggleable(source) descriptorsBySource[source]?.enable?.invoke() ?: error("No matching Store found for $source.") } fun disableStore(source: Source) { validateToggleable(source) descriptorsBySource[source]?.disable?.invoke() ?: error("No matching Store found for $source.") } fun getStore(source: Source): StoreRepository? = getStores()[source] fun isStoreEnabled(source: Source): Boolean = descriptorsBySource[source]?.isEnabled?.invoke() ?: false fun enableStore(source: Source) = when (source) { OPEN_SOURCE -> appLoungePreference.enableOpenSource() PWA -> appLoungePreference.enablePwa() PLAY_STORE -> appLoungePreference.enablePlayStore() else -> error("No matching Store found for $source.") private fun validateToggleable(source: Source) { if (source == SYSTEM_APP) { error("System apps cannot be enabled or disabled.") } fun disableStore(source: Source) = when (source) { OPEN_SOURCE -> appLoungePreference.disableOpenSource() PWA -> appLoungePreference.disablePwa() PLAY_STORE -> appLoungePreference.disablePlayStore() else -> error("No matching Store found for $source.") } fun isStoreEnabled(source: Source): Boolean = getStores().containsKey(source) }
app/src/main/java/foundation/e/apps/di/StoreModule.kt 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import foundation.e.apps.data.StoreDescriptor 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 javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object StoreModule { @Provides @IntoSet @Singleton fun providePlayStoreDescriptor( playStoreRepository: PlayStoreRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.PLAY_STORE, repository = playStoreRepository, isEnabled = { appLoungePreference.isPlayStoreSelected() }, enable = { appLoungePreference.enablePlayStore() }, disable = { appLoungePreference.disablePlayStore() }, ) @Provides @IntoSet @Singleton fun provideOpenSourceDescriptor( cleanApkAppsRepository: CleanApkAppsRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.OPEN_SOURCE, repository = cleanApkAppsRepository, isEnabled = { appLoungePreference.isOpenSourceSelected() }, enable = { appLoungePreference.enableOpenSource() }, disable = { appLoungePreference.disableOpenSource() }, ) @Provides @IntoSet @Singleton fun providePwaDescriptor( cleanApkPwaRepository: CleanApkPwaRepository, appLoungePreference: AppLoungePreference, ): StoreDescriptor = StoreDescriptor( source = Source.PWA, repository = cleanApkPwaRepository, isEnabled = { appLoungePreference.isPWASelected() }, enable = { appLoungePreference.enablePwa() }, disable = { appLoungePreference.disablePwa() }, ) }
app/src/test/java/foundation/e/apps/category/CategoryApiTest.kt +24 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.aurora.gplayapi.data.models.Category import foundation.e.apps.R import foundation.e.apps.data.AppSourcesContainer import foundation.e.apps.data.StoreDescriptor import foundation.e.apps.data.StoreRepository import foundation.e.apps.data.Stores import foundation.e.apps.data.application.ApplicationDataManager import foundation.e.apps.data.application.category.CategoryApi Loading Loading @@ -99,7 +101,13 @@ class CategoryApiTest { val applicationDataManager = ApplicationDataManager(appLoungePackageManager, pwaManager, visibilityFetcher) fakeStores = Stores(playStoreRepository, cleanApkAppsRepository, cleanApkPWARepository, appLoungePreference) fakeStores = Stores( setOf( createDescriptor(Source.PLAY_STORE, playStoreRepository, enabled = true), createDescriptor(Source.OPEN_SOURCE, cleanApkAppsRepository, enabled = true), createDescriptor(Source.PWA, cleanApkPWARepository, enabled = true), ) ) val appSourcesContainer = AppSourcesContainer(playStoreRepository, cleanApkAppsRepository, cleanApkPWARepository) Loading @@ -111,6 +119,21 @@ class CategoryApiTest { ) } private fun createDescriptor( source: Source, repository: StoreRepository, enabled: Boolean, ): StoreDescriptor { var currentState = enabled return StoreDescriptor( source = source, repository = repository, isEnabled = { currentState }, enable = { currentState = true }, disable = { currentState = false }, ) } @Test fun `getCategory when only pwa is selected`() = runTest { val categories = Loading @@ -126,8 +149,6 @@ class CategoryApiTest { Mockito.`when`(context.getString(eq(R.string.pwa))).thenReturn("PWA") Mockito.`when`(appLoungePreference.isPWASelected()).thenReturn(true) val categoryListResponse = categoryApi.getCategoriesList(CategoryType.APPLICATION).map { it.categories }.flatten() Loading @@ -146,8 +167,6 @@ class CategoryApiTest { Mockito.`when`(context.getString(eq(R.string.open_source))).thenReturn("Open source") Mockito.`when`(appLoungePreference.isOpenSourceSelected()).thenReturn(true) fakeStores.disableStore(Source.PWA) fakeStores.disableStore(Source.PLAY_STORE) Loading