Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,5 @@ docs/_site

!/gradle/wrapper/gradle-wrapper.jar
/.idea/AndroidProjectSystem.xml
/.claude
CLAUDE.md
202 changes: 180 additions & 22 deletions sample/src/main/java/com/example/AfterpayUiGalleryActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,35 +36,20 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import java.util.Locale
import kotlin.math.roundToInt

class AfterpayUiGalleryActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.afterpay_ui_widgets)

val logoContainer = findViewById<LinearLayout>(R.id.logo_container)
val darkContainer = findViewById<LinearLayout>(R.id.price_breakdown_container_dark)
val lightContainer = findViewById<LinearLayout>(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.
Expand All @@ -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)
}
Expand Down
Loading