From 7f11eecf7d36898833817ce9bf67d92666f86ade Mon Sep 17 00:00:00 2001 From: tjclawson-stripe <163896025+tjclawson-stripe@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:07:50 -0500 Subject: [PATCH] Embedded Appearance API (#9706) * Add Embedded Appearance API --- paymentsheet/api/paymentsheet.api | 38 ++- .../android/paymentsheet/PaymentSheet.kt | 269 +++++++++++++++++- .../com/stripe/android/uicore/StripeTheme.kt | 37 +++ 3 files changed, 341 insertions(+), 3 deletions(-) diff --git a/paymentsheet/api/paymentsheet.api b/paymentsheet/api/paymentsheet.api index 76ae6bb8695..2a1a026ab44 100644 --- a/paymentsheet/api/paymentsheet.api +++ b/paymentsheet/api/paymentsheet.api @@ -612,13 +612,15 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance : and public fun ()V public fun (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;)V public synthetic fun (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded;)V + public synthetic fun (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Colors; public final fun component2 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Colors; public final fun component3 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes; public final fun component4 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Typography; public final fun component5 ()Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton; - public final fun copy (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance; - public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance; + public final fun copy (Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance; + public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Colors;Lcom/stripe/android/paymentsheet/PaymentSheet$Shapes;Lcom/stripe/android/paymentsheet/PaymentSheet$Typography;Lcom/stripe/android/paymentsheet/PaymentSheet$PrimaryButton;Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance; public final fun describeContents ()I public fun equals (Ljava/lang/Object;)Z public final fun getColors (Z)Lcom/stripe/android/paymentsheet/PaymentSheet$Colors; @@ -651,6 +653,38 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance$Creat public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithCheckmark$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithCheckmark; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithCheckmark; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithRadio$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithRadio; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FlatWithRadio; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FloatingButton$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FloatingButton; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance$Embedded$RowStyle$FloatingButton; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/paymentsheet/PaymentSheet$BillingDetails : android/os/Parcelable { public static final field $stable I public static final field CREATOR Landroid/os/Parcelable$Creator; diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt index ebd3db14d4a..d149ed2b43f 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import androidx.activity.ComponentActivity import androidx.annotation.ColorInt import androidx.annotation.FontRes +import androidx.annotation.RestrictTo import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -18,6 +19,7 @@ import com.stripe.android.link.account.LinkStore import com.stripe.android.model.CardBrand import com.stripe.android.model.PaymentIntent import com.stripe.android.model.SetupIntent +import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationInterceptor import com.stripe.android.paymentsheet.addresselement.AddressDetails import com.stripe.android.paymentsheet.flowcontroller.FlowControllerFactory @@ -906,6 +908,11 @@ class PaymentSheet internal constructor( * Describes the appearance of the primary button (e.g., the "Pay" button). */ val primaryButton: PrimaryButton = PrimaryButton(), + + /** + * Describes the appearance of the Embedded Payment Element + */ + internal val embeddedAppearance: Embedded = Embedded.default ) : Parcelable { constructor() : this( colorsLight = Colors.defaultLight, @@ -915,10 +922,259 @@ class PaymentSheet internal constructor( primaryButton = PrimaryButton(), ) + constructor( + colorsLight: Colors = Colors.defaultLight, + colorsDark: Colors = Colors.defaultDark, + shapes: Shapes = Shapes.default, + typography: Typography = Typography.default, + primaryButton: PrimaryButton = PrimaryButton(), + ) : this( + colorsLight = colorsLight, + colorsDark = colorsDark, + shapes = shapes, + typography = typography, + primaryButton = primaryButton, + embeddedAppearance = Embedded.default + ) + fun getColors(isDark: Boolean): Colors { return if (isDark) colorsDark else colorsLight } + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @Parcelize + @OptIn(ExperimentalEmbeddedPaymentElementApi::class) + class Embedded( + internal val style: RowStyle + ) : Parcelable { + + internal companion object { + @OptIn(ExperimentalEmbeddedPaymentElementApi::class) + val default = Embedded( + style = RowStyle.FlatWithRadio.defaultLight + ) + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @ExperimentalEmbeddedPaymentElementApi + @Parcelize + sealed class RowStyle : Parcelable { + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @ExperimentalEmbeddedPaymentElementApi + @Parcelize + class FlatWithRadio( + /** + * The thickness of the separator line between rows. + */ + val separatorThicknessDp: Float, + + /** + * The color of the separator line between rows. + */ + @ColorInt + val separatorColor: Int, + + /** + * The insets of the separator line between rows. + */ + val separatorInsetsDp: Float, + + /** + * Determines if the top separator is visible at the top of the Embedded Mobile Payment Element. + */ + val topSeparatorEnabled: Boolean, + + /** + * Determines if the bottom separator is visible at the bottom of the Embedded Mobile Payment + * Element. + */ + val bottomSeparatorEnabled: Boolean, + /** + * The color of the radio button when selected. + */ + @ColorInt + val selectedColor: Int, + + /** + * The color of the radio button when unselected. + */ + @ColorInt + val unselectedColor: Int, + /** + * Additional vertical insets applied to a payment method row. + * - Note: Increasing this value increases the height of each row. + */ + val additionalInsetsDp: Float, + ) : RowStyle() { + constructor( + context: Context, + separatorThicknessDp: Int, + separatorColor: Color, + separatorInsetsDp: Int, + topSeparatorEnabled: Boolean, + bottomSeparatorEnabled: Boolean, + selectedColor: Color, + unselectedColor: Color, + additionalInsetsDp: Int + ) : this( + separatorThicknessDp = context.getRawValueFromDimenResource(separatorThicknessDp), + separatorColor = separatorColor.toArgb(), + separatorInsetsDp = context.getRawValueFromDimenResource(separatorInsetsDp), + topSeparatorEnabled = topSeparatorEnabled, + bottomSeparatorEnabled = bottomSeparatorEnabled, + selectedColor = selectedColor.toArgb(), + unselectedColor = unselectedColor.toArgb(), + additionalInsetsDp = context.getRawValueFromDimenResource(additionalInsetsDp) + ) + internal companion object { + val defaultLight = FlatWithRadio( + separatorThicknessDp = StripeThemeDefaults.flat.separatorThickness, + separatorColor = StripeThemeDefaults.colorsLight.componentBorder.toArgb(), + separatorInsetsDp = StripeThemeDefaults.flat.separatorInsets, + topSeparatorEnabled = StripeThemeDefaults.flat.topSeparatorEnabled, + bottomSeparatorEnabled = StripeThemeDefaults.flat.bottomSeparatorEnabled, + selectedColor = StripeThemeDefaults.colorsLight.materialColors.primary.toArgb(), + unselectedColor = StripeThemeDefaults.colorsLight.componentBorder.toArgb(), + additionalInsetsDp = StripeThemeDefaults.embeddedCommon.additionalInsetsDp + ) + + val defaultDark = FlatWithRadio( + separatorThicknessDp = StripeThemeDefaults.flat.separatorThickness, + separatorColor = StripeThemeDefaults.colorsDark.componentBorder.toArgb(), + separatorInsetsDp = StripeThemeDefaults.flat.separatorInsets, + topSeparatorEnabled = StripeThemeDefaults.flat.topSeparatorEnabled, + bottomSeparatorEnabled = StripeThemeDefaults.flat.bottomSeparatorEnabled, + selectedColor = StripeThemeDefaults.colorsDark.materialColors.primary.toArgb(), + unselectedColor = StripeThemeDefaults.colorsDark.componentBorder.toArgb(), + additionalInsetsDp = StripeThemeDefaults.embeddedCommon.additionalInsetsDp + ) + } + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @ExperimentalEmbeddedPaymentElementApi + @Parcelize + class FlatWithCheckmark( + /** + * The thickness of the separator line between rows. + */ + val separatorThicknessDp: Float, + + /** + * The color of the separator line between rows. + */ + @ColorInt + val separatorColor: Int, + + /** + * The insets of the separator line between rows. + */ + val separatorInsetsDp: Float, + + /** + * Determines if the top separator is visible at the top of the Embedded Mobile Payment Element. + */ + val topSeparatorEnabled: Boolean, + + /** + * Determines if the bottom separator is visible at the bottom of the Embedded Mobile Payment + * Element. + */ + val bottomSeparatorEnabled: Boolean, + /** + * The color of the checkmark. + */ + @ColorInt + val checkmarkColor: Int, + /** + * Inset of the checkmark from the end of the row + */ + val checkmarkInsetDp: Float, + /** + * Additional vertical insets applied to a payment method row. + * - Note: Increasing this value increases the height of each row. + */ + val additionalInsetsDp: Float, + ) : RowStyle() { + constructor( + context: Context, + separatorThicknessDp: Int, + separatorColor: Color, + separatorInsetsDp: Int, + topSeparatorEnabled: Boolean, + bottomSeparatorEnabled: Boolean, + checkmarkColor: Color, + checkmarkInsetDp: Int, + additionalInsetsDp: Int + ) : this( + separatorThicknessDp = context.getRawValueFromDimenResource(separatorThicknessDp), + separatorColor = separatorColor.toArgb(), + separatorInsetsDp = context.getRawValueFromDimenResource(separatorInsetsDp), + topSeparatorEnabled = topSeparatorEnabled, + bottomSeparatorEnabled = bottomSeparatorEnabled, + checkmarkColor = checkmarkColor.toArgb(), + checkmarkInsetDp = context.getRawValueFromDimenResource(checkmarkInsetDp), + additionalInsetsDp = context.getRawValueFromDimenResource(additionalInsetsDp) + ) + internal companion object { + val defaultLight = FlatWithCheckmark( + separatorThicknessDp = StripeThemeDefaults.flat.separatorThickness, + separatorColor = StripeThemeDefaults.colorsLight.componentBorder.toArgb(), + separatorInsetsDp = StripeThemeDefaults.flat.separatorInsets, + topSeparatorEnabled = StripeThemeDefaults.flat.topSeparatorEnabled, + bottomSeparatorEnabled = StripeThemeDefaults.flat.bottomSeparatorEnabled, + checkmarkColor = StripeThemeDefaults.colorsLight.materialColors.primary.toArgb(), + checkmarkInsetDp = StripeThemeDefaults.embeddedCommon.checkmarkInsetDp, + additionalInsetsDp = StripeThemeDefaults.embeddedCommon.additionalInsetsDp + ) + + val defaultDark = FlatWithCheckmark( + separatorThicknessDp = StripeThemeDefaults.flat.separatorThickness, + separatorColor = StripeThemeDefaults.colorsDark.componentBorder.toArgb(), + separatorInsetsDp = StripeThemeDefaults.flat.separatorInsets, + topSeparatorEnabled = StripeThemeDefaults.flat.topSeparatorEnabled, + bottomSeparatorEnabled = StripeThemeDefaults.flat.bottomSeparatorEnabled, + checkmarkColor = StripeThemeDefaults.colorsDark.materialColors.primary.toArgb(), + checkmarkInsetDp = StripeThemeDefaults.embeddedCommon.checkmarkInsetDp, + additionalInsetsDp = StripeThemeDefaults.embeddedCommon.additionalInsetsDp + ) + } + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @ExperimentalEmbeddedPaymentElementApi + @Parcelize + class FloatingButton( + /** + * The spacing between payment method rows + */ + val spacingDp: Float, + /** + * Additional vertical insets applied to a payment method row. + * - Note: Increasing this value increases the height of each row. + */ + val additionalInsetsDp: Float, + ) : RowStyle() { + constructor( + context: Context, + spacingDp: Int, + additionalInsetsDp: Int + ) : this( + spacingDp = context.getRawValueFromDimenResource(spacingDp), + additionalInsetsDp = context.getRawValueFromDimenResource(additionalInsetsDp) + ) + + internal companion object { + val default = FloatingButton( + spacingDp = StripeThemeDefaults.floating.spacing, + additionalInsetsDp = StripeThemeDefaults.embeddedCommon.additionalInsetsDp + ) + } + } + } + } + class Builder { private var colorsLight = Colors.defaultLight private var colorsDark = Colors.defaultDark @@ -926,6 +1182,10 @@ class PaymentSheet internal constructor( private var typography = Typography.default private var primaryButton: PrimaryButton = PrimaryButton() + @ExperimentalEmbeddedPaymentElementApi + private var embeddedAppearance: Embedded = + Embedded.default + fun colorsLight(colors: Colors) = apply { this.colorsLight = colors } @@ -946,8 +1206,15 @@ class PaymentSheet internal constructor( this.primaryButton = primaryButton } + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @ExperimentalEmbeddedPaymentElementApi + fun embeddedAppearance(embeddedAppearance: Embedded) = apply { + this.embeddedAppearance = embeddedAppearance + } + + @OptIn(ExperimentalEmbeddedPaymentElementApi::class) fun build(): Appearance { - return Appearance(colorsLight, colorsDark, shapes, typography, primaryButton) + return Appearance(colorsLight, colorsDark, shapes, typography, primaryButton, embeddedAppearance) } } } diff --git a/stripe-ui-core/src/main/java/com/stripe/android/uicore/StripeTheme.kt b/stripe-ui-core/src/main/java/com/stripe/android/uicore/StripeTheme.kt index 43382f15ed1..38b7e34b645 100644 --- a/stripe-ui-core/src/main/java/com/stripe/android/uicore/StripeTheme.kt +++ b/stripe-ui-core/src/main/java/com/stripe/android/uicore/StripeTheme.kt @@ -123,6 +123,26 @@ data class PrimaryButtonTypography( val fontSize: TextUnit ) +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +data class EmbeddedFlatStyle( + val separatorThickness: Float, + val separatorColor: Color, + val separatorInsets: Float, + val topSeparatorEnabled: Boolean, + val bottomSeparatorEnabled: Boolean +) + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +data class EmbeddedInsets( + val checkmarkInsetDp: Float, + val additionalInsetsDp: Float +) + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +data class EmbeddedFloatingStyle( + val spacing: Float +) + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val PRIMARY_BUTTON_SUCCESS_BACKGROUND_COLOR = Color(0xFF24B47E) @@ -209,6 +229,23 @@ object StripeThemeDefaults { fontSize = typography.largeFontSize ) ) + + val flat = EmbeddedFlatStyle( + separatorThickness = 1.0f, + separatorColor = Color(0xFF787880), + separatorInsets = 0.0f, + topSeparatorEnabled = true, + bottomSeparatorEnabled = true + ) + + val embeddedCommon = EmbeddedInsets( + additionalInsetsDp = 4.0f, + checkmarkInsetDp = 12.0f + ) + + val floating = EmbeddedFloatingStyle( + spacing = 12.0f + ) } @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)