From f4df27066e0c7e596ba7359e1baa266bc356836d Mon Sep 17 00:00:00 2001 From: Pedro Veloso Date: Wed, 4 Feb 2026 18:16:06 +0100 Subject: [PATCH 1/3] Improve Example app Widget's UI --- .gitignore | 2 + .../com/example/AfterpayUiGalleryActivity.kt | 194 +++- .../main/res/layout/afterpay_ui_widgets.xml | 1007 ++++++++++++++--- 3 files changed, 998 insertions(+), 205 deletions(-) diff --git a/.gitignore b/.gitignore index 233e76b8..79eb0b61 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,5 @@ docs/_site !/gradle/wrapper/gradle-wrapper.jar /.idea/AndroidProjectSystem.xml +/.claude +CLAUDE.md diff --git a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt index 60986d15..cc8ea4db 100644 --- a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt +++ b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt @@ -15,11 +15,15 @@ */ package com.example +import android.graphics.Typeface import android.os.Bundle +import android.util.TypedValue import android.widget.LinearLayout +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.afterpay.android.Afterpay +import com.afterpay.android.view.AfterpayIntroText import com.afterpay.android.view.AfterpayLogoType import com.afterpay.android.view.AfterpayPriceBreakdown import com.afterpay.android.view.AfterpayWidgetStyle @@ -38,28 +42,12 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.afterpay_ui_widgets) - val logoContainer = findViewById(R.id.logo_container) + val darkContainer = findViewById(R.id.price_breakdown_container_dark) + val lightContainer = findViewById(R.id.price_breakdown_container_light) - // Instantiate an AfterpayPriceBreakdown and fill it with dummy info. - AfterpayLogoType.values().forEach { logoType -> - - AfterpayWidgetStyle.values().forEach { style -> - try { - // Not all logoTypes are valid in each locale (i.e. non-lockup types are not valid in US locale) - // so we catch exceptions here - val breakdownView = AfterpayPriceBreakdown(this).apply { - totalAmount = BigDecimal("100.00") - this.logoType = logoType - this.style = style - } - val params = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT, - ) - logoContainer.addView(breakdownView, params) - } catch (_: IllegalStateException) {} - } - } + // Populate both containers with price breakdown widgets + populatePriceBreakdowns(darkContainer, isDarkBackground = true) + populatePriceBreakdowns(lightContainer, isDarkBackground = false) // This is needed so that the UI gets inflated. // Right now an AP server needs to for the UI to be inflated. @@ -68,10 +56,172 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { } } + private fun populatePriceBreakdowns(container: LinearLayout, isDarkBackground: Boolean) { + val captionColor = if (isDarkBackground) 0xFF999999.toInt() else 0xFF666666.toInt() + val labelColor = if (isDarkBackground) 0xFFCCCCCC.toInt() else 0xFF333333.toInt() + + // Helper to add a price breakdown with caption + fun addPriceBreakdown( + caption: String, + configure: AfterpayPriceBreakdown.() -> Unit, + ) { + try { + val breakdownView = AfterpayPriceBreakdown(this).apply { + totalAmount = BigDecimal("100.00") + configure() + } + val breakdownParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) + container.addView(breakdownView, breakdownParams) + + val captionView = TextView(this).apply { + text = caption + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(captionColor) + } + val captionParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ).apply { + topMargin = (4 * resources.displayMetrics.density).toInt() + bottomMargin = (20 * resources.displayMetrics.density).toInt() + } + container.addView(captionView, captionParams) + } catch (_: IllegalStateException) { + // Some configurations are not valid in certain locales + } + } + + // Helper to add a section divider with label + fun addSectionLabel(label: String) { + val labelView = TextView(this).apply { + text = label + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setTextColor(labelColor) + setTypeface(typeface, Typeface.BOLD) + } + val labelParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ).apply { + topMargin = (16 * resources.displayMetrics.density).toInt() + bottomMargin = (12 * resources.displayMetrics.density).toInt() + } + container.addView(labelView, labelParams) + } + + // ===== Logo type variations ===== + addSectionLabel("Logo type variations") + + addPriceBreakdown("logoType: badge") { + logoType = AfterpayLogoType.BADGE + } + + addPriceBreakdown("logoType: lockup") { + logoType = AfterpayLogoType.LOCKUP + } + + addPriceBreakdown("logoType: compactBadge") { + logoType = AfterpayLogoType.COMPACT_BADGE + } + + // ===== Intro text variations ===== + addSectionLabel("Intro text variations") + + addPriceBreakdown("introText: or") { + introText = AfterpayIntroText.OR + } + + addPriceBreakdown("introText: payIn") { + introText = AfterpayIntroText.PAY_IN + } + + addPriceBreakdown("introText: make") { + introText = AfterpayIntroText.MAKE + } + + addPriceBreakdown("introText: pay") { + introText = AfterpayIntroText.PAY + } + + addPriceBreakdown("introText: in") { + introText = AfterpayIntroText.IN + } + + addPriceBreakdown("introText: empty") { + introText = AfterpayIntroText.EMPTY + } + + // ===== Display options ===== + addSectionLabel("Display options") + + addPriceBreakdown("showWithText: false") { + showWithText = false + } + + addPriceBreakdown("showInterestFreeText: false") { + showInterestFreeText = false + } + + addPriceBreakdown("minimal (no with/interest)") { + showWithText = false + showInterestFreeText = false + } + + // ===== Amount edge case ===== + addSectionLabel("Amount edge cases") + + try { + val outOfRangeView = AfterpayPriceBreakdown(this).apply { + totalAmount = BigDecimal("10000000.00") // Out of range amount + } + val breakdownParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) + container.addView(outOfRangeView, breakdownParams) + + val captionView = TextView(this).apply { + text = "amount out of range" + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(captionColor) + } + val captionParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ).apply { + topMargin = (4 * resources.displayMetrics.density).toInt() + bottomMargin = (20 * resources.displayMetrics.density).toInt() + } + container.addView(captionView, captionParams) + } catch (_: IllegalStateException) {} + + // ===== Logo color schemes ===== + addSectionLabel("Logo color schemes") + + addPriceBreakdown("style: default") { + style = AfterpayWidgetStyle.Default + } + + addPriceBreakdown("style: alt") { + style = AfterpayWidgetStyle.Alt + } + + addPriceBreakdown("style: monochromeDark") { + style = AfterpayWidgetStyle.MonochromeDark + } + + addPriceBreakdown("style: monochromeLight") { + style = AfterpayWidgetStyle.MonochromeLight + } + } + private fun getConfiguration() { CoroutineScope(Dispatchers.IO).launch { merchantApi().getConfiguration().apply { - onFailure { error -> + onFailure { _ -> val msg = "You must run an AP server to fetch configuration." showToastFromBackground(this@AfterpayUiGalleryActivity, msg) } diff --git a/sample/src/main/res/layout/afterpay_ui_widgets.xml b/sample/src/main/res/layout/afterpay_ui_widgets.xml index 6f40e669..0c8597a0 100644 --- a/sample/src/main/res/layout/afterpay_ui_widgets.xml +++ b/sample/src/main/res/layout/afterpay_ui_widgets.xml @@ -9,243 +9,884 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> + + - + android:background="#1A1A1A"> + + + android:layout_margin="16dp" + android:text="Dark Background" + android:textSize="22sp" + android:textStyle="bold" + android:textColor="#FFFFFF" /> - + + android:orientation="vertical" + android:padding="16dp"> - + - + + + + + + + + + + + + + + + + + + android:orientation="vertical" + android:padding="16dp"> - + + + + + + + + + + + + + + + + + + + + android:orientation="vertical" + android:padding="16dp"> + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + android:layout_marginBottom="8dp" + android:text="AfterpayPriceBreakdown" + android:textSize="18sp" + android:paddingHorizontal="16dp" + android:textStyle="bold" + android:textColor="#FFFFFF" /> - - + - + + - - - - + android:background="#FFFFFF"> - + + - + + - - + - + + - + + - + + - + + + - - + + - + + - + - + + - + + - - + + - + + + - - + + - + + android:padding="16dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9572d4c96046fd431d5866a57ea8d282d13f0ffa Mon Sep 17 00:00:00 2001 From: Pedro Veloso Date: Thu, 5 Feb 2026 18:21:05 +0100 Subject: [PATCH 2/3] Use Styles to cleanup implementation --- .../com/example/AfterpayUiGalleryActivity.kt | 24 +- .../main/res/layout/afterpay_ui_widgets.xml | 388 ++++++------------ sample/src/main/res/values/colors.xml | 13 + sample/src/main/res/values/styles.xml | 56 +++ 4 files changed, 204 insertions(+), 277 deletions(-) create mode 100644 sample/src/main/res/values/colors.xml create mode 100644 sample/src/main/res/values/styles.xml diff --git a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt index cc8ea4db..79ca4013 100644 --- a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt +++ b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt @@ -21,6 +21,7 @@ import android.util.TypedValue import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import com.afterpay.android.Afterpay import com.afterpay.android.view.AfterpayIntroText @@ -35,6 +36,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal import java.util.Locale +import kotlin.math.roundToInt class AfterpayUiGalleryActivity : AppCompatActivity() { @@ -57,8 +59,14 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { } private fun populatePriceBreakdowns(container: LinearLayout, isDarkBackground: Boolean) { - val captionColor = if (isDarkBackground) 0xFF999999.toInt() else 0xFF666666.toInt() - val labelColor = if (isDarkBackground) 0xFFCCCCCC.toInt() else 0xFF333333.toInt() + val captionColor = ContextCompat.getColor( + this, + if (isDarkBackground) R.color.ui_gallery_caption_dark else R.color.ui_gallery_caption_light, + ) + val labelColor = ContextCompat.getColor( + this, + if (isDarkBackground) R.color.ui_gallery_label_dark else R.color.ui_gallery_label_light, + ) // Helper to add a price breakdown with caption fun addPriceBreakdown( @@ -85,8 +93,8 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, ).apply { - topMargin = (4 * resources.displayMetrics.density).toInt() - bottomMargin = (20 * resources.displayMetrics.density).toInt() + topMargin = (4 * resources.displayMetrics.density).roundToInt() + bottomMargin = (20 * resources.displayMetrics.density).roundToInt() } container.addView(captionView, captionParams) } catch (_: IllegalStateException) { @@ -106,8 +114,8 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, ).apply { - topMargin = (16 * resources.displayMetrics.density).toInt() - bottomMargin = (12 * resources.displayMetrics.density).toInt() + topMargin = (16 * resources.displayMetrics.density).roundToInt() + bottomMargin = (12 * resources.displayMetrics.density).roundToInt() } container.addView(labelView, labelParams) } @@ -192,8 +200,8 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, ).apply { - topMargin = (4 * resources.displayMetrics.density).toInt() - bottomMargin = (20 * resources.displayMetrics.density).toInt() + topMargin = (4 * resources.displayMetrics.density).roundToInt() + bottomMargin = (20 * resources.displayMetrics.density).roundToInt() } container.addView(captionView, captionParams) } catch (_: IllegalStateException) {} diff --git a/sample/src/main/res/layout/afterpay_ui_widgets.xml b/sample/src/main/res/layout/afterpay_ui_widgets.xml index 0c8597a0..6ab4f3bd 100644 --- a/sample/src/main/res/layout/afterpay_ui_widgets.xml +++ b/sample/src/main/res/layout/afterpay_ui_widgets.xml @@ -16,17 +16,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="#1A1A1A"> + android:background="@color/ui_gallery_dark_bg"> + android:text="Dark Background" /> + android:text="AfterpayBadge" /> + android:text="AfterpayBadge · default" /> + android:text="AfterpayBadge · alt" /> + android:text="AfterpayBadge · monochromeDark" /> + android:text="AfterpayBadge · monochromeLight" /> @@ -101,7 +87,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#333333" /> + android:background="@color/ui_gallery_divider_dark" /> + android:text="AfterpayLockup" /> + android:text="AfterpayLockup · default" /> + android:text="AfterpayLockup · alt" /> + android:text="AfterpayLockup · monochromeDark" /> + android:text="AfterpayLockup · monochromeLight" /> @@ -176,7 +151,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#333333" /> + android:background="@color/ui_gallery_divider_dark" /> + android:text="AfterpayPaymentButton" /> + android:text="AfterpayPaymentButton · buyNow · default" /> + android:text="AfterpayPaymentButton · buyNow · alt" /> + android:text="AfterpayPaymentButton · buyNow · monochromeDark" /> + android:text="AfterpayPaymentButton · buyNow · monochromeLight" /> + android:text="AfterpayPaymentButton · checkout · default" /> + android:text="AfterpayPaymentButton · checkout · alt" /> + android:text="AfterpayPaymentButton · checkout · monochromeDark" /> + android:text="AfterpayPaymentButton · checkout · monochromeLight" /> + android:text="AfterpayPaymentButton · payNow · default" /> + android:text="AfterpayPaymentButton · payNow · alt" /> + android:text="AfterpayPaymentButton · payNow · monochromeDark" /> + android:text="AfterpayPaymentButton · payNow · monochromeLight" /> + android:text="AfterpayPaymentButton · placeOrder · default" /> + android:text="AfterpayPaymentButton · placeOrder · alt" /> + android:text="AfterpayPaymentButton · placeOrder · monochromeDark" /> + android:text="AfterpayPaymentButton · placeOrder · monochromeLight" /> @@ -427,7 +352,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#333333" /> + android:background="@color/ui_gallery_divider_dark" /> + android:text="Light Background" /> + android:text="AfterpayBadge" /> + android:text="AfterpayBadge · default" /> + android:text="AfterpayBadge · alt" /> + android:text="AfterpayBadge · monochromeDark" /> + android:text="AfterpayBadge · monochromeLight" /> @@ -540,7 +451,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#CCCCCC" /> + android:background="@color/ui_gallery_divider_light" /> + android:text="AfterpayLockup" /> + android:text="AfterpayLockup · default" /> + android:text="AfterpayLockup · alt" /> + android:text="AfterpayLockup · monochromeDark" /> + android:text="AfterpayLockup · monochromeLight" /> @@ -615,7 +515,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#CCCCCC" /> + android:background="@color/ui_gallery_divider_light" /> + android:text="AfterpayPaymentButton" /> + android:text="AfterpayPaymentButton · buyNow · default" /> + android:text="AfterpayPaymentButton · buyNow · alt" /> + android:text="AfterpayPaymentButton · buyNow · monochromeDark" /> + android:text="AfterpayPaymentButton · buyNow · monochromeLight" /> + android:text="AfterpayPaymentButton · checkout · default" /> + android:text="AfterpayPaymentButton · checkout · alt" /> + android:text="AfterpayPaymentButton · checkout · monochromeDark" /> + android:text="AfterpayPaymentButton · checkout · monochromeLight" /> + android:text="AfterpayPaymentButton · payNow · default" /> + android:text="AfterpayPaymentButton · payNow · alt" /> + android:text="AfterpayPaymentButton · payNow · monochromeDark" /> + android:text="AfterpayPaymentButton · payNow · monochromeLight" /> + android:text="AfterpayPaymentButton · placeOrder · default" /> + android:text="AfterpayPaymentButton · placeOrder · alt" /> + android:text="AfterpayPaymentButton · placeOrder · monochromeDark" /> + android:text="AfterpayPaymentButton · placeOrder · monochromeLight" /> @@ -866,7 +716,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginVertical="16dp" - android:background="#CCCCCC" /> + android:background="@color/ui_gallery_divider_light" /> + + + #999999 + #666666 + #CCCCCC + #333333 + + + #1A1A1A + #333333 + #CCCCCC + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 00000000..1f76aa2d --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3a1109010a415337b93ca6b284fc048df22d92b1 Mon Sep 17 00:00:00 2001 From: Pedro Veloso Date: Fri, 6 Feb 2026 15:01:32 +0100 Subject: [PATCH 3/3] Clarify exception comment --- sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt index 79ca4013..d055fc71 100644 --- a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt +++ b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt @@ -98,7 +98,7 @@ class AfterpayUiGalleryActivity : AppCompatActivity() { } container.addView(captionView, captionParams) } catch (_: IllegalStateException) { - // Some configurations are not valid in certain locales + // For example, in US locale (Cash App scheme) logoType = BADGE or COMPACT_BADGE is invalid; only LOCKUP is allowed. } }