Skip to content

Commit

Permalink
Credit Card Address and focus bug fixes (#4665)
Browse files Browse the repository at this point in the history
  • Loading branch information
michelleb-stripe authored Mar 9, 2022
1 parent d2ff7ba commit 55a143b
Show file tree
Hide file tree
Showing 18 changed files with 228 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ private fun EmailCollectionSection(
listOf(emailElement.sectionFieldErrorController())
)
),
emptyList()
emptyList(),
emailElement.identifier
)
if (signUpState == SignUpState.VerifyingEmail) {
CircularProgressIndicator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.ui.core.elements
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import com.stripe.android.ui.core.address.AddressFieldElementRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
Expand Down Expand Up @@ -60,6 +61,17 @@ open class AddressElement constructor(
}
}

override fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> = fields.flatMapLatest {
combine(
it
.map {
it.getTextFieldIdentifiers()
}
) {
it.toList().flatten()
}
}

override fun setRawValue(rawValuesMap: Map<IdentifierSpec, String?>) {
this.rawValuesMap = rawValuesMap
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import androidx.compose.ui.Modifier
internal fun AddressElementUI(
enabled: Boolean,
controller: AddressController,
hiddenIdentifiers: List<IdentifierSpec>?
hiddenIdentifiers: List<IdentifierSpec>?,
lastTextFieldIdentifier: IdentifierSpec?
) {
val fields by controller.fieldsFlowable.collectAsState(null)
fields?.let { fieldList ->
Column {
fieldList.forEachIndexed { index, field ->
SectionFieldElementUI(enabled, field, hiddenIdentifiers = hiddenIdentifiers)
SectionFieldElementUI(
enabled,
field,
hiddenIdentifiers = hiddenIdentifiers,
lastTextFieldIdentifier = lastTextFieldIdentifier
)
if ((hiddenIdentifiers?.contains(field.identifier) == false) &&
(index != fieldList.size - 1)
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.stripe.android.ui.core.elements

import android.content.Context
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine

/**
Expand All @@ -19,6 +21,15 @@ internal class CardDetailsElement(
// Nothing from formFragmentArguments to populate
}

override fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> =
MutableStateFlow(
listOf(
controller.numberElement.identifier,
controller.expirationDateElement.identifier,
controller.cvcElement.identifier
)
)

override fun getFormFieldValueFlow() = combine(
controller.numberElement.controller.formFieldValue,
controller.cvcElement.controller.formFieldValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ import androidx.compose.ui.Modifier
internal fun CardDetailsElementUI(
enabled: Boolean,
controller: CardDetailsController,
hiddenIdentifiers: List<IdentifierSpec>?
hiddenIdentifiers: List<IdentifierSpec>?,
lastTextFieldIdentifier: IdentifierSpec?
) {
controller.fields.forEachIndexed { index, field ->
SectionFieldElementUI(enabled, field, hiddenIdentifiers = hiddenIdentifiers)
SectionFieldElementUI(
enabled,
field,
hiddenIdentifiers = hiddenIdentifiers,
lastTextFieldIdentifier = lastTextFieldIdentifier
)
val cardStyle = CardStyle(isSystemInDarkTheme())
Divider(
color = cardStyle.cardBorderColor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.ui.core.elements
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
Expand All @@ -28,7 +29,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.stripe.android.ui.core.R
Expand Down Expand Up @@ -57,7 +61,7 @@ internal fun DropDown(
.indicatorColor(enabled, false, interactionSource)
.value
}

val inputModeManager = LocalInputModeManager.current
Box(
modifier = Modifier
.wrapContentSize(Alignment.TopStart)
Expand All @@ -66,6 +70,9 @@ internal fun DropDown(
// Click handling happens on the box, so that it is a single accessible item
Box(
modifier = Modifier
.focusProperties {
canFocus = inputModeManager.inputMode != InputMode.Touch
}
.clickable(
enabled = enabled,
onClickLabel = stringResource(R.string.change),
Expand Down Expand Up @@ -137,6 +144,7 @@ internal fun DropdownLabel(
error = false,
interactionSource = interactionSource
).value,
modifier = Modifier.focusable(false),
style = MaterialTheme.typography.caption
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.ui.core.elements
import androidx.annotation.RestrictTo
import com.stripe.android.ui.core.forms.FormFieldEntry
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

/**
* This is used to define each section in the visual form layout.
Expand All @@ -14,4 +15,6 @@ sealed class FormElement {
abstract val controller: Controller?

abstract fun getFormFieldValueFlow(): Flow<List<Pair<IdentifierSpec, FormFieldEntry>>>
open fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> =
MutableStateFlow(emptyList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ class RowElement constructor(
it.setRawValue(rawValuesMap)
}
}

override fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> =
fields.map { it.getTextFieldIdentifiers() }.last()
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,61 @@ import androidx.constraintlayout.compose.Dimension
internal fun RowElementUI(
enabled: Boolean,
controller: RowController,
hiddenIdentifiers: List<IdentifierSpec>
hiddenIdentifiers: List<IdentifierSpec>,
lastTextFieldIdentifier: IdentifierSpec?
) {
val fields = controller.fields
val cardStyle = CardStyle(isSystemInDarkTheme())

// An attempt was made to do this with a row, and a vertical divider created with a box.
// The row had a height of IntrinsicSize.Min, and the box/vertical divider filled the height
// when adding in the trailing icon this broke and caused the overall height of the row to
// increase. By using the constraint layout the vertical divider does not negatively effect
// the size of the row.
ConstraintLayout {
// Create references for the composables to constrain
val fieldRefs = fields.map { createRef() }
val dividerRefs = fields.map { createRef() }
// Only draw the row if the items in the row are not hidden, otherwise the entire
// section will fail to draw
if (fields.map { it.identifier }.any { !hiddenIdentifiers.contains(it) }) {
// An attempt was made to do this with a row, and a vertical divider created with a box.
// The row had a height of IntrinsicSize.Min, and the box/vertical divider filled the height
// when adding in the trailing icon this broke and caused the overall height of the row to
// increase. By using the constraint layout the vertical divider does not negatively effect
// the size of the row.
ConstraintLayout {
// Create references for the composables to constrain
val fieldRefs = fields.map { createRef() }
val dividerRefs = fields.map { createRef() }

fields.forEachIndexed { index, field ->
SectionFieldElementUI(
enabled,
field,
Modifier
.constrainAs(fieldRefs[index]) {
if (index == 0) {
start.linkTo(parent.start)
} else {
start.linkTo(dividerRefs[index - 1].end)
}
top.linkTo(parent.top)
}
.fillMaxWidth(
(1f / fields.size.toFloat())
),
hiddenIdentifiers
)

if (index != (fields.size - 1)) {
Divider(
fields.forEachIndexed { index, field ->
SectionFieldElementUI(
enabled,
field,
hiddenIdentifiers = hiddenIdentifiers,
lastTextFieldIdentifier = lastTextFieldIdentifier,
modifier = Modifier
.constrainAs(dividerRefs[index]) {
start.linkTo(fieldRefs[index].end)
.constrainAs(fieldRefs[index]) {
if (index == 0) {
start.linkTo(parent.start)
} else {
start.linkTo(dividerRefs[index - 1].end)
}
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
height = (Dimension.fillToConstraints)
}
.padding(
horizontal = cardStyle.cardBorderWidth
.fillMaxWidth(
(1f / fields.size.toFloat())
)
.width(cardStyle.cardBorderWidth)
.background(cardStyle.cardBorderColor)
)

if (!hiddenIdentifiers.contains(field.identifier) && index != (fields.size - 1)) {
Divider(
modifier = Modifier
.constrainAs(dividerRefs[index]) {
start.linkTo(fieldRefs[index].end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
height = (Dimension.fillToConstraints)
}
.padding(
horizontal = cardStyle.cardBorderWidth
)
.width(cardStyle.cardBorderWidth)
.background(cardStyle.cardBorderColor)
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,14 @@ data class SectionElement(
combine(fields.map { it.getFormFieldValueFlow() }) {
it.toList().flatten()
}

override fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> =
combine(
fields
.map {
it.getTextFieldIdentifiers()
}
) {
it.toList().flatten()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fun SectionElementUI(
enabled: Boolean,
element: SectionElement,
hiddenIdentifiers: List<IdentifierSpec>,
lastTextFieldIdentifier: IdentifierSpec?
) {
if (!hiddenIdentifiers.contains(element.identifier)) {
val controller = element.controller
Expand All @@ -32,7 +33,12 @@ fun SectionElementUI(

Section(controller.label, sectionErrorString) {
element.fields.forEachIndexed { index, field ->
SectionFieldElementUI(enabled, field, hiddenIdentifiers = hiddenIdentifiers)
SectionFieldElementUI(
enabled,
field,
hiddenIdentifiers = hiddenIdentifiers,
lastTextFieldIdentifier = lastTextFieldIdentifier
)
if (index != element.fields.size - 1) {
val cardStyle = CardStyle(isSystemInDarkTheme())
Divider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ sealed interface SectionFieldElement {
fun getFormFieldValueFlow(): Flow<List<Pair<IdentifierSpec, FormFieldEntry>>>
fun sectionFieldErrorController(): SectionFieldErrorController
fun setRawValue(rawValuesMap: Map<IdentifierSpec, String?>)
fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ package com.stripe.android.ui.core.elements

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction

@Composable
internal fun SectionFieldElementUI(
enabled: Boolean,
field: SectionFieldElement,
modifier: Modifier = Modifier,
hiddenIdentifiers: List<IdentifierSpec>? = null
hiddenIdentifiers: List<IdentifierSpec>? = null,
lastTextFieldIdentifier: IdentifierSpec?,
) {
if (hiddenIdentifiers?.contains(field.identifier) == false) {
when (val controller = field.sectionFieldErrorController()) {
is TextFieldController -> {
TextField(
textFieldController = controller,
enabled = enabled,
modifier = modifier
modifier = modifier,
imeAction = if (lastTextFieldIdentifier == field.identifier) {
ImeAction.Done
} else {
ImeAction.Next
}
)
}
is DropdownFieldController -> {
Expand All @@ -29,21 +36,24 @@ internal fun SectionFieldElementUI(
AddressElementUI(
enabled,
controller,
hiddenIdentifiers
hiddenIdentifiers,
lastTextFieldIdentifier
)
}
is RowController -> {
RowElementUI(
enabled,
controller,
hiddenIdentifiers
hiddenIdentifiers,
lastTextFieldIdentifier
)
}
is CardDetailsController -> {
CardDetailsElementUI(
enabled,
controller,
hiddenIdentifiers
hiddenIdentifiers,
lastTextFieldIdentifier
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.ui.core.elements
import androidx.annotation.RestrictTo
import com.stripe.android.ui.core.forms.FormFieldEntry
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map

/**
Expand Down Expand Up @@ -31,4 +32,10 @@ sealed class SectionSingleFieldElement(
override fun setRawValue(rawValuesMap: Map<IdentifierSpec, String?>) {
rawValuesMap[identifier]?.let { controller.onRawValueChange(it) }
}

override fun getTextFieldIdentifiers(): Flow<List<IdentifierSpec>> =
MutableStateFlow(
listOf(identifier).takeIf { controller is TextFieldController }
?: emptyList()
)
}
Loading

0 comments on commit 55a143b

Please sign in to comment.