Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9506e21 authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

refactor(auth): read source capabilities from source selection contract

parent 1baac935
Loading
Loading
Loading
Loading
+13 −43
Original line number Diff line number Diff line
@@ -18,21 +18,17 @@

package foundation.e.apps.data.login.repository

import android.content.SharedPreferences
import foundation.e.apps.data.di.qualifiers.IoCoroutineScope
import foundation.e.apps.data.preference.PREFERENCE_SHOW_FOSS
import foundation.e.apps.data.preference.PREFERENCE_SHOW_GPLAY
import foundation.e.apps.data.preference.PREFERENCE_SHOW_PWA
import foundation.e.apps.domain.auth.AuthSession
import foundation.e.apps.domain.auth.AuthSessionRepository
import foundation.e.apps.domain.auth.PlayStoreLoginMode
import foundation.e.apps.domain.auth.UserCapabilities
import foundation.e.apps.domain.auth.UserCapabilitiesProvider
import foundation.e.apps.domain.source.SourceSelection
import foundation.e.apps.domain.source.SourceSelectionRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@@ -41,22 +37,18 @@ import javax.inject.Singleton
@Singleton
class UserCapabilitiesProviderImpl @Inject constructor(
    private val authSessionRepository: AuthSessionRepository,
    private val sharedPreferences: SharedPreferences,
    private val sourceSelectionRepository: SourceSelectionRepository,
    @IoCoroutineScope
    private val coroutineScope: CoroutineScope,
) : UserCapabilitiesProvider {

    override val capabilities: StateFlow<UserCapabilities> = combine(
        authSessionRepository.currentSession,
        booleanPreferenceFlow(PREFERENCE_SHOW_FOSS),
        booleanPreferenceFlow(PREFERENCE_SHOW_PWA),
        booleanPreferenceFlow(PREFERENCE_SHOW_GPLAY),
    ) { session, openSourceSelected, pwaSelected, playStoreSelected ->
        sourceSelectionRepository.sourceSelection,
    ) { session, sourceSelection ->
        buildCapabilities(
            session = session,
            openSourceSelected = openSourceSelected,
            pwaSelected = pwaSelected,
            playStoreSelected = playStoreSelected,
            sourceSelection = sourceSelection,
        )
    }.stateIn(
        coroutineScope,
@@ -69,39 +61,16 @@ class UserCapabilitiesProviderImpl @Inject constructor(
        )
    )

    private fun booleanPreferenceFlow(key: String) = callbackFlow {
        trySend(sharedPreferences.getBoolean(key, true))

        val listener = SharedPreferences.OnSharedPreferenceChangeListener { preferences, changedKey ->
            if (changedKey == key) {
                trySend(preferences.getBoolean(key, true))
            }
        }

        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
        awaitClose {
            sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
        }
    }.stateIn(
        coroutineScope,
        SharingStarted.Eagerly,
        sharedPreferences.getBoolean(key, true)
    )

    override suspend fun resolveCapabilities(): UserCapabilities {
        return buildCapabilities(
            session = authSessionRepository.resolveCurrentSession(),
            openSourceSelected = sharedPreferences.getBoolean(PREFERENCE_SHOW_FOSS, true),
            pwaSelected = sharedPreferences.getBoolean(PREFERENCE_SHOW_PWA, true),
            playStoreSelected = sharedPreferences.getBoolean(PREFERENCE_SHOW_GPLAY, true),
            sourceSelection = sourceSelectionRepository.currentSourceSelection(),
        )
    }

    private fun buildCapabilities(
        session: AuthSession,
        openSourceSelected: Boolean,
        pwaSelected: Boolean,
        playStoreSelected: Boolean,
        sourceSelection: SourceSelection,
    ): UserCapabilities {
        if (session == AuthSession.Unauthenticated) {
            return UserCapabilities(
@@ -113,10 +82,11 @@ class UserCapabilitiesProviderImpl @Inject constructor(
        }

        return UserCapabilities(
            canAccessPlayStore = playStoreSelected && session is AuthSession.PlayStoreSession,
            canAccessOpenSource = openSourceSelected,
            canAccessPwa = pwaSelected,
            canPurchaseApps = playStoreSelected &&
            canAccessPlayStore = sourceSelection.isPlayStoreSelected &&
                session is AuthSession.PlayStoreSession,
            canAccessOpenSource = sourceSelection.isOpenSourceSelected,
            canAccessPwa = sourceSelection.isPwaSelected,
            canPurchaseApps = sourceSelection.isPlayStoreSelected &&
                session is AuthSession.PlayStoreSession &&
                session.loginMode != PlayStoreLoginMode.ANONYMOUS,
        )
+49 −51
Original line number Diff line number Diff line
@@ -18,15 +18,12 @@

package foundation.e.apps.data.login.repository

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import foundation.e.apps.data.preference.PREFERENCE_SHOW_FOSS
import foundation.e.apps.data.preference.PREFERENCE_SHOW_GPLAY
import foundation.e.apps.data.preference.PREFERENCE_SHOW_PWA
import foundation.e.apps.domain.auth.AuthSession
import foundation.e.apps.domain.auth.AuthSessionRepository
import foundation.e.apps.domain.auth.PlayStoreLoginMode
import foundation.e.apps.domain.source.SourceSelection
import foundation.e.apps.domain.source.SourceSelectionRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -42,19 +39,11 @@ class UserCapabilitiesProviderImplTest {

    @Test
    fun `capabilities track anonymous play store restrictions`() = runTest {
        val sharedPreferences = context()
            .getSharedPreferences("UserCapabilitiesProviderImplTest.anonymous", Context.MODE_PRIVATE)
        sharedPreferences.edit()
            .putBoolean(PREFERENCE_SHOW_FOSS, true)
            .putBoolean(PREFERENCE_SHOW_PWA, true)
            .putBoolean(PREFERENCE_SHOW_GPLAY, true)
            .commit()

        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.PlayStoreSession(PlayStoreLoginMode.ANONYMOUS),
            ),
            sharedPreferences = sharedPreferences,
            sourceSelectionRepository = FakeSourceSelectionRepository(),
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

@@ -69,19 +58,11 @@ class UserCapabilitiesProviderImplTest {

    @Test
    fun `capabilities stay disabled when unauthenticated`() = runTest {
        val sharedPreferences = context()
            .getSharedPreferences("UserCapabilitiesProviderImplTest.unauthenticated", Context.MODE_PRIVATE)
        sharedPreferences.edit()
            .putBoolean(PREFERENCE_SHOW_FOSS, true)
            .putBoolean(PREFERENCE_SHOW_PWA, true)
            .putBoolean(PREFERENCE_SHOW_GPLAY, true)
            .commit()

        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.Unauthenticated,
            ),
            sharedPreferences = sharedPreferences,
            sourceSelectionRepository = FakeSourceSelectionRepository(),
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

@@ -95,19 +76,11 @@ class UserCapabilitiesProviderImplTest {

    @Test
    fun `capabilities keep open source access enabled for open source sessions`() = runTest {
        val sharedPreferences = context()
            .getSharedPreferences("UserCapabilitiesProviderImplTest.openSource", Context.MODE_PRIVATE)
        sharedPreferences.edit()
            .putBoolean(PREFERENCE_SHOW_FOSS, true)
            .putBoolean(PREFERENCE_SHOW_PWA, true)
            .putBoolean(PREFERENCE_SHOW_GPLAY, true)
            .commit()

        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.OpenSourceSession,
            ),
            sharedPreferences = sharedPreferences,
            sourceSelectionRepository = FakeSourceSelectionRepository(),
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

@@ -121,19 +94,11 @@ class UserCapabilitiesProviderImplTest {

    @Test
    fun `capabilities keep play store access enabled during google session refresh`() = runTest {
        val sharedPreferences = context()
            .getSharedPreferences("UserCapabilitiesProviderImplTest.google", Context.MODE_PRIVATE)
        sharedPreferences.edit()
            .putBoolean(PREFERENCE_SHOW_FOSS, true)
            .putBoolean(PREFERENCE_SHOW_PWA, true)
            .putBoolean(PREFERENCE_SHOW_GPLAY, true)
            .commit()

        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.PlayStoreSession(PlayStoreLoginMode.GOOGLE),
            ),
            sharedPreferences = sharedPreferences,
            sourceSelectionRepository = FakeSourceSelectionRepository(),
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

@@ -148,19 +113,11 @@ class UserCapabilitiesProviderImplTest {

    @Test
    fun `resolveCapabilities reflects persisted google session state immediately`() = runTest {
        val sharedPreferences = context()
            .getSharedPreferences("UserCapabilitiesProviderImplTest.awaitGoogle", Context.MODE_PRIVATE)
        sharedPreferences.edit()
            .putBoolean(PREFERENCE_SHOW_FOSS, true)
            .putBoolean(PREFERENCE_SHOW_PWA, true)
            .putBoolean(PREFERENCE_SHOW_GPLAY, true)
            .commit()

        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.PlayStoreSession(PlayStoreLoginMode.GOOGLE),
            ),
            sharedPreferences = sharedPreferences,
            sourceSelectionRepository = FakeSourceSelectionRepository(),
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

@@ -171,7 +128,34 @@ class UserCapabilitiesProviderImplTest {
        assertThat(capabilities.canPurchaseApps).isTrue()
    }

    private fun context(): Context = ApplicationProvider.getApplicationContext()
    @Test
    fun `capabilities react to source selection changes from repository`() = runTest {
        val sourceSelectionRepository = FakeSourceSelectionRepository()
        val repository = UserCapabilitiesProviderImpl(
            authSessionRepository = FakeAuthSessionRepository(
                session = AuthSession.PlayStoreSession(PlayStoreLoginMode.GOOGLE),
            ),
            sourceSelectionRepository = sourceSelectionRepository,
            coroutineScope = TestScope(StandardTestDispatcher(testScheduler)),
        )

        advanceUntilIdle()

        sourceSelectionRepository.saveSourceSelection(
            SourceSelection(
                isOpenSourceSelected = true,
                isPlayStoreSelected = false,
                isPwaSelected = false,
            )
        )
        advanceUntilIdle()

        val capabilities = repository.capabilities.value
        assertThat(capabilities.canAccessPlayStore).isFalse()
        assertThat(capabilities.canAccessOpenSource).isTrue()
        assertThat(capabilities.canAccessPwa).isFalse()
        assertThat(capabilities.canPurchaseApps).isFalse()
    }

    private class FakeAuthSessionRepository(
        session: AuthSession,
@@ -190,4 +174,18 @@ class UserCapabilitiesProviderImplTest {
            sessionState.value = AuthSession.Unauthenticated
        }
    }

    private class FakeSourceSelectionRepository(
        sourceSelection: SourceSelection = SourceSelection.DEFAULT,
    ) : SourceSelectionRepository {
        private val sourceSelectionState = MutableStateFlow(sourceSelection)

        override val sourceSelection: StateFlow<SourceSelection> = sourceSelectionState

        override fun currentSourceSelection(): SourceSelection = sourceSelectionState.value

        override fun saveSourceSelection(sourceSelection: SourceSelection) {
            sourceSelectionState.value = sourceSelection
        }
    }
}