mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Perform client side checks on name and email for donation flows.
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user