Add minimum amount error for boosts.

This commit is contained in:
Alex Hart
2022-12-06 15:55:10 -04:00
committed by Cody Henthorne
parent 1618141342
commit 0bef37bfc1
9 changed files with 99 additions and 5 deletions

View File

@@ -85,6 +85,16 @@ fun DonationsConfiguration.getSubscriptionLevels(): Map<Int, LevelConfiguration>
return levels.filterKeys { SUBSCRIPTION_LEVELS.contains(it) }.toSortedMap()
}
/**
* Get a map describing the minimum donation amounts per currency.
* This returns only the currencies available to the user.
*/
fun DonationsConfiguration.getMinimumDonationAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, FiatMoney> {
return getFilteredCurrencies(paymentMethodAvailability)
.mapKeys { Currency.getInstance(it.key.uppercase()) }
.mapValues { FiatMoney(it.value.minimum, it.key) }
}
private fun DonationsConfiguration.getFilteredCurrencies(paymentMethodAvailability: PaymentMethodAvailability): Map<String, DonationsConfiguration.CurrencyConfiguration> {
val userPaymentMethods = paymentMethodAvailability.toSet()
val availableCurrencyCodes = PlatformCurrencyUtil.getAvailableCurrencyCodes()

View File

@@ -64,6 +64,13 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
.map { it.getBoostBadges().first() }
}
fun getMinimumDonationAmounts(): Single<Map<Currency, FiatMoney>> {
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
.flatMap { it.flattenResult() }
.subscribeOn(Schedulers.io())
.map { it.getMinimumDonationAmounts() }
}
fun waitForOneTimeRedemption(
price: FiatMoney,
paymentIntentId: String,

View File

@@ -10,6 +10,7 @@ import android.text.Spanned
import android.text.TextWatcher
import android.text.method.DigitsKeyListener
import android.view.View
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.animation.doOnEnd
@@ -26,6 +27,7 @@ import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.visible
import java.lang.Integer.min
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
@@ -102,7 +104,9 @@ data class Boost(
val currency: Currency,
override val isEnabled: Boolean,
val onBoostClick: (View, Boost) -> Unit,
val minimumAmount: FiatMoney,
val isCustomAmountFocused: Boolean,
val isCustomAmountTooSmall: Boolean,
val onCustomAmountChanged: (String) -> Unit,
val onCustomAmountFocusChanged: (Boolean) -> Unit,
) : PreferenceModel<SelectionModel>(isEnabled = isEnabled) {
@@ -113,7 +117,10 @@ data class Boost(
newItem.boosts == boosts &&
newItem.selectedBoost == selectedBoost &&
newItem.currency == currency &&
newItem.isCustomAmountFocused == isCustomAmountFocused
newItem.isCustomAmountFocused == isCustomAmountFocused &&
newItem.isCustomAmountTooSmall == isCustomAmountTooSmall &&
newItem.minimumAmount.amount == minimumAmount.amount &&
newItem.minimumAmount.currency == minimumAmount.currency
}
}
@@ -126,6 +133,7 @@ data class Boost(
private val boost5: MaterialButton = itemView.findViewById(R.id.boost_5)
private val boost6: MaterialButton = itemView.findViewById(R.id.boost_6)
private val custom: AppCompatEditText = itemView.findViewById(R.id.boost_custom)
private val error: TextView = itemView.findViewById(R.id.boost_custom_too_small)
private val boostButtons: List<MaterialButton>
get() {
@@ -145,6 +153,16 @@ data class Boost(
override fun bind(model: SelectionModel) {
itemView.isEnabled = model.isEnabled
error.text = context.getString(
R.string.Boost__the_minimum_amount_you_can_donate_is_s,
FiatMoneyUtil.format(
context.resources, model.minimumAmount,
FiatMoneyUtil.formatOptions().trimZerosAfterDecimal()
)
)
error.visible = model.isCustomAmountTooSmall
model.boosts.zip(boostButtons).forEach { (boost, button) ->
val isSelected = boost == model.selectedBoost && !model.isCustomAmountFocused
button.isSelected = isSelected

View File

@@ -242,6 +242,7 @@ class DonateToSignalFragment :
when (state.donateToSignalType) {
DonateToSignalType.ONE_TIME -> displayOneTimeSelection(state.areFieldsEnabled, state.oneTimeDonationState)
DonateToSignalType.MONTHLY -> displayMonthlySelection(state.areFieldsEnabled, state.monthlyDonationState)
DonateToSignalType.GIFT -> error("This fragment does not support gifts.")
}
space(20.dp)
@@ -310,6 +311,8 @@ class DonateToSignalFragment :
selectedBoost = state.selectedBoost,
currency = state.customAmount.currency,
isCustomAmountFocused = state.isCustomAmountFocused,
isCustomAmountTooSmall = state.shouldDisplayCustomAmountTooSmallError,
minimumAmount = state.minimumDonationAmountOfSelectedCurrency,
isEnabled = areFieldsEnabled,
onBoostClick = { view, boost ->
startAnimationAboveSelectedBoost(view)

View File

@@ -81,9 +81,15 @@ data class DonateToSignalState(
val customAmount: FiatMoney = FiatMoney(BigDecimal.ZERO, selectedCurrency),
val isCustomAmountFocused: Boolean = false,
val donationStage: DonationStage = DonationStage.INIT,
val selectableCurrencyCodes: List<String> = emptyList()
val selectableCurrencyCodes: List<String> = emptyList(),
private val minimumDonationAmounts: Map<Currency, FiatMoney> = emptyMap()
) {
val isSelectionValid: Boolean = if (isCustomAmountFocused) customAmount.amount > BigDecimal.ZERO else selectedBoost != null
val minimumDonationAmountOfSelectedCurrency: FiatMoney = minimumDonationAmounts[selectedCurrency] ?: FiatMoney(BigDecimal.ZERO, selectedCurrency)
private val isCustomAmountTooSmall: Boolean = if (isCustomAmountFocused) customAmount.amount < minimumDonationAmountOfSelectedCurrency.amount else false
private val isCustomAmountZero: Boolean = customAmount.amount == BigDecimal.ZERO
val isSelectionValid: Boolean = if (isCustomAmountFocused) !isCustomAmountTooSmall else selectedBoost != null
val shouldDisplayCustomAmountTooSmallError: Boolean = isCustomAmountTooSmall && !isCustomAmountZero
}
data class MonthlyDonationState(

View File

@@ -214,6 +214,15 @@ class DonateToSignalViewModel(
}
)
oneTimeDonationDisposables += oneTimeDonationRepository.getMinimumDonationAmounts().subscribeBy(
onSuccess = { amountMap ->
store.update { it.copy(oneTimeDonationState = it.oneTimeDonationState.copy(minimumDonationAmounts = amountMap)) }
},
onError = {
Log.w(TAG, "Could not load minimum custom donation amounts.", it)
}
)
val boosts: Observable<Map<Currency, List<Boost>>> = oneTimeDonationRepository.getBoosts().toObservable()
val oneTimeCurrency: Observable<Currency> = SignalStore.donationsValues().observableOneTimeCurrency