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..d055fc71 100644
--- a/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt
+++ b/sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt
@@ -15,11 +15,16 @@
*/
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.core.content.ContextCompat
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
@@ -31,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() {
@@ -38,28 +44,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 +58,178 @@ class AfterpayUiGalleryActivity : AppCompatActivity() {
}
}
+ private fun populatePriceBreakdowns(container: LinearLayout, isDarkBackground: Boolean) {
+ 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(
+ 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).roundToInt()
+ bottomMargin = (20 * resources.displayMetrics.density).roundToInt()
+ }
+ container.addView(captionView, captionParams)
+ } catch (_: IllegalStateException) {
+ // For example, in US locale (Cash App scheme) logoType = BADGE or COMPACT_BADGE is invalid; only LOCKUP is allowed.
+ }
+ }
+
+ // 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).roundToInt()
+ bottomMargin = (12 * resources.displayMetrics.density).roundToInt()
+ }
+ 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).roundToInt()
+ bottomMargin = (20 * resources.displayMetrics.density).roundToInt()
+ }
+ 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..6ab4f3bd 100644
--- a/sample/src/main/res/layout/afterpay_ui_widgets.xml
+++ b/sample/src/main/res/layout/afterpay_ui_widgets.xml
@@ -9,243 +9,734 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
+
+
-
+ android:background="@color/ui_gallery_dark_bg">
+
+
+ android:text="Dark Background" />
-
+
+ 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">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 00000000..cf84a99a
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+
+
+
+ #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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+