Skip to content

Commit

Permalink
Add administrative area dropdown for US, CA (#5518)
Browse files Browse the repository at this point in the history
* Add administrative area dropdown for US, CA
  • Loading branch information
jameswoo-stripe authored Sep 7, 2022
1 parent 92f3a67 commit 025a6b4
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 40 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

### PaymentSheet

* [CHANGED][5487](https://github.com/stripe/stripe-android/pull/5487) Updated Google Pay button to match new brand guidelines.
* [ADDED][5502](https://github.com/stripe/stripe-android/pull/5502) Added phone number minimum length
validation
* [CHANGED][5487](https://github.com/stripe/stripe-android/pull/5487) Updated Google Pay button to
match new brand guidelines.
* [ADDED][5502](https://github.com/stripe/stripe-android/pull/5502) Added phone number minimum
length validation
* [ADDED][5518](https://github.com/stripe/stripe-android/pull/5518) Added state/province dropdown
for US and Canada.

## 20.11.0 - 2022-08-29
This release adds postal code validation for PaymentSheet and fixed a fileprovider naming bug for Identity.
Expand Down
39 changes: 39 additions & 0 deletions payments-ui-core/api/payments-ui-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,45 @@ public final class com/stripe/android/ui/core/elements/AddressType$ShippingExpan
public fun toString ()Ljava/lang/String;
}

public abstract class com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country {
public static final field $stable I
public synthetic fun <init> (ILjava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getAdministrativeAreas ()Ljava/util/List;
public fun getLabel ()I
}

public final class com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$Canada : com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country {
public static final field $stable I
public fun <init> ()V
public fun <init> (ILjava/util/List;)V
public synthetic fun <init> (ILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()Ljava/util/List;
public final fun copy (ILjava/util/List;)Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$Canada;
public static synthetic fun copy$default (Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$Canada;ILjava/util/List;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$Canada;
public fun equals (Ljava/lang/Object;)Z
public fun getAdministrativeAreas ()Ljava/util/List;
public fun getLabel ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$US : com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country {
public static final field $stable I
public fun <init> ()V
public fun <init> (ILjava/util/List;)V
public synthetic fun <init> (ILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()Ljava/util/List;
public final fun copy (ILjava/util/List;)Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$US;
public static synthetic fun copy$default (Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$US;ILjava/util/List;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country$US;
public fun equals (Ljava/lang/Object;)Z
public fun getAdministrativeAreas ()Ljava/util/List;
public fun getLabel ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/stripe/android/ui/core/elements/AffirmElementUIKt {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import androidx.annotation.StringRes
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import com.stripe.android.ui.core.R
import com.stripe.android.ui.core.elements.AdministrativeAreaConfig
import com.stripe.android.ui.core.elements.AdministrativeAreaElement
import com.stripe.android.ui.core.elements.DropdownFieldController
import com.stripe.android.ui.core.elements.IdentifierSpec
import com.stripe.android.ui.core.elements.PostalCodeConfig
import com.stripe.android.ui.core.elements.RowController
Expand All @@ -13,6 +16,7 @@ import com.stripe.android.ui.core.elements.SectionSingleFieldElement
import com.stripe.android.ui.core.elements.SimpleTextElement
import com.stripe.android.ui.core.elements.SimpleTextFieldConfig
import com.stripe.android.ui.core.elements.SimpleTextFieldController
import com.stripe.android.ui.core.elements.TextFieldConfig
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
Expand Down Expand Up @@ -213,44 +217,95 @@ internal fun parseAddressesSchema(inputStream: InputStream?) =
private fun getJsonStringFromInputStream(inputStream: InputStream?) =
inputStream?.bufferedReader().use { it?.readText() }

internal fun List<CountryAddressSchema>.transformToElementList(countryCode: String): List<SectionFieldElement> {
internal fun List<CountryAddressSchema>.transformToElementList(
countryCode: String
): List<SectionFieldElement> {
val countryAddressElements = this
.filterNot {
it.type == FieldType.SortingCode ||
it.type == FieldType.DependentLocality
}
.mapNotNull { addressField ->
addressField.type?.let {
val textFieldConfig = when (it) {
FieldType.PostalCode -> {
PostalCodeConfig(
label = addressField.schema?.nameType?.stringResId ?: it.defaultLabel,
capitalization = it.capitalization(),
keyboard = getKeyboard(addressField.schema),
country = countryCode
)
}
else -> {
SimpleTextFieldConfig(
label = addressField.schema?.nameType?.stringResId ?: it.defaultLabel,
capitalization = it.capitalization(),
keyboard = getKeyboard(addressField.schema)
)
}
}
addressField.type?.toElement(
identifierSpec = addressField.type.identifierSpec,
label = addressField.schema?.nameType?.stringResId
?: addressField.type.defaultLabel,
capitalization = addressField.type.capitalization(),
keyboardType = getKeyboard(addressField.schema),
countryCode = countryCode,
showOptionalLabel = !addressField.required
)
}

// Put it in a single row
return combineCityAndPostal(countryAddressElements)
}

SimpleTextElement(
addressField.type.identifierSpec,
SimpleTextFieldController(
textFieldConfig = textFieldConfig,
showOptionalLabel = !addressField.required
private fun FieldType.toElement(
identifierSpec: IdentifierSpec,
label: Int,
capitalization: KeyboardCapitalization,
keyboardType: KeyboardType,
countryCode: String,
showOptionalLabel: Boolean
): SectionSingleFieldElement {
val simpleTextElement = SimpleTextElement(
identifierSpec,
SimpleTextFieldController(
textFieldConfig = toConfig(
label = label,
capitalization = capitalization,
keyboardType = keyboardType,
countryCode = countryCode
),
showOptionalLabel = showOptionalLabel
)
)
return when (this) {
FieldType.AdministrativeArea -> {
val supportsAdministrativeAreaDropdown = listOf(
"CA",
"US"
).contains(countryCode)
if (supportsAdministrativeAreaDropdown) {
val country = when (countryCode) {
"CA" -> AdministrativeAreaConfig.Country.Canada()
"US" -> AdministrativeAreaConfig.Country.US()
else -> throw IllegalArgumentException()
}
AdministrativeAreaElement(
identifierSpec,
DropdownFieldController(
AdministrativeAreaConfig(country)
)
)
} else {
simpleTextElement
}
}
else -> simpleTextElement
}
}

// Put it in a single row
return combineCityAndPostal(countryAddressElements)
private fun FieldType.toConfig(
label: Int,
capitalization: KeyboardCapitalization,
keyboardType: KeyboardType,
countryCode: String
): TextFieldConfig {
return when (this) {
FieldType.PostalCode -> PostalCodeConfig(
label = label,
capitalization = capitalization,
keyboard = keyboardType,
country = countryCode
)
else -> SimpleTextFieldConfig(
label = label,
capitalization = capitalization,
keyboard = keyboardType
)
}
}

private fun combineCityAndPostal(countryAddressElements: List<SectionSingleFieldElement>) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.stripe.android.ui.core.elements

import androidx.annotation.RestrictTo
import androidx.annotation.StringRes
import com.stripe.android.ui.core.R

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class AdministrativeAreaConfig(
country: Country
) : DropdownConfig {
private val shortAdministrativeAreaNames = country.administrativeAreas.map { it.first }
private val fullAdministrativeAreaNames = country.administrativeAreas.map { it.second }

override val tinyMode: Boolean = false
override val debugLabel = "administrativeArea"

@StringRes
override val label = country.label

override val rawItems = shortAdministrativeAreaNames

override val displayItems: List<String> = fullAdministrativeAreaNames

override fun getSelectedItemLabel(index: Int) = fullAdministrativeAreaNames[index]

override fun convertFromRaw(rawValue: String): String {
return if (shortAdministrativeAreaNames.contains(rawValue)) {
fullAdministrativeAreaNames[shortAdministrativeAreaNames.indexOf(rawValue)]
} else {
fullAdministrativeAreaNames[0]
}
}

sealed class Country(
open val label: Int,
open val administrativeAreas: List<Pair<String, String>>
) {
data class Canada(
override val label: Int = R.string.address_label_province,
override val administrativeAreas: List<Pair<String, String>> = listOf(
Pair("AB", "Alberta"),
Pair("BC", "British Columbia"),
Pair("MB", "Manitoba"),
Pair("NB", "New Brunswick"),
Pair("NL", "Newfoundland and Labrador"),
Pair("NT", "Northwest Territories"),
Pair("NS", "Nova Scotia"),
Pair("NU", "Nunavut"),
Pair("ON", "Ontario"),
Pair("PE", "Prince Edward Island"),
Pair("QC", "Quebec"),
Pair("SK", "Saskatchewan"),
Pair("YT", "Yukon")
)
) : Country(label, administrativeAreas)

data class US(
override val label: Int = R.string.address_label_state,
override val administrativeAreas: List<Pair<String, String>> = listOf(
Pair("AL", "Alabama"),
Pair("AK", "Alaska"),
Pair("AS", "American Samoa"),
Pair("AZ", "Arizona"),
Pair("AR", "Arkansas"),
Pair("AA", "Armed Forces (AA)"),
Pair("AE", "Armed Forces (AE)"),
Pair("AP", "Armed Forces (AP)"),
Pair("CA", "California"),
Pair("CO", "Colorado"),
Pair("CT", "Connecticut"),
Pair("DE", "Delaware"),
Pair("DC", "District of Columbia"),
Pair("FL", "Florida"),
Pair("GA", "Georgia"),
Pair("GU", "Guam"),
Pair("HI", "Hawaii"),
Pair("ID", "Idaho"),
Pair("IL", "Illinois"),
Pair("IN", "Indiana"),
Pair("IA", "Iowa"),
Pair("KS", "Kansas"),
Pair("KY", "Kentucky"),
Pair("LA", "Louisiana"),
Pair("ME", "Maine"),
Pair("MH", "Marshal Islands"),
Pair("MD", "Maryland"),
Pair("MA", "Massachusetts"),
Pair("MI", "Michigan"),
Pair("FM", "Micronesia"),
Pair("MN", "Minnesota"),
Pair("MS", "Mississippi"),
Pair("MO", "Missouri"),
Pair("MT", "Montana"),
Pair("NE", "Nebraska"),
Pair("NV", "Nevada"),
Pair("NH", "New Hampshire"),
Pair("NJ", "New Jersey"),
Pair("NM", "New Mexico"),
Pair("NY", "New York"),
Pair("NC", "North Carolina"),
Pair("ND", "North Dakota"),
Pair("MP", "Northern Mariana Islands"),
Pair("OH", "Ohio"),
Pair("OK", "Oklahoma"),
Pair("OR", "Oregon"),
Pair("PW", "Palau"),
Pair("PA", "Pennsylvania"),
Pair("PR", "Puerto Rico"),
Pair("RI", "Rhode Island"),
Pair("SC", "South Carolina"),
Pair("SD", "South Dakota"),
Pair("TN", "Tennessee"),
Pair("TX", "Texas"),
Pair("UT", "Utah"),
Pair("VT", "Vermont"),
Pair("VI", "Virgin Islands"),
Pair("VA", "Virginia"),
Pair("WA", "Washington"),
Pair("WV", "West Virginia"),
Pair("WI", "Wisconsin"),
Pair("WY", "Wyoming")
)
) : Country(label, administrativeAreas)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.stripe.android.ui.core.elements

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class AdministrativeAreaElement(
override val identifier: IdentifierSpec,
override val controller: DropdownFieldController
) : SectionSingleFieldElement(identifier)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import com.google.common.truth.Truth.assertThat
import com.stripe.android.ui.core.R
import com.stripe.android.ui.core.address.AddressRepository.Companion.supportedCountries
import com.stripe.android.ui.core.elements.AdministrativeAreaConfig
import com.stripe.android.ui.core.elements.AdministrativeAreaElement
import com.stripe.android.ui.core.elements.Capitalization
import com.stripe.android.ui.core.elements.IdentifierSpec
import com.stripe.android.ui.core.elements.KeyboardType
Expand Down Expand Up @@ -48,14 +50,6 @@ class TransformAddressToElementTest {
showOptionalLabel = false
)

val state = SimpleTextSpec(
IdentifierSpec.State,
R.string.address_label_state,
Capitalization.Words,
KeyboardType.Text,
showOptionalLabel = false
)

val zip = SimpleTextSpec(
IdentifierSpec.PostalCode,
R.string.address_label_zip_code,
Expand All @@ -82,9 +76,15 @@ class TransformAddressToElementTest {
cityZipRow.fields[1],
zip
)
verifySimpleTextSpecInTextFieldController(
simpleTextList[3] as SectionSingleFieldElement,
state

// US has state dropdown
val stateDropdownElement = simpleTextList[3] as AdministrativeAreaElement
val stateDropdownController = stateDropdownElement.controller
assertThat(stateDropdownController.displayItems).isEqualTo(
AdministrativeAreaConfig.Country.US().administrativeAreas.map { it.second }
)
assertThat(stateDropdownController.label.first()).isEqualTo(
R.string.address_label_state
)
}

Expand Down
Loading

0 comments on commit 025a6b4

Please sign in to comment.