diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppPaymentsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppPaymentsRepository.kt index a7b3be2523..46f5407233 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppPaymentsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/InAppPaymentsRepository.kt @@ -70,6 +70,12 @@ object InAppPaymentsRepository { private const val JOB_PREFIX = "InAppPayments__" private val TAG = Log.tag(InAppPaymentsRepository::class.java) + /** + * Upper bound on how long we'll wait for the donations configuration before surfacing a retryable + * failure rather than leaving the user on an indefinite loading spinner (e.g. on a slow VPN). + */ + const val DONATIONS_CONFIGURATION_TIMEOUT_SECONDS = 30L + private val backupExpirationTimeout = 30.days private val backupExpirationDeletion = 60.days diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/OneTimeInAppPaymentRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/OneTimeInAppPaymentRepository.kt index 5f69ba1292..cc805ce963 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/OneTimeInAppPaymentRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/OneTimeInAppPaymentRepository.kt @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import java.util.Currency import java.util.Locale +import java.util.concurrent.TimeUnit /** * Shared one-time payment methods that apply to both Stripe and PayPal payments. @@ -77,6 +78,7 @@ object OneTimeInAppPaymentRepository { fun getBoosts(): Single>> { return Single.fromCallable { AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault()) } .subscribeOn(Schedulers.io()) + .timeout(InAppPaymentsRepository.DONATIONS_CONFIGURATION_TIMEOUT_SECONDS, TimeUnit.SECONDS) .flatMap { it.flattenResult() } .map { config -> config.getBoostAmounts().mapValues { (_, value) -> @@ -97,6 +99,7 @@ object OneTimeInAppPaymentRepository { .getDonationsConfiguration(Locale.getDefault()) } .subscribeOn(Schedulers.io()) + .timeout(InAppPaymentsRepository.DONATIONS_CONFIGURATION_TIMEOUT_SECONDS, TimeUnit.SECONDS) .flatMap { it.flattenResult() } .map { it.getBoostBadges().first() } } @@ -107,8 +110,9 @@ object OneTimeInAppPaymentRepository { */ fun getMinimumDonationAmounts(): Single> { return Single.fromCallable { AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault()) } - .flatMap { it.flattenResult() } .subscribeOn(Schedulers.io()) + .timeout(InAppPaymentsRepository.DONATIONS_CONFIGURATION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .flatMap { it.flattenResult() } .map { it.getMinimumDonationAmounts() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt index a6ffb85eb5..c831ac458d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt @@ -39,6 +39,7 @@ import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration import java.math.BigDecimal import java.util.Locale +import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.milliseconds /** @@ -109,6 +110,7 @@ object RecurringInAppPaymentRepository { return Single .fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) } .subscribeOn(Schedulers.io()) + .timeout(InAppPaymentsRepository.DONATIONS_CONFIGURATION_TIMEOUT_SECONDS, TimeUnit.SECONDS) .flatMap { it.flattenResult() } .map { config -> config.getSubscriptionLevels().map { (level, levelConfig) -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt index d49020ebbf..96b19af373 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt @@ -18,6 +18,7 @@ import org.signal.core.util.money.PlatformCurrencyUtil import org.signal.core.util.orNull import org.signal.donations.InAppPaymentType import org.thoughtcrime.securesms.badges.Badges +import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatValue import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeInAppPaymentRepository @@ -265,15 +266,6 @@ class DonateToSignalViewModel( store.update { it.copy(oneTimeDonationState = it.oneTimeDonationState.copy(pendingOneTimeDonation = pendingOneTimeDonation.orNull())) } } - oneTimeDonationDisposables += oneTimeInAppPaymentRepository.getBoostBadge().subscribeBy( - onSuccess = { badge -> - store.update { it.copy(oneTimeDonationState = it.oneTimeDonationState.copy(badge = badge)) } - }, - onError = { - Log.w(TAG, "Could not load boost badge", it) - } - ) - oneTimeDonationDisposables += oneTimeInAppPaymentRepository.getMinimumDonationAmounts().subscribeBy( onSuccess = { amountMap -> store.update { it.copy(oneTimeDonationState = it.oneTimeDonationState.copy(minimumDonationAmounts = amountMap)) } @@ -283,10 +275,14 @@ class DonateToSignalViewModel( } ) - val boosts: Observable>> = oneTimeInAppPaymentRepository.getBoosts().toObservable() + val boostsAndBadge: Observable>, Badge>> = Single.zip( + oneTimeInAppPaymentRepository.getBoosts(), + oneTimeInAppPaymentRepository.getBoostBadge() + ) { boosts, badge -> boosts to badge }.toObservable() + val oneTimeCurrency: Observable = SignalStore.inAppPayments.observableOneTimeCurrency - oneTimeDonationDisposables += Observable.combineLatest(boosts, oneTimeCurrency) { boostMap, currency -> + oneTimeDonationDisposables += Observable.combineLatest(boostsAndBadge, oneTimeCurrency) { (boostMap, badge), currency -> val boostList = if (currency in boostMap) { boostMap[currency]!! } else { @@ -294,12 +290,13 @@ class DonateToSignalViewModel( listOf() } - Triple(boostList, currency, boostMap.keys) + OneTimeConfiguration(boostList, badge, currency, boostMap.keys) }.subscribeBy( - onNext = { (boostList, currency, availableCurrencies) -> + onNext = { (boostList, badge, currency, availableCurrencies) -> store.update { state -> state.copy( oneTimeDonationState = state.oneTimeDonationState.copy( + badge = badge, boosts = boostList, selectedBoost = null, selectedCurrency = currency, @@ -321,6 +318,13 @@ class DonateToSignalViewModel( ) } + private data class OneTimeConfiguration( + val boosts: List, + val badge: Badge, + val currency: Currency, + val availableCurrencies: Set + ) + private fun initializeMonthlyDonationState(subscriptionsRepository: RecurringInAppPaymentRepository) { monitorLevelUpdateProcessing()