-
Notifications
You must be signed in to change notification settings - Fork 664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MOBILESDK 2683] Request for feedback on approach: Set As Default Payment Method Element in AddCard #10129
[MOBILESDK 2683] Request for feedback on approach: Set As Default Payment Method Element in AddCard #10129
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.stripe.android.ui.core.elements | ||
|
||
import androidx.annotation.RestrictTo | ||
import com.stripe.android.ui.core.R | ||
import com.stripe.android.uicore.elements.FieldError | ||
import com.stripe.android.uicore.elements.InputController | ||
import com.stripe.android.uicore.forms.FormFieldEntry | ||
import com.stripe.android.uicore.utils.combineAsStateFlow | ||
import com.stripe.android.uicore.utils.mapAsStateFlow | ||
import com.stripe.android.uicore.utils.stateFlowOf | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) | ||
class SetAsDefaultPaymentMethodController( | ||
setAsDefaultPaymentMethodInitialValue: Boolean = false | ||
) : InputController { | ||
override val label: StateFlow<Int> = MutableStateFlow( | ||
R.string.stripe_set_as_default_payment_method | ||
) | ||
|
||
private val _setAsDefaultPaymentMethod = MutableStateFlow(setAsDefaultPaymentMethodInitialValue) | ||
val setAsDefaultPaymentMethod: StateFlow<Boolean> = _setAsDefaultPaymentMethod | ||
|
||
override val fieldValue: StateFlow<String> = setAsDefaultPaymentMethod.mapAsStateFlow { it.toString() } | ||
override val rawFieldValue: StateFlow<String?> = fieldValue | ||
|
||
override val error: StateFlow<FieldError?> = stateFlowOf(null) | ||
override val showOptionalLabel: Boolean = false | ||
override val isComplete: StateFlow<Boolean> = stateFlowOf(true) | ||
override val formFieldValue: StateFlow<FormFieldEntry> = | ||
combineAsStateFlow(isComplete, rawFieldValue) { complete, value -> | ||
FormFieldEntry(value, complete) | ||
} | ||
|
||
fun onValueChange(setAsDefaultPaymentMethod: Boolean) { | ||
_setAsDefaultPaymentMethod.value = setAsDefaultPaymentMethod | ||
} | ||
|
||
override fun onRawValueChange(rawValue: String) { | ||
onValueChange(rawValue.toBooleanStrictOrNull() ?: true) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.stripe.android.ui.core.elements | ||
|
||
import androidx.annotation.RestrictTo | ||
import com.stripe.android.core.strings.ResolvableString | ||
import com.stripe.android.uicore.elements.FormElement | ||
import com.stripe.android.uicore.elements.IdentifierSpec | ||
import com.stripe.android.uicore.forms.FormFieldEntry | ||
import com.stripe.android.uicore.utils.mapAsStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
/** | ||
* This is an element that will set elements | ||
*/ | ||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) | ||
data class SetAsDefaultPaymentMethodElement( | ||
val initialValue: Boolean, | ||
val shouldShowElementFlow: StateFlow<Boolean> | ||
): FormElement { | ||
override val identifier: IdentifierSpec = IdentifierSpec.SetAsDefaultPaymentMethod | ||
|
||
override val controller: SetAsDefaultPaymentMethodController = SetAsDefaultPaymentMethodController( | ||
setAsDefaultPaymentMethodInitialValue = initialValue | ||
) | ||
override val allowsUserInteraction: Boolean = true | ||
|
||
override val mandateText: ResolvableString? = null | ||
|
||
override fun getFormFieldValueFlow(): StateFlow<List<Pair<IdentifierSpec, FormFieldEntry>>> = | ||
controller.formFieldValue.mapAsStateFlow { | ||
listOf( | ||
identifier to it | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.stripe.android.ui.core.elements | ||
|
||
import androidx.annotation.RestrictTo | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import com.stripe.android.uicore.elements.CheckboxElementUI | ||
import com.stripe.android.uicore.utils.collectAsState | ||
|
||
const val SET_AS_DEFAULT_PAYMENT_METHOD_TEST_TAG = "SET_AS_DEFAULT_PAYMENT_METHOD_TEST_TAG" | ||
|
||
@Composable | ||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) | ||
fun SetAsDefaultPaymentMethodElementUI( | ||
enabled: Boolean, | ||
element: SetAsDefaultPaymentMethodElement, | ||
modifier: Modifier = Modifier, | ||
) { | ||
val controller = element.controller | ||
val checked by controller.setAsDefaultPaymentMethod.collectAsState() | ||
val label by controller.label.collectAsState() | ||
val resources = LocalContext.current.resources | ||
|
||
val shouldShow = element.shouldShowElementFlow.collectAsState() | ||
|
||
if (shouldShow.value) { | ||
CheckboxElementUI( | ||
automationTestTag = SET_AS_DEFAULT_PAYMENT_METHOD_TEST_TAG, | ||
isChecked = checked, | ||
label = resources.getString(label), | ||
isEnabled = enabled, | ||
modifier = modifier, | ||
onValueChange = { | ||
controller.onValueChange(!checked) | ||
}, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import com.stripe.android.ui.core.elements.CardDetailsSectionElement | |
import com.stripe.android.ui.core.elements.EmailElement | ||
import com.stripe.android.ui.core.elements.MandateTextElement | ||
import com.stripe.android.ui.core.elements.SaveForFutureUseElement | ||
import com.stripe.android.ui.core.elements.SetAsDefaultPaymentMethodElement | ||
import com.stripe.android.uicore.elements.FormElement | ||
import com.stripe.android.uicore.elements.IdentifierSpec | ||
import com.stripe.android.uicore.elements.PhoneNumberController | ||
|
@@ -109,10 +110,18 @@ private object CardUiDefinitionFactory : UiDefinitionFactory.Simple { | |
|
||
val canChangeSaveForFutureUsage = saveForFutureUsageIsChangeable(metadata) | ||
|
||
val saveForFutureUseElement = SaveForFutureUseElement(arguments.saveForFutureUseInitialValue, arguments.merchantName) | ||
val isSaveForFutureUseCheckedFlow = saveForFutureUseElement.controller.saveForFutureUse | ||
|
||
if (canChangeSaveForFutureUsage) { | ||
add(SaveForFutureUseElement(arguments.saveForFutureUseInitialValue, arguments.merchantName)) | ||
add(saveForFutureUseElement) | ||
add(SetAsDefaultPaymentMethodElement( | ||
initialValue = false, | ||
shouldShowElementFlow = isSaveForFutureUseCheckedFlow | ||
)) | ||
} | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the approach is sound. It has its upsides and tradeoffs.
Tradeoffs here are that the element is not a serializable element and is coupled to the state of whatever decides to show the element (in this case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that this approach lgtm! I do think we only care about serializing the values -- when do we parcelize the form elements themselves @samer-stripe? |
||
val signupMode = if ( | ||
metadata.linkInlineConfiguration != null && arguments.linkConfigurationCoordinator != null | ||
) { | ||
|
@@ -159,7 +168,7 @@ private object CardUiDefinitionFactory : UiDefinitionFactory.Simple { | |
code = PaymentMethod.Type.Card.code, | ||
intent = metadata.stripeIntent, | ||
paymentMethodSaveConsentBehavior = metadata.paymentMethodSaveConsentBehavior, | ||
hasCustomerConfiguration = metadata.customerMetadata.hasCustomerConfiguration, | ||
hasCustomerConfiguration = metadata.customerMetadata != null, | ||
) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably doesn't make sense as a
data class
since we are passing aStateFlow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that it should be a normal class instead then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup! That way we don't generate the additional
equals
andcopy
methods.