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

Commit 95dbb543 authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

refactor(login): move Play bootstrap and rollback state behind domain contracts

parent d3d755db
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -33,9 +33,12 @@ import foundation.e.apps.data.login.playstore.PlayStoreTokenRefreshHandler
import foundation.e.apps.data.login.repository.AuthRefreshRepositoryImpl
import foundation.e.apps.data.login.repository.AuthenticatorRepository
import foundation.e.apps.data.login.repository.OkHttpLoginCacheRepository
import foundation.e.apps.data.login.repository.PlayLoginPreparationServiceImpl
import foundation.e.apps.domain.auth.AuthRefreshRepository
import foundation.e.apps.domain.auth.TokenRefreshHandler
import foundation.e.apps.domain.login.LoginCacheRepository
import foundation.e.apps.domain.login.PlayCredentialStateRepository
import foundation.e.apps.domain.login.PlayLoginPreparationService
import javax.inject.Singleton

@Module
@@ -78,6 +81,18 @@ interface LoginBindingModule {
        authRefreshRepositoryImpl: AuthRefreshRepositoryImpl,
    ): AuthRefreshRepository

    @Binds
    @Singleton
    fun bindPlayCredentialStateRepository(
        playLoginPreparationServiceImpl: PlayLoginPreparationServiceImpl,
    ): PlayCredentialStateRepository

    @Binds
    @Singleton
    fun bindPlayLoginPreparationService(
        playLoginPreparationServiceImpl: PlayLoginPreparationServiceImpl,
    ): PlayLoginPreparationService

    @Binds
    @Singleton
    fun bindTokenRefreshHandler(
+39 −50
Original line number Diff line number Diff line
@@ -17,14 +17,6 @@

package foundation.e.apps.feature.auth.login

import com.aurora.gplayapi.data.models.AuthData
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.login.api.PlayStoreAuthManager
import foundation.e.apps.data.login.api.PlayStoreBootstrapCredentials
import foundation.e.apps.data.login.api.PlayStoreLoginBootstrapper
import foundation.e.apps.data.login.core.StoreType
import foundation.e.apps.data.login.core.toAuthError
import foundation.e.apps.data.preference.PlayStoreAuthStore
import foundation.e.apps.domain.auth.AuthError
import foundation.e.apps.domain.auth.AuthSession
import foundation.e.apps.domain.auth.AuthSessionRepository
@@ -34,6 +26,12 @@ import foundation.e.apps.domain.login.AnonymousLoginUseCase
import foundation.e.apps.domain.login.GoogleLoginUseCase
import foundation.e.apps.domain.login.MicrogLoginUseCase
import foundation.e.apps.domain.login.NoGoogleLoginUseCase
import foundation.e.apps.domain.login.PlayCredentialState
import foundation.e.apps.domain.login.PlayCredentialStateRepository
import foundation.e.apps.domain.login.PlayLoginPreparationRequest
import foundation.e.apps.domain.login.PlayLoginPreparationResult
import foundation.e.apps.domain.login.PlayLoginPreparationService
import foundation.e.apps.domain.login.PreparedPlayLogin
import foundation.e.apps.domain.source.SourceSelection
import foundation.e.apps.domain.source.SourceSelectionRepository
import foundation.e.apps.feature.auth.session.SessionStateController
@@ -46,24 +44,23 @@ import kotlin.coroutines.cancellation.CancellationException
class LoginWorkflowCoordinator @Inject constructor(
    private val sessionStateController: SessionStateController,
    private val authSessionRepository: AuthSessionRepository,
    private val playStoreAuthManager: PlayStoreAuthManager,
    private val playStoreLoginBootstrapper: PlayStoreLoginBootstrapper,
    private val playStoreAuthStore: PlayStoreAuthStore,
    private val anonymousLoginUseCase: AnonymousLoginUseCase,
    private val googleLoginUseCase: GoogleLoginUseCase,
    private val microgLoginUseCase: MicrogLoginUseCase,
    private val noGoogleLoginUseCase: NoGoogleLoginUseCase,
    private val periodicUpdatesScheduler: PeriodicUpdatesScheduler,
    private val playCredentialStateRepository: PlayCredentialStateRepository,
    private val playLoginPreparationService: PlayLoginPreparationService,
    private val sourceSelectionRepository: SourceSelectionRepository,
) {
    private sealed interface RollbackStateCapture {
        data class Success(val rollbackState: LoginRollbackState) : RollbackStateCapture
        data class Failure(val result: LoginWorkflowResult.Failure) : RollbackStateCapture
        data class Success(val rollbackState: LoginRollbackState) : RollbackStateCapture
    }

    private data class LoginRollbackState(
        val session: AuthSession,
        val playStoreCredentials: PlayStoreAuthStore.CredentialsSnapshot,
        val playCredentialState: PlayCredentialState,
        val sourceSelection: SourceSelection,
    )

@@ -71,7 +68,7 @@ class LoginWorkflowCoordinator @Inject constructor(
        return submitValidatedPlayLogin(
            expectedSession = ::isAnonymousPlaySession,
            preferredStore = AuthStore.PLAY_STORE,
            loginMode = PlayStoreLoginMode.ANONYMOUS,
            preparationRequest = PlayLoginPreparationRequest.Anonymous,
            commitAction = anonymousLoginUseCase::invoke,
        )
    }
@@ -83,8 +80,7 @@ class LoginWorkflowCoordinator @Inject constructor(
        return submitValidatedPlayLogin(
            expectedSession = ::isGooglePlaySession,
            preferredStore = AuthStore.PLAY_STORE,
            loginMode = PlayStoreLoginMode.GOOGLE,
            credentials = PlayStoreBootstrapCredentials(
            preparationRequest = PlayLoginPreparationRequest.Google(
                email = email,
                oauthToken = oauthToken,
            ),
@@ -99,8 +95,7 @@ class LoginWorkflowCoordinator @Inject constructor(
        return submitValidatedPlayLogin(
            expectedSession = ::isMicrogSession,
            preferredStore = AuthStore.PLAY_STORE,
            loginMode = PlayStoreLoginMode.MICROG,
            credentials = PlayStoreBootstrapCredentials(
            preparationRequest = PlayLoginPreparationRequest.Microg(
                email = email,
                oauthToken = oauthToken,
            ),
@@ -119,37 +114,33 @@ class LoginWorkflowCoordinator @Inject constructor(
    private suspend fun submitValidatedPlayLogin(
        expectedSession: (AuthSession) -> Boolean,
        preferredStore: AuthStore,
        loginMode: PlayStoreLoginMode,
        credentials: PlayStoreBootstrapCredentials = PlayStoreBootstrapCredentials(),
        preparationRequest: PlayLoginPreparationRequest,
        commitAction: suspend () -> Unit,
    ): LoginWorkflowResult {
        return when (val rollbackStateCapture = captureRollbackStateSafely()) {
            is RollbackStateCapture.Failure -> rollbackStateCapture.result
            is RollbackStateCapture.Success -> {
                val bootstrapResult = bootstrapPlayLoginCandidate(
                    loginMode = loginMode,
                    credentials = credentials,
                )
                val validatedAuth = bootstrapResult.data
                if (!bootstrapResult.isSuccess() || validatedAuth == null) {
                    return LoginWorkflowResult.Failure(
                when (val preparationResult = preparePlayLoginCandidate(preparationRequest)) {
                    is PlayLoginPreparationResult.Failure ->
                        LoginWorkflowResult.Failure(
                            LoginWorkflowFailure.RefreshValidationFailed(
                                preferredStore = preferredStore,
                            authError = bootstrapResult.toAuthError(StoreType.PLAY_STORE),
                                authError = preparationResult.authError,
                            )
                        )
                }

                    is PlayLoginPreparationResult.Success ->
                        submitCommittedLoginWithRollbackState(
                            rollbackState = rollbackStateCapture.rollbackState,
                            expectedSession = expectedSession,
                            preferredStore = preferredStore,
                            commitAction = commitAction,
                    validatedPlayAuth = validatedAuth,
                            preparedPlayLogin = preparationResult.preparedLogin,
                        )
                }
            }
        }
    }

    private suspend fun submitCommittedLogin(
        expectedSession: (AuthSession) -> Boolean,
@@ -196,11 +187,13 @@ class LoginWorkflowCoordinator @Inject constructor(
        expectedSession: (AuthSession) -> Boolean,
        preferredStore: AuthStore,
        commitAction: suspend () -> Unit,
        validatedPlayAuth: AuthData? = null,
        preparedPlayLogin: PreparedPlayLogin? = null,
    ): LoginWorkflowResult {
        return try {
            commitAction()
            validatedPlayAuth?.let { playStoreAuthManager.setGPlayAuth(it) }
            preparedPlayLogin?.let { preparedLogin ->
                playLoginPreparationService.persistPreparedLogin(preparedLogin)
            }
            sessionStateController.clearLoadedSessions()
            sessionStateController.refreshSessions()
            resolveLoginRefreshResult(rollbackState, expectedSession, preferredStore)
@@ -213,14 +206,10 @@ class LoginWorkflowCoordinator @Inject constructor(
        }
    }

    private suspend fun bootstrapPlayLoginCandidate(
        loginMode: PlayStoreLoginMode,
        credentials: PlayStoreBootstrapCredentials,
    ): ResultSupreme<AuthData?> {
        return playStoreLoginBootstrapper.bootstrapPlayStoreAuth(
            loginMode = loginMode,
            credentials = credentials,
        )
    private suspend fun preparePlayLoginCandidate(
        request: PlayLoginPreparationRequest,
    ): PlayLoginPreparationResult {
        return playLoginPreparationService.prepare(request)
    }

    private suspend fun resolveLoginRefreshResult(
@@ -296,14 +285,14 @@ class LoginWorkflowCoordinator @Inject constructor(
    private suspend fun captureRollbackState(): LoginRollbackState {
        return LoginRollbackState(
            session = authSessionRepository.resolveCurrentSession(),
            playStoreCredentials = playStoreAuthStore.snapshotCredentials(),
            playCredentialState = playCredentialStateRepository.snapshotCredentialState(),
            sourceSelection = sourceSelectionRepository.currentSourceSelection(),
        )
    }

    private suspend fun rollbackFailedLogin(rollbackState: LoginRollbackState) {
        restoreStoreSelection(rollbackState)
        playStoreAuthStore.restoreCredentials(rollbackState.playStoreCredentials)
        playCredentialStateRepository.restoreCredentialState(rollbackState.playCredentialState)
        when (rollbackState.session) {
            AuthSession.Unauthenticated -> authSessionRepository.clearSession()
            else -> authSessionRepository.setSession(rollbackState.session)
+43 −45
Original line number Diff line number Diff line
@@ -18,13 +18,7 @@

package foundation.e.apps.feature.auth.login

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.login.api.PlayStoreAuthManager
import foundation.e.apps.data.login.api.PlayStoreBootstrapCredentials
import foundation.e.apps.data.login.api.PlayStoreLoginBootstrapper
import foundation.e.apps.data.preference.PlayStoreAuthStore
import foundation.e.apps.domain.auth.AuthError
import foundation.e.apps.domain.auth.AuthRefreshEntry
import foundation.e.apps.domain.auth.AuthRefreshSnapshot
@@ -37,6 +31,12 @@ import foundation.e.apps.domain.login.AnonymousLoginUseCase
import foundation.e.apps.domain.login.GoogleLoginUseCase
import foundation.e.apps.domain.login.MicrogLoginUseCase
import foundation.e.apps.domain.login.NoGoogleLoginUseCase
import foundation.e.apps.domain.login.PlayCredentialState
import foundation.e.apps.domain.login.PlayCredentialStateRepository
import foundation.e.apps.domain.login.PlayLoginPreparationRequest
import foundation.e.apps.domain.login.PlayLoginPreparationResult
import foundation.e.apps.domain.login.PlayLoginPreparationService
import foundation.e.apps.domain.login.PreparedPlayLogin
import foundation.e.apps.domain.source.SourceSelection
import foundation.e.apps.domain.source.SourceSelectionRepository
import foundation.e.apps.feature.auth.session.SessionStateController
@@ -54,6 +54,7 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@@ -62,47 +63,38 @@ class LoginWorkflowCoordinatorTest {

    private val authRefreshSnapshotState = MutableStateFlow<AuthRefreshSnapshot?>(null)
    private val currentSessionState = MutableStateFlow<AuthSession>(AuthSession.Unauthenticated)
    private val credentialsSnapshot = PlayStoreAuthStore.CredentialsSnapshot(
        authData = null,
        email = "",
        oauthToken = "",
        aasToken = "",
        authSource = null,
    )
    private val validatedPlayAuth = AuthData(email = "validated@example.com")
    private val playCredentialState = object : PlayCredentialState {}
    private val preparedPlayLogin = object : PreparedPlayLogin {}

    @Mock
    private lateinit var sessionStateController: SessionStateController
    private lateinit var anonymousLoginUseCase: AnonymousLoginUseCase

    @Mock
    private lateinit var authSessionRepository: AuthSessionRepository

    @Mock
    private lateinit var playStoreAuthManager: PlayStoreAuthManager

    @Mock
    private lateinit var playStoreLoginBootstrapper: PlayStoreLoginBootstrapper
    private lateinit var googleLoginUseCase: GoogleLoginUseCase

    @Mock
    private lateinit var playStoreAuthStore: PlayStoreAuthStore
    private lateinit var microgLoginUseCase: MicrogLoginUseCase

    @Mock
    private lateinit var sourceSelectionRepository: SourceSelectionRepository
    private lateinit var noGoogleLoginUseCase: NoGoogleLoginUseCase

    @Mock
    private lateinit var anonymousLoginUseCase: AnonymousLoginUseCase
    private lateinit var periodicUpdatesScheduler: PeriodicUpdatesScheduler

    @Mock
    private lateinit var googleLoginUseCase: GoogleLoginUseCase
    private lateinit var playCredentialStateRepository: PlayCredentialStateRepository

    @Mock
    private lateinit var microgLoginUseCase: MicrogLoginUseCase
    private lateinit var playLoginPreparationService: PlayLoginPreparationService

    @Mock
    private lateinit var noGoogleLoginUseCase: NoGoogleLoginUseCase
    private lateinit var sessionStateController: SessionStateController

    @Mock
    private lateinit var periodicUpdatesScheduler: PeriodicUpdatesScheduler
    private lateinit var sourceSelectionRepository: SourceSelectionRepository

    private lateinit var coordinator: LoginWorkflowCoordinator
    private val sourceSelection = SourceSelection.OPEN_SOURCE_AND_PWA
@@ -119,22 +111,22 @@ class LoginWorkflowCoordinatorTest {
        whenever(authSessionRepository.currentSession).thenReturn(currentSessionState)
        runBlocking {
            whenever(authSessionRepository.resolveCurrentSession()).thenAnswer { currentSessionState.value }
            whenever(playStoreAuthStore.snapshotCredentials()).thenReturn(credentialsSnapshot)
            whenever(playStoreLoginBootstrapper.bootstrapPlayStoreAuth(any(), any()))
                .thenReturn(ResultSupreme.Success(validatedPlayAuth))
            whenever(playCredentialStateRepository.snapshotCredentialState()).thenReturn(playCredentialState)
            whenever(playLoginPreparationService.prepare(any())).thenReturn(
                PlayLoginPreparationResult.Success(preparedPlayLogin)
            )
            whenever(sourceSelectionRepository.currentSourceSelection()).thenReturn(sourceSelection)
        }
        coordinator = LoginWorkflowCoordinator(
            sessionStateController = sessionStateController,
            authSessionRepository = authSessionRepository,
            playStoreAuthManager = playStoreAuthManager,
            playStoreLoginBootstrapper = playStoreLoginBootstrapper,
            playStoreAuthStore = playStoreAuthStore,
            anonymousLoginUseCase = anonymousLoginUseCase,
            googleLoginUseCase = googleLoginUseCase,
            microgLoginUseCase = microgLoginUseCase,
            noGoogleLoginUseCase = noGoogleLoginUseCase,
            periodicUpdatesScheduler = periodicUpdatesScheduler,
            playCredentialStateRepository = playCredentialStateRepository,
            playLoginPreparationService = playLoginPreparationService,
            sourceSelectionRepository = sourceSelectionRepository,
        )
    }
@@ -148,7 +140,7 @@ class LoginWorkflowCoordinatorTest {

        assertThat(result).isEqualTo(LoginWorkflowResult.Success)
        verify(anonymousLoginUseCase).invoke()
        verify(playStoreAuthManager).setGPlayAuth(validatedPlayAuth)
        verify(playLoginPreparationService).persistPreparedLogin(preparedPlayLogin)
        verify(sessionStateController).refreshSessions(any())
        verify(periodicUpdatesScheduler).refreshPeriodicUpdatesSchedule()
    }
@@ -169,14 +161,20 @@ class LoginWorkflowCoordinatorTest {
    fun `submitGoogleLogin returns refresh validation failure without committing when bootstrap fails`() = runTest(mainCoroutineRule.testDispatcher) {
        runBlocking {
            whenever(
                playStoreLoginBootstrapper.bootstrapPlayStoreAuth(
                    PlayStoreLoginMode.GOOGLE,
                    PlayStoreBootstrapCredentials(
                playLoginPreparationService.prepare(
                    PlayLoginPreparationRequest.Google(
                        email = "user@gmail.com",
                        oauthToken = "oauth-token",
                    ),
                    )
            ).thenReturn(ResultSupreme.Error("bootstrap failed"))
                )
            ).thenReturn(
                PlayLoginPreparationResult.Failure(
                    AuthError.LoginRequired(
                        store = AuthStore.PLAY_STORE,
                        message = "bootstrap failed",
                    )
                )
            )
        }

        val result = coordinator.submitGoogleLogin("user@gmail.com", "oauth-token")
@@ -193,9 +191,9 @@ class LoginWorkflowCoordinatorTest {
                ),
            ),
        )
        verify(sessionStateController, org.mockito.kotlin.never()).refreshSessions(any())
        verify(googleLoginUseCase, org.mockito.kotlin.never()).invoke(any(), any())
        verify(playStoreAuthStore, org.mockito.kotlin.never()).restoreCredentials(any())
        verify(sessionStateController, never()).refreshSessions(any())
        verify(googleLoginUseCase, never()).invoke(any(), any())
        verify(playCredentialStateRepository, never()).restoreCredentialState(any())
    }

    @Test
@@ -233,7 +231,7 @@ class LoginWorkflowCoordinatorTest {
                ),
            ),
        )
        verify(playStoreAuthStore).restoreCredentials(credentialsSnapshot)
        verify(playCredentialStateRepository).restoreCredentialState(playCredentialState)
        verify(authSessionRepository).setSession(AuthSession.OpenSourceSession)
    }

@@ -263,7 +261,7 @@ class LoginWorkflowCoordinatorTest {
            .hasMessageThat()
            .isEqualTo("refresh failed")
        verify(authSessionRepository).setSession(AuthSession.OpenSourceSession)
        verify(playStoreAuthStore).restoreCredentials(credentialsSnapshot)
        verify(playCredentialStateRepository).restoreCredentialState(playCredentialState)
    }

    @Test
@@ -302,7 +300,7 @@ class LoginWorkflowCoordinatorTest {
            .hasMessageThat()
            .isEqualTo("submission failed")
        verify(authSessionRepository).setSession(AuthSession.OpenSourceSession)
        verify(playStoreAuthStore).restoreCredentials(credentialsSnapshot)
        verify(playCredentialStateRepository).restoreCredentialState(playCredentialState)
    }

    @Test
@@ -344,7 +342,7 @@ class LoginWorkflowCoordinatorTest {
            ),
        )
        runBlocking {
            whenever(playStoreAuthStore.restoreCredentials(credentialsSnapshot))
            whenever(playCredentialStateRepository.restoreCredentialState(playCredentialState))
                .thenThrow(IllegalStateException("rollback failed"))
        }

+122 −0
Original line number Diff line number Diff line
/*
 * 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 <https://www.gnu.org/licenses/>.
 */

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

import com.aurora.gplayapi.data.models.AuthData
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.login.api.PlayStoreAuthManager
import foundation.e.apps.data.login.api.PlayStoreBootstrapCredentials
import foundation.e.apps.data.login.api.PlayStoreLoginBootstrapper
import foundation.e.apps.data.login.core.StoreType
import foundation.e.apps.data.login.core.toAuthError
import foundation.e.apps.data.preference.PlayStoreAuthStore
import foundation.e.apps.domain.auth.PlayStoreLoginMode
import foundation.e.apps.domain.login.PlayCredentialState
import foundation.e.apps.domain.login.PlayCredentialStateRepository
import foundation.e.apps.domain.login.PlayLoginPreparationRequest
import foundation.e.apps.domain.login.PlayLoginPreparationResult
import foundation.e.apps.domain.login.PlayLoginPreparationService
import foundation.e.apps.domain.login.PreparedPlayLogin
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PlayLoginPreparationServiceImpl @Inject constructor(
    private val playStoreAuthManager: PlayStoreAuthManager,
    private val playStoreAuthStore: PlayStoreAuthStore,
    private val playStoreLoginBootstrapper: PlayStoreLoginBootstrapper,
) : PlayCredentialStateRepository, PlayLoginPreparationService {
    override suspend fun prepare(
        request: PlayLoginPreparationRequest,
    ): PlayLoginPreparationResult {
        val bootstrapResult = when (request) {
            PlayLoginPreparationRequest.Anonymous ->
                bootstrap(
                    loginMode = PlayStoreLoginMode.ANONYMOUS,
                    credentials = PlayStoreBootstrapCredentials(),
                )

            is PlayLoginPreparationRequest.Google ->
                bootstrap(
                    loginMode = PlayStoreLoginMode.GOOGLE,
                    credentials = PlayStoreBootstrapCredentials(
                        email = request.email,
                        oauthToken = request.oauthToken,
                    ),
                )

            is PlayLoginPreparationRequest.Microg ->
                bootstrap(
                    loginMode = PlayStoreLoginMode.MICROG,
                    credentials = PlayStoreBootstrapCredentials(
                        email = request.email,
                        oauthToken = request.oauthToken,
                    ),
                )
        }

        val validatedAuth = bootstrapResult.data
        return if (bootstrapResult.isSuccess() && validatedAuth != null) {
            PlayLoginPreparationResult.Success(
                preparedLogin = PreparedPlayLoginImpl(validatedAuth),
            )
        } else {
            PlayLoginPreparationResult.Failure(
                authError = bootstrapResult.toAuthError(StoreType.PLAY_STORE),
            )
        }
    }

    override suspend fun persistPreparedLogin(
        preparedLogin: PreparedPlayLogin,
    ) {
        val resolvedPreparedLogin = preparedLogin as? PreparedPlayLoginImpl
            ?: error("Unsupported prepared Play login implementation: ${preparedLogin::class.qualifiedName}")
        playStoreAuthManager.setGPlayAuth(resolvedPreparedLogin.authData)
    }

    override suspend fun snapshotCredentialState(): PlayCredentialState {
        return PlayCredentialStateImpl(playStoreAuthStore.snapshotCredentials())
    }

    override suspend fun restoreCredentialState(
        state: PlayCredentialState,
    ) {
        val resolvedState = state as? PlayCredentialStateImpl
            ?: error("Unsupported Play credential state implementation: ${state::class.qualifiedName}")
        playStoreAuthStore.restoreCredentials(resolvedState.snapshot)
    }

    private suspend fun bootstrap(
        loginMode: PlayStoreLoginMode,
        credentials: PlayStoreBootstrapCredentials,
    ): ResultSupreme<AuthData?> {
        return playStoreLoginBootstrapper.bootstrapPlayStoreAuth(
            loginMode = loginMode,
            credentials = credentials,
        )
    }

    private data class PlayCredentialStateImpl(
        val snapshot: PlayStoreAuthStore.CredentialsSnapshot,
    ) : PlayCredentialState

    private data class PreparedPlayLoginImpl(
        val authData: AuthData,
    ) : PreparedPlayLogin
}
+224 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading