Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link Payment Method Rows UI #9681

Merged
merged 11 commits into from
Nov 25, 2024
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
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
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
Loading