Skip to content

Commit

Permalink
Link Payment Method Rows UI (#9681)
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe authored Nov 25, 2024
1 parent 742ae45 commit 3c205d7
Show file tree
Hide file tree
Showing 33 changed files with 485 additions and 0 deletions.
7 changes: 7 additions & 0 deletions link/api/link.api
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ public final class com/stripe/android/link/ui/verification/ComposableSingletons$
public final fun getLambda-1$link_release ()Lkotlin/jvm/functions/Function3;
}

public final class com/stripe/android/link/ui/wallet/ComposableSingletons$PaymentDetailsKt {
public static final field INSTANCE Lcom/stripe/android/link/ui/wallet/ComposableSingletons$PaymentDetailsKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public fun <init> ()V
public final fun getLambda-1$link_release ()Lkotlin/jvm/functions/Function2;
}

public final class com/stripe/android/link/utils/ComposableSingletons$InlineContentTemplateBuilderKt {
public static final field INSTANCE Lcom/stripe/android/link/utils/ComposableSingletons$InlineContentTemplateBuilderKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
Expand Down
16 changes: 16 additions & 0 deletions link/res/drawable/stripe_link_add_green.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,0L18,0A6,6 0,0 1,24 6L24,18A6,6 0,0 1,18 24L6,24A6,6 0,0 1,0 18L0,6A6,6 0,0 1,6 0z"
android:fillColor="#33DDB3"
android:fillAlpha="0.15"/>
<path
android:pathData="M12.75,11.25H17.25C17.664,11.25 18,11.586 18,12C18,12.414 17.664,12.75 17.25,12.75H12.75V17.25C12.75,17.664 12.414,18 12,18C11.586,18 11.25,17.664 11.25,17.25V12.75H6.75C6.336,12.75 6,12.414 6,12C6,11.586 6.336,11.25 6.75,11.25H11.25V6.75C11.25,6.336 11.586,6 12,6C12.414,6 12.75,6.336 12.75,6.75V11.25Z"
android:strokeAlpha="0.9"
android:fillColor="#05A87F"
android:fillType="evenOdd"
android:fillAlpha="0.9"/>
</vector>
9 changes: 9 additions & 0 deletions link/res/drawable/stripe_link_bank.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="19dp"
android:height="17dp"
android:viewportWidth="19"
android:viewportHeight="17">
<path
android:pathData="M1.328,5.047C1.328,5.43 1.625,5.797 2.133,5.797H16.852C17.359,5.797 17.656,5.43 17.656,5.047C17.656,4.766 17.5,4.539 17.164,4.328L10.508,0.492C10.18,0.305 9.828,0.203 9.492,0.203C9.156,0.203 8.805,0.305 8.477,0.492L1.82,4.328C1.484,4.539 1.328,4.766 1.328,5.047ZM2.359,14.055C2.359,14.43 2.578,14.648 2.961,14.648H4.961C5.344,14.648 5.563,14.43 5.563,14.055V13.844C5.563,13.477 5.344,13.258 4.961,13.258H4.664V7.789H4.961C5.344,7.789 5.563,7.57 5.563,7.195V6.984C5.563,6.609 5.344,6.391 4.961,6.391H2.961C2.578,6.391 2.359,6.609 2.359,6.984V7.195C2.359,7.57 2.578,7.789 2.961,7.789H3.273V13.258H2.961C2.578,13.258 2.359,13.477 2.359,13.844V14.055ZM6.078,14.055C6.078,14.43 6.305,14.648 6.68,14.648H8.688C9.063,14.648 9.281,14.43 9.281,14.055V13.844C9.281,13.477 9.063,13.258 8.688,13.258H8.383V7.789H8.688C9.063,7.789 9.281,7.57 9.281,7.195V6.984C9.281,6.609 9.063,6.391 8.688,6.391H6.68C6.305,6.391 6.078,6.609 6.078,6.984V7.195C6.078,7.57 6.305,7.789 6.68,7.789H6.992V13.258H6.68C6.305,13.258 6.078,13.477 6.078,13.844V14.055ZM9.813,14.055C9.813,14.43 10.031,14.648 10.406,14.648H12.414C12.789,14.648 13.016,14.43 13.016,14.055V13.844C13.016,13.477 12.789,13.258 12.414,13.258H12.117V7.789H12.414C12.789,7.789 13.016,7.57 13.016,7.195V6.984C13.016,6.609 12.789,6.391 12.414,6.391H10.406C10.031,6.391 9.813,6.609 9.813,6.984V7.195C9.813,7.57 10.031,7.789 10.406,7.789H10.719V13.258H10.406C10.031,13.258 9.813,13.477 9.813,13.844V14.055ZM13.531,14.055C13.531,14.43 13.75,14.648 14.133,14.648H16.133C16.516,14.648 16.734,14.43 16.734,14.055V13.844C16.734,13.477 16.516,13.258 16.133,13.258H15.836V7.789H16.133C16.516,7.789 16.734,7.57 16.734,7.195V6.984C16.734,6.609 16.516,6.391 16.133,6.391H14.133C13.75,6.391 13.531,6.609 13.531,6.984V7.195C13.531,7.57 13.75,7.789 14.133,7.789H14.438V13.258H14.133C13.75,13.258 13.531,13.477 13.531,13.844V14.055ZM0.906,16C0.906,16.406 1.242,16.742 1.656,16.742H17.344C17.75,16.742 18.086,16.406 18.086,16C18.086,15.586 17.75,15.25 17.344,15.25H1.656C1.242,15.25 0.906,15.586 0.906,16Z"
android:fillColor="#6A7383"/>
</vector>
10 changes: 10 additions & 0 deletions link/res/drawable/stripe_link_chevron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="9dp"
android:viewportWidth="16"
android:viewportHeight="9">
<path
android:pathData="M13.5913,0.2932C13.9822,-0.0977 14.6159,-0.0977 15.0068,0.2932C15.3977,0.6841 15.3977,1.3178 15.0068,1.7087L8.7071,8.0084C8.3165,8.3989 7.6834,8.3989 7.2928,8.0084L0.9931,1.7087C0.6022,1.3178 0.6022,0.6841 0.9931,0.2932C1.384,-0.0977 2.0178,-0.0977 2.4086,0.2932L7.9999,5.8845L13.5913,0.2932Z"
android:fillColor="#C0C8D2"
android:fillType="evenOdd"/>
</vector>
4 changes: 4 additions & 0 deletions link/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
<string name="stripe_wallet_default">Default</string>
<!-- Title for a section listing one or more payment methods. -->
<string name="stripe_wallet_expanded_title">Payment methods</string>
<!-- Prefix for last 4 digits of payment method id e.g •••• 1234 -->
<string name="stripe_wallet_last4_prefix" translatable="false">•••• </string>
<!-- Accessibility description for Passthrough payment method -->
<string name="stripe_wallet_passthrough_description">Passthrough</string>
<!-- Label of a button that when tapped allows the user to select a different form of payment. -->
<string name="stripe_wallet_pay_another_way">Pay another way</string>
<!-- A text notice shown when the user selects a card that requires re-entering the security code (CVV/CVC). -->
Expand Down
10 changes: 10 additions & 0 deletions link/src/main/java/com/stripe/android/link/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ private val ButtonLabel = Color(0xFF011E0F)
private val ErrorText = Color(0xFFFF2F4C)
private val ErrorBackground = Color(0x2EFE87A1)

private val LightComponentBackground = Color.White
private val LightComponentBorder = Color(0xFFE0E6EB)
private val LightComponentDivider = Color(0xFFEFF2F4)
private val LightTextPrimary = Color(0xFF30313D)
private val LightTextSecondary = Color(0xFF6A7383)
private val LightTextDisabled = Color(0xFFA3ACBA)
Expand All @@ -30,7 +32,9 @@ private val LightCloseButton = Color(0xFF30313D)
private val LightLinkLogo = Color(0xFF1D3944)
private val LightOtpPlaceholder = Color(0xFFEBEEF1)

private val DarkComponentBackground = Color(0x2E747480)
private val DarkComponentBorder = Color(0x5C787880)
private val DarkComponentDivider = Color(0x33787880)
private val DarkTextPrimary = Color.White
private val DarkTextSecondary = Color(0x99EBEBF5)
private val DarkTextDisabled = Color(0x61FFFFFF)
Expand All @@ -42,7 +46,9 @@ private val DarkProgressIndicator = LinkTeal
private val DarkOtpPlaceholder = Color(0x61FFFFFF)

internal data class LinkColors(
val componentBackground: Color,
val componentBorder: Color,
val componentDivider: Color,
val actionLabel: Color,
val buttonLabel: Color,
val actionLabelLight: Color,
Expand All @@ -64,7 +70,9 @@ internal object LinkThemeConfig {
}

private val colorsLight = LinkColors(
componentBackground = LightComponentBackground,
componentBorder = LightComponentBorder,
componentDivider = LightComponentDivider,
buttonLabel = ButtonLabel,
actionLabelLight = ActionLightGreen,
errorText = ErrorText,
Expand All @@ -91,7 +99,9 @@ internal object LinkThemeConfig {
)

private val colorsDark = colorsLight.copy(
componentBackground = DarkComponentBackground,
componentBorder = DarkComponentBorder,
componentDivider = DarkComponentDivider,
progressIndicator = DarkProgressIndicator,
linkLogo = DarkLinkLogo,
closeButton = DarkCloseButton,
Expand Down
1 change: 1 addition & 0 deletions link/src/main/java/com/stripe/android/link/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp

private val LocalColors = staticCompositionLocalOf { LinkThemeConfig.colors(false) }

internal val MinimumTouchTargetSize = 48.dp
internal val PrimaryButtonHeight = 56.dp
internal val AppBarHeight = 56.dp

Expand Down
250 changes: 250 additions & 0 deletions link/src/main/java/com/stripe/android/link/ui/wallet/PaymentDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package com.stripe.android.link.ui.wallet

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButton
import androidx.compose.material.RadioButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.stripe.android.link.R
import com.stripe.android.link.theme.MinimumTouchTargetSize
import com.stripe.android.link.theme.linkColors
import com.stripe.android.link.theme.linkShapes
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetails.Card
import com.stripe.android.R as StripeR
import com.stripe.android.ui.core.R as StripeUiCoreR

@Composable
internal fun PaymentDetailsListItem(
paymentDetails: ConsumerPaymentDetails.PaymentDetails,
enabled: Boolean,
isSelected: Boolean,
isUpdating: Boolean,
onClick: () -> Unit,
onMenuButtonClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 56.dp)
.clickable(enabled = enabled, onClick = onClick),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = isSelected,
onClick = null,
modifier = Modifier.padding(start = 20.dp, end = 6.dp),
colors = RadioButtonDefaults.colors(
selectedColor = MaterialTheme.linkColors.actionLabelLight,
unselectedColor = MaterialTheme.linkColors.disabledText
)
)
Column(
modifier = Modifier
.padding(vertical = 8.dp)
.weight(1f)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
PaymentDetails(paymentDetails = paymentDetails)

if (paymentDetails.isDefault) {
DefaultTag()
}

val showWarning = (paymentDetails as? Card)?.isExpired ?: false
if (showWarning) {
Icon(
painter = painterResource(R.drawable.stripe_link_error),
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = MaterialTheme.linkColors.errorText
)
}
}
}

MenuAndLoader(
enabled = enabled,
isUpdating = isUpdating,
onMenuButtonClick = onMenuButtonClick
)
}
}

@Composable
private fun MenuAndLoader(
enabled: Boolean,
isUpdating: Boolean,
onMenuButtonClick: () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(MinimumTouchTargetSize)
.padding(end = 12.dp)
) {
if (isUpdating) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
strokeWidth = 2.dp
)
} else {
IconButton(
onClick = onMenuButtonClick,
enabled = enabled
) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(StripeR.string.stripe_edit),
tint = MaterialTheme.linkColors.actionLabelLight,
modifier = Modifier.size(24.dp)
)
}
}
}
}

@Composable
private fun DefaultTag() {
Box(
modifier = Modifier
.background(
color = MaterialTheme.colors.secondary,
shape = MaterialTheme.linkShapes.extraSmall
),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(id = R.string.stripe_wallet_default),
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
color = MaterialTheme.linkColors.disabledText,
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
}
}

@Composable
private fun RowScope.PaymentDetails(
paymentDetails: ConsumerPaymentDetails.PaymentDetails,
) {
when (paymentDetails) {
is Card -> {
CardInfo(
last4 = paymentDetails.last4,
icon = paymentDetails.brand.icon,
contentDescription = paymentDetails.brand.displayName
)
}
is ConsumerPaymentDetails.BankAccount -> {
BankAccountInfo(bankAccount = paymentDetails)
}
is ConsumerPaymentDetails.Passthrough -> {
CardInfo(
last4 = paymentDetails.last4,
icon = R.drawable.stripe_link_bank,
contentDescription = stringResource(R.string.stripe_wallet_passthrough_description)
)
}
}
}

@Composable
private fun RowScope.CardInfo(
last4: String,
icon: Int,
contentDescription: String? = null
) {
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = icon),
contentDescription = contentDescription,
modifier = Modifier
.width(38.dp)
.padding(horizontal = 6.dp),
alignment = Alignment.Center,
)
Text(
text = stringResource(R.string.stripe_wallet_last4_prefix),
color = MaterialTheme.colors.onPrimary,
)
Text(
text = last4,
color = MaterialTheme.colors.onPrimary,
style = MaterialTheme.typography.h6
)
}
}

@Composable
private fun RowScope.BankAccountInfo(
bankAccount: ConsumerPaymentDetails.BankAccount,
) {
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.stripe_link_bank),
contentDescription = null,
modifier = Modifier
.width(38.dp)
.padding(horizontal = 6.dp),
alignment = Alignment.Center,
colorFilter = ColorFilter.tint(MaterialTheme.linkColors.actionLabelLight)
)
Column(horizontalAlignment = Alignment.Start) {
Text(
text = bankAccount.bankName ?: stringResource(StripeUiCoreR.string.stripe_payment_method_bank),
color = MaterialTheme.colors.onPrimary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.h6
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.stripe_wallet_last4_prefix),
color = MaterialTheme.colors.onSecondary,
style = MaterialTheme.typography.body2
)
Text(
text = bankAccount.last4,
color = MaterialTheme.colors.onSecondary,
style = MaterialTheme.typography.body2
)
}
}
}
}
Loading

0 comments on commit 3c205d7

Please sign in to comment.