diff --git a/link/src/main/java/com/stripe/android/link/ui/LinkContent.kt b/link/src/main/java/com/stripe/android/link/ui/LinkContent.kt index 70aa7d81108..6ac9815c958 100644 --- a/link/src/main/java/com/stripe/android/link/ui/LinkContent.kt +++ b/link/src/main/java/com/stripe/android/link/ui/LinkContent.kt @@ -37,6 +37,7 @@ import com.stripe.android.link.ui.signup.SignUpViewModel import com.stripe.android.link.ui.verification.VerificationScreen import com.stripe.android.link.ui.verification.VerificationViewModel import com.stripe.android.link.ui.wallet.WalletScreen +import com.stripe.android.link.ui.wallet.WalletViewModel import com.stripe.android.ui.core.CircularProgressIndicator import kotlinx.coroutines.launch @@ -155,7 +156,17 @@ private fun Screens( } composable(LinkScreen.Wallet.route) { - WalletScreen() + val linkAccount = getLinkAccount() + ?: return@composable dismissWithResult(LinkActivityResult.Failed(NoLinkAccountFoundException())) + val viewModel: WalletViewModel = linkViewModel { parentComponent -> + WalletViewModel.factory( + parentComponent = parentComponent, + linkAccount = linkAccount, + navigate = { _, _ -> }, + dismissWithResult = dismissWithResult + ) + } + WalletScreen(viewModel) } composable(LinkScreen.CardEdit.route) { diff --git a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletScreen.kt b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletScreen.kt index 05acb8633f2..5de98bce0a7 100644 --- a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletScreen.kt +++ b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletScreen.kt @@ -48,6 +48,19 @@ internal fun WalletScreen( ) { val state by viewModel.uiState.collectAsState() + WalletBody( + state = state, + onItemSelected = viewModel::onItemSelected, + onExpandedChanged = viewModel::setExpanded + ) +} + +@Composable +internal fun WalletBody( + state: WalletUiState, + onItemSelected: (ConsumerPaymentDetails.PaymentDetails) -> Unit, + onExpandedChanged: (Boolean) -> Unit, +) { if (state.paymentDetailsList.isEmpty()) { Box( modifier = Modifier @@ -56,6 +69,7 @@ internal fun WalletScreen( ) { CircularProgressIndicator() } + return } val focusManager = LocalFocusManager.current @@ -79,32 +93,26 @@ internal fun WalletScreen( if (state.isExpanded || selectedItem == null) { ExpandedPaymentDetails( uiState = state, - onItemSelected = viewModel::onItemSelected, + onItemSelected = onItemSelected, onMenuButtonClick = {}, onAddNewPaymentMethodClick = {}, - onCollapse = {} + onCollapse = { + onExpandedChanged(false) + } ) } else { CollapsedPaymentDetails( selectedPaymentMethod = selectedItem, enabled = !state.primaryButtonState.isBlocking, - onClick = {} + onClick = { + onExpandedChanged(true) + } ) } } AnimatedVisibility(state.showBankAccountTerms) { - Html( - html = stringResource(R.string.stripe_wallet_bank_account_terms).replaceHyperlinks(), - color = MaterialTheme.colors.onSecondary, - style = MaterialTheme.typography.caption, - modifier = Modifier - .fillMaxWidth() - .padding(top = 12.dp), - urlSpanStyle = SpanStyle( - color = MaterialTheme.colors.primary - ) - ) + BankAccountTerms() } } } @@ -182,32 +190,10 @@ private fun ExpandedPaymentDetails( ) ) { item { - Row( - modifier = Modifier - .height(44.dp) - .clickable(enabled = isEnabled, onClick = onCollapse), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(id = R.string.stripe_wallet_expanded_title), - modifier = Modifier - .padding(start = HorizontalPadding, top = 20.dp), - color = MaterialTheme.colors.onPrimary, - style = MaterialTheme.typography.button - ) - Spacer(modifier = Modifier.weight(1f)) - Icon( - painter = painterResource(id = R.drawable.stripe_link_chevron), - contentDescription = stringResource(id = R.string.stripe_wallet_expand_accessibility), - modifier = Modifier - .padding(top = 20.dp, end = 22.dp) - .rotate(180f) - .semantics { - testTag = "ChevronIcon" - }, - tint = MaterialTheme.colors.onPrimary - ) - } + ExpandedRowHeader( + isEnabled = isEnabled, + onCollapse = onCollapse + ) } items( @@ -231,31 +217,92 @@ private fun ExpandedPaymentDetails( } item { - Row( - modifier = Modifier - .fillMaxWidth() - .height(60.dp) - .clickable(enabled = isEnabled, onClick = onAddNewPaymentMethodClick), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(id = R.drawable.stripe_link_add_green), - contentDescription = null, - modifier = Modifier.padding(start = HorizontalPadding, end = 12.dp), - tint = Color.Unspecified - ) - Text( - text = stringResource(id = R.string.stripe_add_payment_method), - modifier = Modifier.padding(end = HorizontalPadding), - color = MaterialTheme.linkColors.actionLabel, - style = MaterialTheme.typography.button - ) - } + AddPaymentMethodRow( + isEnabled = isEnabled, + onAddNewPaymentMethodClick = onAddNewPaymentMethodClick + ) } } } +@Composable +private fun ExpandedRowHeader( + isEnabled: Boolean, + onCollapse: () -> Unit, +) { + Row( + modifier = Modifier + .height(44.dp) + .clickable(enabled = isEnabled, onClick = onCollapse), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.stripe_wallet_expanded_title), + modifier = Modifier + .padding(start = HorizontalPadding, top = 20.dp), + color = MaterialTheme.colors.onPrimary, + style = MaterialTheme.typography.button + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(id = R.drawable.stripe_link_chevron), + contentDescription = stringResource(id = R.string.stripe_wallet_expand_accessibility), + modifier = Modifier + .padding(top = 20.dp, end = 22.dp) + .rotate(CHEVRON_ICON_ROTATION) + .semantics { + testTag = "ChevronIcon" + }, + tint = MaterialTheme.colors.onPrimary + ) + } +} + +@Composable +private fun AddPaymentMethodRow( + isEnabled: Boolean, + onAddNewPaymentMethodClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(60.dp) + .clickable(enabled = isEnabled, onClick = onAddNewPaymentMethodClick), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.stripe_link_add_green), + contentDescription = null, + modifier = Modifier.padding(start = HorizontalPadding, end = 12.dp), + tint = Color.Unspecified + ) + Text( + text = stringResource(id = R.string.stripe_add_payment_method), + modifier = Modifier.padding(end = HorizontalPadding), + color = MaterialTheme.linkColors.actionLabel, + style = MaterialTheme.typography.button + ) + } +} + +@Composable +private fun BankAccountTerms() { + Html( + html = stringResource(R.string.stripe_wallet_bank_account_terms).replaceHyperlinks(), + color = MaterialTheme.colors.onSecondary, + style = MaterialTheme.typography.caption, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp), + urlSpanStyle = SpanStyle( + color = MaterialTheme.colors.primary + ) + ) +} + private fun String.replaceHyperlinks() = this.replace( "", "" ).replace("", "") + +private const val CHEVRON_ICON_ROTATION = 180f diff --git a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletUiState.kt b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletUiState.kt index e101c92b58d..25a1149462f 100644 --- a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletUiState.kt +++ b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletUiState.kt @@ -6,11 +6,10 @@ import com.stripe.android.model.ConsumerPaymentDetails @Immutable internal data class WalletUiState( - val supportedTypes: Set, val paymentDetailsList: List, val selectedItem: ConsumerPaymentDetails.PaymentDetails?, val isProcessing: Boolean, - val isExpanded: Boolean = false + val isExpanded: Boolean ) { val showBankAccountTerms = selectedItem is ConsumerPaymentDetails.BankAccount diff --git a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletViewModel.kt b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletViewModel.kt index e3082913b5f..84f74c68de4 100644 --- a/link/src/main/java/com/stripe/android/link/ui/wallet/WalletViewModel.kt +++ b/link/src/main/java/com/stripe/android/link/ui/wallet/WalletViewModel.kt @@ -32,10 +32,10 @@ internal class WalletViewModel @Inject constructor( private val _uiState = MutableStateFlow( value = WalletUiState( - supportedTypes = stripeIntent.supportedPaymentMethodTypes(linkAccount), paymentDetailsList = emptyList(), selectedItem = null, - isProcessing = false + isProcessing = false, + isExpanded = false ) ) @@ -52,7 +52,7 @@ internal class WalletViewModel @Inject constructor( viewModelScope.launch { linkAccountManager.listPaymentDetails( - paymentMethodTypes = _uiState.value.supportedTypes + paymentMethodTypes = stripeIntent.supportedPaymentMethodTypes(linkAccount) ).fold( onSuccess = { response -> _uiState.update { @@ -82,6 +82,12 @@ internal class WalletViewModel @Inject constructor( } } + fun setExpanded(expanded: Boolean) { + _uiState.update { + it.copy(isExpanded = expanded) + } + } + companion object { fun factory( parentComponent: NativeLinkComponent, diff --git a/link/src/test/java/com/stripe/android/link/TestFactory.kt b/link/src/test/java/com/stripe/android/link/TestFactory.kt index 85e10f6a959..9cd4a0602dd 100644 --- a/link/src/test/java/com/stripe/android/link/TestFactory.kt +++ b/link/src/test/java/com/stripe/android/link/TestFactory.kt @@ -82,6 +82,19 @@ internal object TestFactory { ) ) + private val CONSUMER_PAYMENT_DETAILS_BANK_ACCOUNT = ConsumerPaymentDetails.BankAccount( + id = "pm_124", + last4 = "4242", + isDefault = false, + bankName = "Stripe Test Bank", + bankIconCode = null + ) + + private val CONSUMER_PAYMENT_DETAILS_PASSTHROUGH = ConsumerPaymentDetails.Passthrough( + id = "pm_125", + last4 = "4242", + ) + val LINK_NEW_PAYMENT_DETAILS = LinkPaymentDetails.New( paymentDetails = CONSUMER_PAYMENT_DETAILS_CARD, paymentMethodCreateParams = PAYMENT_METHOD_CREATE_PARAMS, @@ -92,7 +105,9 @@ internal object TestFactory { val CONSUMER_PAYMENT_DETAILS: ConsumerPaymentDetails = ConsumerPaymentDetails( paymentDetails = listOf( - CONSUMER_PAYMENT_DETAILS_CARD + CONSUMER_PAYMENT_DETAILS_CARD, + CONSUMER_PAYMENT_DETAILS_BANK_ACCOUNT, + CONSUMER_PAYMENT_DETAILS_PASSTHROUGH, ) ) diff --git a/link/src/test/java/com/stripe/android/link/ui/wallet/WalletScreenScreenshotTest.kt b/link/src/test/java/com/stripe/android/link/ui/wallet/WalletScreenScreenshotTest.kt new file mode 100644 index 00000000000..e6470146153 --- /dev/null +++ b/link/src/test/java/com/stripe/android/link/ui/wallet/WalletScreenScreenshotTest.kt @@ -0,0 +1,77 @@ +package com.stripe.android.link.ui.wallet + +import com.stripe.android.link.TestFactory +import com.stripe.android.link.theme.DefaultLinkTheme +import com.stripe.android.model.ConsumerPaymentDetails +import com.stripe.android.screenshottesting.PaparazziRule +import org.junit.Rule +import org.junit.Test + +internal class WalletScreenScreenshotTest { + @get:Rule + val paparazziRule = PaparazziRule() + + @Test + fun testEmptyState() { + snapshot( + state = WalletUiState( + paymentDetailsList = emptyList(), + selectedItem = null, + isProcessing = false, + isExpanded = false + ) + ) + } + + @Test + fun testCollapsedState() { + snapshot( + state = WalletUiState( + paymentDetailsList = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails, + selectedItem = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails.firstOrNull(), + isProcessing = false, + isExpanded = false + ) + ) + } + + @Test + fun testExpandedState() { + snapshot( + state = WalletUiState( + paymentDetailsList = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails, + selectedItem = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails.firstOrNull(), + isProcessing = false, + isExpanded = true + ) + ) + } + + @Test + fun testBankAccountSelectedState() { + snapshot( + state = WalletUiState( + paymentDetailsList = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails, + selectedItem = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails.firstOrNull { + it is ConsumerPaymentDetails.BankAccount + }, + isProcessing = false, + isExpanded = true + ) + ) + } + + private fun snapshot( + state: WalletUiState + ) { + paparazziRule.snapshot { + DefaultLinkTheme { + WalletBody( + state = state, + onItemSelected = {}, + onExpandedChanged = {} + ) + } + } + } +} diff --git a/link/src/test/java/com/stripe/android/link/ui/wallet/WalletViewModelTest.kt b/link/src/test/java/com/stripe/android/link/ui/wallet/WalletViewModelTest.kt index 4ac37082c0f..b2853e272b4 100644 --- a/link/src/test/java/com/stripe/android/link/ui/wallet/WalletViewModelTest.kt +++ b/link/src/test/java/com/stripe/android/link/ui/wallet/WalletViewModelTest.kt @@ -54,10 +54,10 @@ class WalletViewModelTest { assertThat(viewModel.uiState.value).isEqualTo( WalletUiState( - supportedTypes = TestFactory.LINK_CONFIGURATION.stripeIntent.paymentMethodTypes.toSet(), paymentDetailsList = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails, selectedItem = TestFactory.CONSUMER_PAYMENT_DETAILS.paymentDetails.firstOrNull(), - isProcessing = false + isProcessing = false, + isExpanded = false ) ) assertThat(viewModel.uiState.value.primaryButtonState).isEqualTo(PrimaryButtonState.Disabled) diff --git a/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testBankAccountSelectedState[].png b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testBankAccountSelectedState[].png new file mode 100644 index 00000000000..991097a8a9f Binary files /dev/null and b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testBankAccountSelectedState[].png differ diff --git a/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testCollapsedState[].png b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testCollapsedState[].png new file mode 100644 index 00000000000..b71c72a355d Binary files /dev/null and b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testCollapsedState[].png differ diff --git a/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testEmptyState[].png b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testEmptyState[].png new file mode 100644 index 00000000000..cd69858e104 Binary files /dev/null and b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testEmptyState[].png differ diff --git a/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testExpandedState[].png b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testExpandedState[].png new file mode 100644 index 00000000000..aa088c73d1a Binary files /dev/null and b/link/src/test/snapshots/images/com.stripe.android.link.ui.wallet_WalletScreenScreenshotTest_testExpandedState[].png differ