Perform client side checks on name and email for donation flows.

This commit is contained in:
Cody Henthorne
2023-11-30 10:46:34 -05:00
parent 1005be006f
commit 0b0c54d874
8 changed files with 204 additions and 43 deletions

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer
object BankDetailsValidator {
private val EMAIL_REGEX: Regex = ".+@.+\\..+".toRegex()
fun validName(name: String): Boolean {
return name.length >= 2
}
fun validEmail(email: String): Boolean {
return email.length >= 3 && email.matches(EMAIL_REGEX)
}
}

View File

@@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.ga
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankTransferRequestKeys import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankTransferRequestKeys
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.details.BankTransferDetailsViewModel.Field
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.compose.ComposeFragment import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.payments.FiatMoneyUtil import org.thoughtcrime.securesms.payments.FiatMoneyUtil
@@ -133,7 +134,7 @@ class BankTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate.
setDisplayFindAccountInfoSheet = viewModel::setDisplayFindAccountInfoSheet, setDisplayFindAccountInfoSheet = viewModel::setDisplayFindAccountInfoSheet,
onLearnMoreClick = this::onLearnMoreClick, onLearnMoreClick = this::onLearnMoreClick,
onDonateClick = this::onDonateClick, onDonateClick = this::onDonateClick,
onIBANFocusChanged = viewModel::onIBANFocusChanged, onFocusChanged = viewModel::onFocusChanged,
donateLabel = donateLabel donateLabel = donateLabel
) )
} }
@@ -186,7 +187,7 @@ private fun BankTransferDetailsContentPreview() {
setDisplayFindAccountInfoSheet = {}, setDisplayFindAccountInfoSheet = {},
onLearnMoreClick = {}, onLearnMoreClick = {},
onDonateClick = {}, onDonateClick = {},
onIBANFocusChanged = {}, onFocusChanged = { _, _ -> },
donateLabel = "Donate $5/month" donateLabel = "Donate $5/month"
) )
} }
@@ -202,7 +203,7 @@ private fun BankTransferDetailsContent(
setDisplayFindAccountInfoSheet: (Boolean) -> Unit, setDisplayFindAccountInfoSheet: (Boolean) -> Unit,
onLearnMoreClick: () -> Unit, onLearnMoreClick: () -> Unit,
onDonateClick: () -> Unit, onDonateClick: () -> Unit,
onIBANFocusChanged: (Boolean) -> Unit, onFocusChanged: (Field, Boolean) -> Unit,
donateLabel: String donateLabel: String
) { ) {
Scaffolds.Settings( Scaffolds.Settings(
@@ -275,7 +276,7 @@ private fun BankTransferDetailsContent(
.fillMaxWidth() .fillMaxWidth()
.padding(top = 12.dp) .padding(top = 12.dp)
.defaultMinSize(minHeight = 78.dp) .defaultMinSize(minHeight = 78.dp)
.onFocusChanged { onIBANFocusChanged(it.hasFocus) } .onFocusChanged { onFocusChanged(Field.IBAN, it.hasFocus) }
.focusRequester(focusRequester) .focusRequester(focusRequester)
) )
} }
@@ -294,11 +295,17 @@ private fun BankTransferDetailsContent(
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) } onNext = { focusManager.moveFocus(FocusDirection.Down) }
), ),
supportingText = {}, isError = state.showNameError(),
supportingText = {
if (state.showNameError()) {
Text(text = stringResource(id = R.string.BankTransferDetailsFragment__minimum_2_characters))
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp) .padding(top = 16.dp)
.defaultMinSize(minHeight = 78.dp) .defaultMinSize(minHeight = 78.dp)
.onFocusChanged { onFocusChanged(Field.NAME, it.hasFocus) }
) )
} }
@@ -316,11 +323,17 @@ private fun BankTransferDetailsContent(
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onDone = { onDonateClick() } onDone = { onDonateClick() }
), ),
supportingText = {}, isError = state.showEmailError(),
supportingText = {
if (state.showEmailError()) {
Text(text = stringResource(id = R.string.BankTransferDetailsFragment__invalid_email_address))
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp) .padding(top = 16.dp)
.defaultMinSize(minHeight = 78.dp) .defaultMinSize(minHeight = 78.dp)
.onFocusChanged { onFocusChanged(Field.EMAIL, it.hasFocus) }
) )
} }

View File

@@ -6,15 +6,26 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.details package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.details
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankDetailsValidator
data class BankTransferDetailsState( data class BankTransferDetailsState(
val name: String = "", val name: String = "",
val nameFocusState: FocusState = FocusState.NOT_FOCUSED,
val iban: String = "", val iban: String = "",
val email: String = "", val email: String = "",
val emailFocusState: FocusState = FocusState.NOT_FOCUSED,
val ibanValidity: IBANValidator.Validity = IBANValidator.Validity.POTENTIALLY_VALID, val ibanValidity: IBANValidator.Validity = IBANValidator.Validity.POTENTIALLY_VALID,
val displayFindAccountInfoSheet: Boolean = false val displayFindAccountInfoSheet: Boolean = false
) { ) {
val canProceed = name.isNotBlank() && email.isNotBlank() && ibanValidity == IBANValidator.Validity.COMPLETELY_VALID val canProceed = BankDetailsValidator.validName(name) && BankDetailsValidator.validEmail(email) && ibanValidity == IBANValidator.Validity.COMPLETELY_VALID
fun showNameError(): Boolean {
return nameFocusState == FocusState.LOST_FOCUS && !BankDetailsValidator.validName(name)
}
fun showEmailError(): Boolean {
return emailFocusState == FocusState.LOST_FOCUS && !BankDetailsValidator.validEmail(email)
}
fun asSEPADebitData(): StripeApi.SEPADebitData { fun asSEPADebitData(): StripeApi.SEPADebitData {
return StripeApi.SEPADebitData( return StripeApi.SEPADebitData(
@@ -23,4 +34,10 @@ data class BankTransferDetailsState(
email = email.trim() email = email.trim()
) )
} }
enum class FocusState {
NOT_FOCUSED,
FOCUSED,
LOST_FOCUS
}
} }

View File

@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.t
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.details.BankTransferDetailsState.FocusState
class BankTransferDetailsViewModel : ViewModel() { class BankTransferDetailsViewModel : ViewModel() {
@@ -30,10 +31,30 @@ class BankTransferDetailsViewModel : ViewModel() {
) )
} }
fun onIBANFocusChanged(isFocused: Boolean) { fun onFocusChanged(field: Field, isFocused: Boolean) {
internalState.value = internalState.value.copy( when (field) {
ibanValidity = IBANValidator.validate(internalState.value.iban, isFocused) Field.IBAN -> {
) internalState.value = internalState.value.copy(
ibanValidity = IBANValidator.validate(internalState.value.iban, isFocused)
)
}
Field.NAME -> {
if (isFocused && internalState.value.nameFocusState == FocusState.NOT_FOCUSED) {
internalState.value = internalState.value.copy(nameFocusState = FocusState.FOCUSED)
} else if (!isFocused && internalState.value.nameFocusState == FocusState.FOCUSED) {
internalState.value = internalState.value.copy(nameFocusState = FocusState.LOST_FOCUS)
}
}
Field.EMAIL -> {
if (isFocused && internalState.value.emailFocusState == FocusState.NOT_FOCUSED) {
internalState.value = internalState.value.copy(emailFocusState = FocusState.FOCUSED)
} else if (!isFocused && internalState.value.emailFocusState == FocusState.FOCUSED) {
internalState.value = internalState.value.copy(emailFocusState = FocusState.LOST_FOCUS)
}
}
}
} }
fun onIBANChanged(iban: String) { fun onIBANChanged(iban: String) {
@@ -48,4 +69,10 @@ class BankTransferDetailsViewModel : ViewModel() {
email = email email = email
) )
} }
enum class Field {
IBAN,
NAME,
EMAIL
}
} }

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.t
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.StringRes
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -28,6 +29,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -41,7 +44,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@@ -62,12 +64,14 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.ga
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankTransferRequestKeys import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankTransferRequestKeys
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.ideal.IdealTransferDetailsViewModel.Field
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.compose.ComposeFragment import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.payments.FiatMoneyUtil import org.thoughtcrime.securesms.payments.FiatMoneyUtil
import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.fragments.requireListener import org.thoughtcrime.securesms.util.fragments.requireListener
import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.viewModel
/** /**
* Fragment for inputting necessary bank transfer information for iDEAL donation * Fragment for inputting necessary bank transfer information for iDEAL donation
@@ -75,7 +79,9 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate
class IdealTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate.ErrorHandlerCallback { class IdealTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate.ErrorHandlerCallback {
private val args: IdealTransferDetailsFragmentArgs by navArgs() private val args: IdealTransferDetailsFragmentArgs by navArgs()
private val viewModel: IdealTransferDetailsViewModel by viewModels() private val viewModel: IdealTransferDetailsViewModel by viewModel {
IdealTransferDetailsViewModel(args.request.donateToSignalType == DonateToSignalType.MONTHLY)
}
private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels( private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels(
R.id.donate_to_signal, R.id.donate_to_signal,
@@ -127,14 +133,24 @@ class IdealTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate
} }
} }
val idealDirections = remember(args.request) {
if (args.request.donateToSignalType == DonateToSignalType.MONTHLY) {
R.string.IdealTransferDetailsFragment__enter_your_bank
} else {
R.string.IdealTransferDetailsFragment__enter_your_bank_details_one_time
}
}
IdealTransferDetailsContent( IdealTransferDetailsContent(
state = state, state = state,
idealDirections = idealDirections,
donateLabel = donateLabel, donateLabel = donateLabel,
onNavigationClick = { findNavController().popBackStack() }, onNavigationClick = { findNavController().popBackStack() },
onLearnMoreClick = { findNavController().navigate(IdealTransferDetailsFragmentDirections.actionBankTransferDetailsFragmentToYourInformationIsPrivateBottomSheet()) }, onLearnMoreClick = { findNavController().navigate(IdealTransferDetailsFragmentDirections.actionBankTransferDetailsFragmentToYourInformationIsPrivateBottomSheet()) },
onSelectBankClick = { findNavController().navigate(IdealTransferDetailsFragmentDirections.actionIdealTransferDetailsFragmentToIdealTransferBankSelectionDialogFragment()) }, onSelectBankClick = { findNavController().navigate(IdealTransferDetailsFragmentDirections.actionIdealTransferDetailsFragmentToIdealTransferBankSelectionDialogFragment()) },
onNameChanged = viewModel::onNameChanged, onNameChanged = viewModel::onNameChanged,
onEmailChanged = viewModel::onEmailChanged, onEmailChanged = viewModel::onEmailChanged,
onFocusChanged = viewModel::onFocusChanged,
onDonateClick = this::onDonateClick onDonateClick = this::onDonateClick
) )
} }
@@ -171,13 +187,15 @@ class IdealTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate
@Composable @Composable
private fun IdealTransferDetailsContentPreview() { private fun IdealTransferDetailsContentPreview() {
IdealTransferDetailsContent( IdealTransferDetailsContent(
state = IdealTransferDetailsState(), state = IdealTransferDetailsState(isMonthly = true),
idealDirections = R.string.IdealTransferDetailsFragment__enter_your_bank,
donateLabel = "Donate $5/month", donateLabel = "Donate $5/month",
onNavigationClick = {}, onNavigationClick = {},
onLearnMoreClick = {}, onLearnMoreClick = {},
onSelectBankClick = {}, onSelectBankClick = {},
onNameChanged = {}, onNameChanged = {},
onEmailChanged = {}, onEmailChanged = {},
onFocusChanged = { _, _ -> },
onDonateClick = {} onDonateClick = {}
) )
} }
@@ -185,12 +203,14 @@ private fun IdealTransferDetailsContentPreview() {
@Composable @Composable
private fun IdealTransferDetailsContent( private fun IdealTransferDetailsContent(
state: IdealTransferDetailsState, state: IdealTransferDetailsState,
@StringRes idealDirections: Int,
donateLabel: String, donateLabel: String,
onNavigationClick: () -> Unit, onNavigationClick: () -> Unit,
onLearnMoreClick: () -> Unit, onLearnMoreClick: () -> Unit,
onSelectBankClick: () -> Unit, onSelectBankClick: () -> Unit,
onNameChanged: (String) -> Unit, onNameChanged: (String) -> Unit,
onEmailChanged: (String) -> Unit, onEmailChanged: (String) -> Unit,
onFocusChanged: (Field, Boolean) -> Unit,
onDonateClick: () -> Unit onDonateClick: () -> Unit
) { ) {
Scaffolds.Settings( Scaffolds.Settings(
@@ -211,7 +231,7 @@ private fun IdealTransferDetailsContent(
) { ) {
item { item {
val learnMore = stringResource(id = R.string.IdealTransferDetailsFragment__learn_more) val learnMore = stringResource(id = R.string.IdealTransferDetailsFragment__learn_more)
val fullString = stringResource(id = R.string.IdealTransferDetailsFragment__enter_your_bank, learnMore) val fullString = stringResource(id = idealDirections, learnMore)
Texts.LinkifiedText( Texts.LinkifiedText(
textWithUrlSpans = SpanUtil.urlSubsequence(fullString, learnMore, stringResource(id = R.string.donate_faq_url)), textWithUrlSpans = SpanUtil.urlSubsequence(fullString, learnMore, stringResource(id = R.string.donate_faq_url)),
@@ -248,38 +268,52 @@ private fun IdealTransferDetailsContent(
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) } onNext = { focusManager.moveFocus(FocusDirection.Down) }
), ),
supportingText = {}, isError = state.showNameError(),
supportingText = {
if (state.showNameError()) {
Text(text = stringResource(id = R.string.BankTransferDetailsFragment__minimum_2_characters))
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp) .padding(top = 16.dp)
.defaultMinSize(minHeight = 78.dp) .defaultMinSize(minHeight = 78.dp)
.onFocusChanged { onFocusChanged(Field.NAME, it.hasFocus) }
) )
} }
item { if (state.isMonthly) {
TextField( item {
value = state.email, TextField(
onValueChange = onEmailChanged, value = state.email,
label = { onValueChange = onEmailChanged,
Text(text = stringResource(id = R.string.IdealTransferDetailsFragment__email)) label = {
}, Text(text = stringResource(id = R.string.IdealTransferDetailsFragment__email))
keyboardOptions = KeyboardOptions( },
keyboardType = KeyboardType.Email, keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done keyboardType = KeyboardType.Email,
), imeAction = ImeAction.Done
keyboardActions = KeyboardActions( ),
onDone = { keyboardActions = KeyboardActions(
if (state.canProceed()) { onDone = {
onDonateClick() if (state.canProceed()) {
onDonateClick()
}
} }
} ),
), isError = state.showEmailError(),
supportingText = {}, supportingText = {
modifier = Modifier if (state.showEmailError()) {
.fillMaxWidth() Text(text = stringResource(id = R.string.BankTransferDetailsFragment__invalid_email_address))
.padding(top = 16.dp) }
.defaultMinSize(minHeight = 78.dp) },
) modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.defaultMinSize(minHeight = 78.dp)
.onFocusChanged { onFocusChanged(Field.EMAIL, it.hasFocus) }
)
}
} }
} }
@@ -324,6 +358,7 @@ private fun IdealBankSelector(
Image( Image(
painter = painterResource(id = uiValues?.icon ?: R.drawable.bank_transfer), painter = painterResource(id = uiValues?.icon ?: R.drawable.bank_transfer),
contentDescription = null, contentDescription = null,
colorFilter = if (uiValues?.icon == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurface) else null,
modifier = Modifier modifier = Modifier
.padding(start = 16.dp, end = 12.dp) .padding(start = 16.dp, end = 12.dp)
.size(32.dp) .size(32.dp)

View File

@@ -6,12 +6,25 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.ideal package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.ideal
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.BankDetailsValidator
data class IdealTransferDetailsState( data class IdealTransferDetailsState(
val isMonthly: Boolean,
val idealBank: IdealBank? = null, val idealBank: IdealBank? = null,
val name: String = "", val name: String = "",
val email: String = "" val nameFocusState: FocusState = FocusState.NOT_FOCUSED,
val email: String = "",
val emailFocusState: FocusState = FocusState.NOT_FOCUSED
) { ) {
fun showNameError(): Boolean {
return nameFocusState == FocusState.LOST_FOCUS && !BankDetailsValidator.validName(name)
}
fun showEmailError(): Boolean {
return emailFocusState == FocusState.LOST_FOCUS && !BankDetailsValidator.validEmail(email)
}
fun asIDEALData(): StripeApi.IDEALData { fun asIDEALData(): StripeApi.IDEALData {
return StripeApi.IDEALData( return StripeApi.IDEALData(
bank = idealBank!!.code, bank = idealBank!!.code,
@@ -21,6 +34,12 @@ data class IdealTransferDetailsState(
} }
fun canProceed(): Boolean { fun canProceed(): Boolean {
return idealBank != null && name.isNotBlank() && email.isNotBlank() return idealBank != null && BankDetailsValidator.validName(name) && (!isMonthly || BankDetailsValidator.validEmail(email))
}
enum class FocusState {
NOT_FOCUSED,
FOCUSED,
LOST_FOCUS
} }
} }

View File

@@ -9,9 +9,9 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
class IdealTransferDetailsViewModel : ViewModel() { class IdealTransferDetailsViewModel(isMonthly: Boolean) : ViewModel() {
private val internalState = mutableStateOf(IdealTransferDetailsState()) private val internalState = mutableStateOf(IdealTransferDetailsState(isMonthly = isMonthly))
var state: State<IdealTransferDetailsState> = internalState var state: State<IdealTransferDetailsState> = internalState
fun onNameChanged(name: String) { fun onNameChanged(name: String) {
@@ -26,9 +26,34 @@ class IdealTransferDetailsViewModel : ViewModel() {
) )
} }
fun onFocusChanged(field: Field, isFocused: Boolean) {
when (field) {
Field.NAME -> {
if (isFocused && internalState.value.nameFocusState == IdealTransferDetailsState.FocusState.NOT_FOCUSED) {
internalState.value = internalState.value.copy(nameFocusState = IdealTransferDetailsState.FocusState.FOCUSED)
} else if (!isFocused && internalState.value.nameFocusState == IdealTransferDetailsState.FocusState.FOCUSED) {
internalState.value = internalState.value.copy(nameFocusState = IdealTransferDetailsState.FocusState.LOST_FOCUS)
}
}
Field.EMAIL -> {
if (isFocused && internalState.value.emailFocusState == IdealTransferDetailsState.FocusState.NOT_FOCUSED) {
internalState.value = internalState.value.copy(emailFocusState = IdealTransferDetailsState.FocusState.FOCUSED)
} else if (!isFocused && internalState.value.emailFocusState == IdealTransferDetailsState.FocusState.FOCUSED) {
internalState.value = internalState.value.copy(emailFocusState = IdealTransferDetailsState.FocusState.LOST_FOCUS)
}
}
}
}
fun onBankSelected(idealBank: IdealBank) { fun onBankSelected(idealBank: IdealBank) {
internalState.value = internalState.value.copy( internalState.value = internalState.value.copy(
idealBank = idealBank idealBank = idealBank
) )
} }
enum class Field {
NAME,
EMAIL
}
} }

View File

@@ -5966,12 +5966,18 @@
<string name="BankTransferDetailsFragment__iban_country_code_is_not_supported">IBAN country code is not supported</string> <string name="BankTransferDetailsFragment__iban_country_code_is_not_supported">IBAN country code is not supported</string>
<!-- Error label for IBAN field when number is invalid --> <!-- Error label for IBAN field when number is invalid -->
<string name="BankTransferDetailsFragment__invalid_iban">Invalid IBAN</string> <string name="BankTransferDetailsFragment__invalid_iban">Invalid IBAN</string>
<!-- Error label for name field when name is not at least two characters long -->
<string name="BankTransferDetailsFragment__minimum_2_characters">Minimum 2 characters</string>
<!-- Error label for email field when email is not valid -->
<string name="BankTransferDetailsFragment__invalid_email_address">Invalid email address</string>
<!-- IdealTransferDetailsFragment --> <!-- IdealTransferDetailsFragment -->
<!-- Title of the screen, displayed in the toolbar --> <!-- Title of the screen, displayed in the toolbar -->
<string name="IdealTransferDetailsFragment__ideal">iDEAL</string> <string name="IdealTransferDetailsFragment__ideal">iDEAL</string>
<!-- Subtitle of the screen, displayed below the toolbar. Placeholder is for \'learn more\' --> <!-- Subtitle of the screen, displayed below the toolbar. Placeholder is for \'learn more\' -->
<string name="IdealTransferDetailsFragment__enter_your_bank">Enter your bank, name and email. Stripe uses this email to send you updates about your donation. %1$s</string> <string name="IdealTransferDetailsFragment__enter_your_bank">Enter your bank, name and email. Stripe uses this email to send you updates about your donation. %1$s</string>
<!-- Subtitle of the screen, displayed below the toolbar. Placeholder is for \'learn more\' -->
<string name="IdealTransferDetailsFragment__enter_your_bank_details_one_time">Enter your bank details. Signal does not collect or store your personal information. %1$s</string>
<!-- Subtitle learn-more button displayed inline with the subtitle text --> <!-- Subtitle learn-more button displayed inline with the subtitle text -->
<string name="IdealTransferDetailsFragment__learn_more">Learn more</string> <string name="IdealTransferDetailsFragment__learn_more">Learn more</string>
<!-- Hint label for text entry box for name on bank account --> <!-- Hint label for text entry box for name on bank account -->