From 37fe5709f75996bb16fa0b8def05e55968f3bb68 Mon Sep 17 00:00:00 2001 From: toluo-stripe Date: Thu, 26 Sep 2024 13:17:37 -0400 Subject: [PATCH] Add Sign Up screen skeleton --- .../com/stripe/android/link/LinkActivity.kt | 22 +++++++-- .../android/link/LinkActivityContract.kt | 5 +++ .../android/link/LinkActivityViewModel.kt | 12 ----- .../java/com/stripe/android/link/LinkState.kt | 3 +- .../com/stripe/android/link/LinkViewModel.kt | 2 - .../stripe/android/link/ui/signup/Model.kt | 18 ++++++++ .../android/link/ui/signup/SignUpScreen.kt | 25 +++++++++-- .../android/link/ui/signup/SignUpViewModel.kt | 40 +++++++++++++++++ .../stripe/android/link/LinkActivityTest.kt | 13 ------ .../link/ui/signup/SignUpViewModelTest.kt | 45 +++++++++++++++++++ 10 files changed, 150 insertions(+), 35 deletions(-) create mode 100644 link/src/main/java/com/stripe/android/link/ui/signup/Model.kt create mode 100644 link/src/main/java/com/stripe/android/link/ui/signup/SignUpViewModel.kt create mode 100644 link/src/test/java/com/stripe/android/link/ui/signup/SignUpViewModelTest.kt diff --git a/link/src/main/java/com/stripe/android/link/LinkActivity.kt b/link/src/main/java/com/stripe/android/link/LinkActivity.kt index 8a53c62c5cc..61d0d57bb35 100644 --- a/link/src/main/java/com/stripe/android/link/LinkActivity.kt +++ b/link/src/main/java/com/stripe/android/link/LinkActivity.kt @@ -1,12 +1,16 @@ package com.stripe.android.link +import android.app.Activity +import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.LaunchedEffect +import androidx.core.os.bundleOf import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -14,6 +18,7 @@ import androidx.navigation.compose.rememberNavController import com.stripe.android.link.ui.cardedit.CardEditScreen import com.stripe.android.link.ui.paymentmenthod.PaymentMethodScreen import com.stripe.android.link.ui.signup.SignUpScreen +import com.stripe.android.link.ui.signup.SignUpViewModel import com.stripe.android.link.ui.verification.VerificationScreen import com.stripe.android.link.ui.wallet.WalletScreen @@ -35,8 +40,15 @@ internal class LinkActivity : AppCompatActivity() { viewModel.effect.collect { effect -> when (effect) { LinkEffect.GoBack -> navController.popBackStack() - is LinkEffect.NavigateTo -> { - navController.navigate(effect.screen.route) + is LinkEffect.SendResult -> { + val bundle = bundleOf( + LinkActivityContract.EXTRA_RESULT to LinkActivityContract.Result(effect.result) + ) + this@LinkActivity.setResult( + Activity.RESULT_OK, + Intent().putExtras(bundle) + ) + this@LinkActivity.finish() } } } @@ -47,7 +59,11 @@ internal class LinkActivity : AppCompatActivity() { startDestination = LinkScreen.SignUp.route ) { composable(LinkScreen.SignUp.route) { - SignUpScreen() + val vm = viewModel() + SignUpScreen( + viewModel = vm, + navController = navController + ) } composable(LinkScreen.Verification.route) { diff --git a/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt b/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt index 1e3c0141b3e..df495019a90 100644 --- a/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt +++ b/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt @@ -39,4 +39,9 @@ class LinkActivityContract @Inject internal constructor( data class Result( val linkResult: LinkActivityResult ) + + companion object { + const val EXTRA_RESULT = + "com.stripe.android.link.LinkActivityContract.extra_result" + } } diff --git a/link/src/main/java/com/stripe/android/link/LinkActivityViewModel.kt b/link/src/main/java/com/stripe/android/link/LinkActivityViewModel.kt index 6f1c23614b9..44eca6f578a 100644 --- a/link/src/main/java/com/stripe/android/link/LinkActivityViewModel.kt +++ b/link/src/main/java/com/stripe/android/link/LinkActivityViewModel.kt @@ -9,10 +9,8 @@ internal class LinkActivityViewModel : LinkViewModel { - // These are placeholder actions, they may be removed return when (action) { LinkAction.BackPressed -> handleBackPressed() - LinkAction.WalletClicked -> handleWalletClicked() } } @@ -20,16 +18,6 @@ internal class LinkActivityViewModel : LinkViewModel { - return flowOf( - value = LinkResult.SendEffect( - effect = LinkEffect.NavigateTo( - screen = LinkScreen.Wallet - ) - ) - ) - } - override fun resultToState(currentState: LinkState, result: LinkResult) = currentState override fun resultToEffect(result: LinkResult): LinkEffect? { diff --git a/link/src/main/java/com/stripe/android/link/LinkState.kt b/link/src/main/java/com/stripe/android/link/LinkState.kt index 84968f77cfd..fead76363b0 100644 --- a/link/src/main/java/com/stripe/android/link/LinkState.kt +++ b/link/src/main/java/com/stripe/android/link/LinkState.kt @@ -4,7 +4,6 @@ internal object LinkState internal sealed interface LinkAction { data object BackPressed : LinkAction - data object WalletClicked : LinkAction } internal sealed interface LinkResult { @@ -13,5 +12,5 @@ internal sealed interface LinkResult { internal sealed interface LinkEffect { data object GoBack : LinkEffect - data class NavigateTo(val screen: LinkScreen) : LinkEffect + data class SendResult(val result: LinkActivityResult) : LinkEffect } diff --git a/link/src/main/java/com/stripe/android/link/LinkViewModel.kt b/link/src/main/java/com/stripe/android/link/LinkViewModel.kt index e4b4b352415..2237a1de7b9 100644 --- a/link/src/main/java/com/stripe/android/link/LinkViewModel.kt +++ b/link/src/main/java/com/stripe/android/link/LinkViewModel.kt @@ -12,8 +12,6 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -// This will be used as a base for all Link screen viewmodels to create a consistent architecture -// There should be no more logic here besides updating state and sending effects internal abstract class LinkViewModel( initialState: State, ) : ViewModel() { diff --git a/link/src/main/java/com/stripe/android/link/ui/signup/Model.kt b/link/src/main/java/com/stripe/android/link/ui/signup/Model.kt new file mode 100644 index 00000000000..0a03cc95405 --- /dev/null +++ b/link/src/main/java/com/stripe/android/link/ui/signup/Model.kt @@ -0,0 +1,18 @@ +package com.stripe.android.link.ui.signup + +data class SignUpViewState( + val loading: Boolean = false +) + +sealed interface SignUpAction { + data object SignUpClicked : SignUpAction +} + +sealed interface SignUpResult { + data object ShowLoader : SignUpResult + data class SendEffect(val effect: SignUpEffect) : SignUpResult +} + +sealed interface SignUpEffect { + data object NavigateToWallet : SignUpEffect +} diff --git a/link/src/main/java/com/stripe/android/link/ui/signup/SignUpScreen.kt b/link/src/main/java/com/stripe/android/link/ui/signup/SignUpScreen.kt index 877500d116a..6c38cb6cdf5 100644 --- a/link/src/main/java/com/stripe/android/link/ui/signup/SignUpScreen.kt +++ b/link/src/main/java/com/stripe/android/link/ui/signup/SignUpScreen.kt @@ -1,9 +1,28 @@ package com.stripe.android.link.ui.signup -import androidx.compose.material.Text +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.navigation.NavHostController +import com.stripe.android.link.LinkScreen +import com.stripe.android.uicore.utils.collectAsState @Composable -internal fun SignUpScreen() { - Text(text = "SignUpScreen") +internal fun SignUpScreen( + viewModel: SignUpViewModel, + navController: NavHostController +) { + LaunchedEffect("SignUpEffects") { + viewModel.effect.collect { effect -> + when (effect) { + SignUpEffect.NavigateToWallet -> navController.navigate(LinkScreen.Wallet.route) + } + } + } + val state by viewModel.state.collectAsState() + AnimatedVisibility(visible = state.loading) { + CircularProgressIndicator() + } } diff --git a/link/src/main/java/com/stripe/android/link/ui/signup/SignUpViewModel.kt b/link/src/main/java/com/stripe/android/link/ui/signup/SignUpViewModel.kt new file mode 100644 index 00000000000..dc81fa302c5 --- /dev/null +++ b/link/src/main/java/com/stripe/android/link/ui/signup/SignUpViewModel.kt @@ -0,0 +1,40 @@ +package com.stripe.android.link.ui.signup + +import com.stripe.android.link.LinkViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +internal class SignUpViewModel : LinkViewModel( + initialState = SignUpViewState() +) { + override fun actionToResult(action: SignUpAction): Flow { + return when (action) { + SignUpAction.SignUpClicked -> handleSignUpClicked() + } + } + + private fun handleSignUpClicked() = flow { + emit(SignUpResult.ShowLoader) + emit( + value = SignUpResult.SendEffect( + effect = SignUpEffect.NavigateToWallet + ) + ) + } + + override fun resultToState(currentState: SignUpViewState, result: SignUpResult): SignUpViewState { + return when (result) { + is SignUpResult.SendEffect -> currentState + SignUpResult.ShowLoader -> { + currentState.copy(loading = true) + } + } + } + + override fun resultToEffect(result: SignUpResult): SignUpEffect? { + return when (result) { + is SignUpResult.SendEffect -> result.effect + SignUpResult.ShowLoader -> null + } + } +} diff --git a/link/src/test/java/com/stripe/android/link/LinkActivityTest.kt b/link/src/test/java/com/stripe/android/link/LinkActivityTest.kt index 4119227e918..e8df2292585 100644 --- a/link/src/test/java/com/stripe/android/link/LinkActivityTest.kt +++ b/link/src/test/java/com/stripe/android/link/LinkActivityTest.kt @@ -53,19 +53,6 @@ internal class LinkActivityTest { } } - @Test - fun `test that navigator navigates to wallet on wallet clicked`() { - val vm = LinkActivityViewModel() - val scenario = activityScenario(viewModel = vm) - scenario.launchTest { - vm.handleAction(LinkAction.WalletClicked) - - composeTestRule.waitForIdle() - - verify(navHostController).navigate(LinkScreen.Wallet.route) - } - } - private fun InjectableActivityScenario.launchTest( startIntent: Intent = Intent(context, LinkActivity::class.java), block: (LinkActivity) -> Unit diff --git a/link/src/test/java/com/stripe/android/link/ui/signup/SignUpViewModelTest.kt b/link/src/test/java/com/stripe/android/link/ui/signup/SignUpViewModelTest.kt new file mode 100644 index 00000000000..b55b7cb9df6 --- /dev/null +++ b/link/src/test/java/com/stripe/android/link/ui/signup/SignUpViewModelTest.kt @@ -0,0 +1,45 @@ +package com.stripe.android.link.ui.signup + +import app.cash.turbine.test +import com.google.common.truth.Truth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SignUpViewModelTest { + private val dispatcher = UnconfinedTestDispatcher() + private val vm = SignUpViewModel() + + @Before + fun setUp() { + Dispatchers.setMain(dispatcher) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `test that viewmodel has correct initial state`() = runTest(dispatcher) { + vm.state.test { + Truth.assertThat(awaitItem()).isEqualTo(SignUpViewState()) + } + } + + @Test + fun `test that correct effect is emitted on successful sign up`() = runTest(dispatcher) { + vm.effect.test { + vm.handleAction(SignUpAction.SignUpClicked) + Truth.assertThat(awaitItem()).isEqualTo(SignUpEffect.NavigateToWallet) + } + } +}