diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt index fbc051557..9eaee389b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt @@ -13,7 +13,6 @@ import io.reactivex.rxjava3.subjects.PublishSubject import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.badges.BadgeRepository import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository -import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.livedata.Store import org.whispersystems.libsignal.util.guava.Optional @@ -44,7 +43,7 @@ class BadgesOverviewViewModel( disposables += Single.zip( subscriptionsRepository.getActiveSubscription(), - subscriptionsRepository.getSubscriptions(SignalStore.donationsValues().getSubscriptionCurrency()) + subscriptionsRepository.getSubscriptions() ) { active, all -> if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) { Optional.fromNullable(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt index 71366a946..1c41cdb5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt @@ -5,6 +5,7 @@ import org.signal.core.util.money.FiatMoney import org.thoughtcrime.securesms.badges.Badges import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.subscription.Subscription +import org.thoughtcrime.securesms.util.PlatformCurrencyUtil import org.whispersystems.signalservice.api.services.DonationsService import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels @@ -28,7 +29,7 @@ class SubscriptionsRepository(private val donationsService: DonationsService) { } } - fun getSubscriptions(currency: Currency): Single> = donationsService.getSubscriptionLevels(Locale.getDefault()) + fun getSubscriptions(): Single> = donationsService.getSubscriptionLevels(Locale.getDefault()) .flatMap(ServiceResponse::flattenResult) .map { subscriptionLevels -> subscriptionLevels.levels.map { (code, level) -> @@ -36,7 +37,13 @@ class SubscriptionsRepository(private val donationsService: DonationsService) { id = code, name = level.name, badge = Badges.fromServiceBadge(level.badge), - price = FiatMoney(level.currencies[currency.currencyCode]!!, currency), + prices = level.currencies.filter { + PlatformCurrencyUtil + .getAvailableCurrencyCodes() + .contains(it.key) + }.map { (currencyCode, price) -> + FiatMoney(price, Currency.getInstance(currencyCode)) + }.toSet(), level = code.toInt() ) }.sortedBy { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt index 1027e088a..7b4581435 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt @@ -141,10 +141,10 @@ class BoostFragment : DSLSettingsBottomSheetFragment( customPref( CurrencySelection.Model( - currencySelection = state.currencySelection, + selectedCurrency = state.currencySelection, isEnabled = state.stage == BoostState.Stage.READY, onClick = { - findNavController().navigate(BoostFragmentDirections.actionBoostFragmentToSetDonationCurrencyFragment(true)) + findNavController().navigate(BoostFragmentDirections.actionBoostFragmentToSetDonationCurrencyFragment(true, viewModel.getSupportedCurrencyCodes().toTypedArray())) } ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostRepository.kt index 89ee630f8..1b827045d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostRepository.kt @@ -4,6 +4,7 @@ import io.reactivex.rxjava3.core.Single import org.signal.core.util.money.FiatMoney import org.thoughtcrime.securesms.badges.Badges import org.thoughtcrime.securesms.badges.models.Badge +import org.thoughtcrime.securesms.util.PlatformCurrencyUtil import org.whispersystems.signalservice.api.profiles.SignalServiceProfile import org.whispersystems.signalservice.api.services.DonationsService import org.whispersystems.signalservice.internal.ServiceResponse @@ -13,12 +14,14 @@ import java.util.Locale class BoostRepository(private val donationsService: DonationsService) { - fun getBoosts(currency: Currency): Single> { + fun getBoosts(): Single>> { return donationsService.boostAmounts .flatMap(ServiceResponse>>::flattenResult) .map { result -> - val boosts = result[currency.currencyCode] ?: throw Exception("Unsupported currency! ${currency.currencyCode}") - boosts.map { Boost(FiatMoney(it, currency)) } + result + .filter { PlatformCurrencyUtil.getAvailableCurrencyCodes().contains(it.key) } + .mapKeys { (code, _) -> Currency.getInstance(code) } + .mapValues { (currency, prices) -> prices.map { Boost(FiatMoney(it, currency)) } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt index c84462154..5684baa8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt @@ -2,19 +2,19 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.boost import org.signal.core.util.money.FiatMoney import org.thoughtcrime.securesms.badges.models.Badge -import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection import java.math.BigDecimal import java.util.Currency data class BoostState( val boostBadge: Badge? = null, - val currencySelection: CurrencySelection = CurrencySelection("USD"), + val currencySelection: Currency, val isGooglePayAvailable: Boolean = false, val boosts: List = listOf(), val selectedBoost: Boost? = null, - val customAmount: FiatMoney = FiatMoney(BigDecimal.ZERO, Currency.getInstance(currencySelection.selectedCurrencyCode)), + val customAmount: FiatMoney = FiatMoney(BigDecimal.ZERO, currencySelection), val isCustomAmountFocused: Boolean = false, val stage: Stage = Stage.INIT, + val supportedCurrencyCodes: List = emptyList() ) { enum class Stage { INIT, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt index 955faf53b..fe11847fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt @@ -17,10 +17,11 @@ import org.signal.donations.GooglePayApi import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentRepository -import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.PlatformCurrencyUtil import org.thoughtcrime.securesms.util.livedata.Store import java.math.BigDecimal +import java.util.Currency class BoostViewModel( private val boostRepository: BoostRepository, @@ -28,7 +29,7 @@ class BoostViewModel( private val fetchTokenRequestCode: Int ) : ViewModel() { - private val store = Store(BoostState()) + private val store = Store(BoostState(currencySelection = SignalStore.donationsValues().getBoostCurrency())) private val eventPublisher: PublishSubject = PublishSubject.create() private val disposables = CompositeDisposable() @@ -41,15 +42,26 @@ class BoostViewModel( disposables.clear() } + fun getSupportedCurrencyCodes(): List { + return store.state.supportedCurrencyCodes + } + fun refresh() { disposables.clear() val currencyObservable = SignalStore.donationsValues().observableBoostCurrency - val boosts = currencyObservable.flatMapSingle { boostRepository.getBoosts(it) } + val allBoosts = boostRepository.getBoosts() val boostBadge = boostRepository.getBoostBadge() - disposables += Observable.combineLatest(boosts, boostBadge.toObservable()) { boostList, badge -> - BoostInfo(boostList, boostList[2], badge) + disposables += Observable.combineLatest(currencyObservable, allBoosts.toObservable(), boostBadge.toObservable()) { currency, boostMap, badge -> + val boostList = if (currency in boostMap) { + boostMap[currency]!! + } else { + SignalStore.donationsValues().setBoostCurrency(PlatformCurrencyUtil.USD) + listOf() + } + + BoostInfo(boostList, boostList[2], badge, boostMap.keys) }.subscribeBy( onNext = { info -> store.update { @@ -57,7 +69,8 @@ class BoostViewModel( boosts = info.boosts, selectedBoost = if (it.selectedBoost in info.boosts) it.selectedBoost else info.defaultBoost, boostBadge = it.boostBadge ?: info.boostBadge, - stage = if (it.stage == BoostState.Stage.INIT || it.stage == BoostState.Stage.FAILURE) BoostState.Stage.READY else it.stage + stage = if (it.stage == BoostState.Stage.INIT || it.stage == BoostState.Stage.FAILURE) BoostState.Stage.READY else it.stage, + supportedCurrencyCodes = info.supportedCurrencies.map(Currency::getCurrencyCode) ) } }, @@ -77,7 +90,7 @@ class BoostViewModel( disposables += currencyObservable.subscribeBy { currency -> store.update { it.copy( - currencySelection = CurrencySelection(currency.currencyCode), + currencySelection = currency, isCustomAmountFocused = false, customAmount = FiatMoney( BigDecimal.ZERO, currency @@ -169,7 +182,7 @@ class BoostViewModel( store.update { it.copy(isCustomAmountFocused = isFocused) } } - private data class BoostInfo(val boosts: List, val defaultBoost: Boost?, val boostBadge: Badge) + private data class BoostInfo(val boosts: List, val defaultBoost: Boost?, val boostBadge: Badge, val supportedCurrencies: Set) class Factory( private val boostRepository: BoostRepository, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyFragment.kt index 7e44d6c96..0e38a07fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyFragment.kt @@ -15,7 +15,8 @@ class SetCurrencyFragment : DSLSettingsBottomSheetFragment() { private val viewModel: SetCurrencyViewModel by viewModels( factoryProducer = { - SetCurrencyViewModel.Factory(SetCurrencyFragmentArgs.fromBundle(requireArguments()).isBoost) + val args = SetCurrencyFragmentArgs.fromBundle(requireArguments()) + SetCurrencyViewModel.Factory(args.isBoost, args.supportedCurrencyCodes.toList()) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyViewModel.kt index 7f452b37a..5cb5e3023 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/currency/SetCurrencyViewModel.kt @@ -4,7 +4,6 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import org.signal.donations.StripeApi import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.storage.StorageSyncHelper @@ -14,32 +13,26 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriberId import java.util.Currency import java.util.Locale -class SetCurrencyViewModel(private val isBoost: Boolean) : ViewModel() { +class SetCurrencyViewModel( + private val isBoost: Boolean, + supportedCurrencyCodes: List +) : ViewModel() { - private val store = Store(SetCurrencyState()) + private val store = Store( + SetCurrencyState( + selectedCurrencyCode = if (isBoost) { + SignalStore.donationsValues().getBoostCurrency().currencyCode + } else { + SignalStore.donationsValues().getSubscriptionCurrency().currencyCode + }, + currencies = supportedCurrencyCodes + .map(Currency::getInstance) + .sortedWith(CurrencyComparator(BuildConfig.DEFAULT_CURRENCIES.split(","))) + ) + ) val state: LiveData = store.stateLiveData - init { - val defaultCurrency = if (isBoost) { - SignalStore.donationsValues().getBoostCurrency() - } else { - SignalStore.donationsValues().getSubscriptionCurrency() - } - - store.update { state -> - val platformCurrencies = Currency.getAvailableCurrencies() - val stripeCurrencies = platformCurrencies - .filter { StripeApi.Validation.supportedCurrencyCodes.contains(it.currencyCode) } - .sortedWith(CurrencyComparator(BuildConfig.DEFAULT_CURRENCIES.split(","))) - - state.copy( - selectedCurrencyCode = defaultCurrency.currencyCode, - currencies = stripeCurrencies - ) - } - } - fun setSelectedCurrency(selectedCurrencyCode: String) { store.update { it.copy(selectedCurrencyCode = selectedCurrencyCode) } @@ -93,9 +86,9 @@ class SetCurrencyViewModel(private val isBoost: Boolean) : ViewModel() { } } - class Factory(private val isBoost: Boolean) : ViewModelProvider.Factory { + class Factory(private val isBoost: Boolean, private val supportedCurrencyCodes: List) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(SetCurrencyViewModel(isBoost))!! + return modelClass.cast(SetCurrencyViewModel(isBoost, supportedCurrencyCodes))!! } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ActiveSubscriptionPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ActiveSubscriptionPreference.kt index 3f312fc1c..34a555d49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ActiveSubscriptionPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ActiveSubscriptionPreference.kt @@ -6,6 +6,7 @@ import com.google.android.material.button.MaterialButton import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.badges.BadgeImageView import org.thoughtcrime.securesms.components.settings.PreferenceModel +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.payments.FiatMoneyUtil import org.thoughtcrime.securesms.subscription.Subscription import org.thoughtcrime.securesms.util.DateUtils @@ -51,7 +52,7 @@ object ActiveSubscriptionPreference { R.string.MySupportPreference__s_per_month, FiatMoneyUtil.format( context.resources, - model.subscription.price, + model.subscription.prices.first { it.currency == SignalStore.donationsValues().getSubscriptionCurrency() }, FiatMoneyUtil.formatOptions() ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt index 08a48ec08..f3bd18010 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt @@ -12,7 +12,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.subjects.PublishSubject import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository -import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.subscription.LevelUpdate import org.thoughtcrime.securesms.util.livedata.Store @@ -66,7 +65,7 @@ class ManageDonationsViewModel( } ) - disposables += subscriptionsRepository.getSubscriptions(SignalStore.donationsValues().getSubscriptionCurrency()).subscribeBy( + disposables += subscriptionsRepository.getSubscriptions().subscribeBy( onSuccess = { subs -> store.update { it.copy(availableSubscriptions = subs) } }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt index 8ead1a259..306ccd09e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt @@ -6,19 +6,16 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.util.MappingAdapter import org.thoughtcrime.securesms.util.MappingViewHolder +import java.util.Currency -data class CurrencySelection( - val selectedCurrencyCode: String, -) { +object CurrencySelection { - companion object { - fun register(adapter: MappingAdapter) { - adapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it) }, R.layout.subscription_currency_selection)) - } + fun register(adapter: MappingAdapter) { + adapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it) }, R.layout.subscription_currency_selection)) } class Model( - val currencySelection: CurrencySelection, + val selectedCurrency: Currency, override val isEnabled: Boolean, val onClick: () -> Unit ) : PreferenceModel(isEnabled = isEnabled) { @@ -28,7 +25,7 @@ data class CurrencySelection( override fun areContentsTheSame(newItem: Model): Boolean { return super.areContentsTheSame(newItem) && - newItem.currencySelection.selectedCurrencyCode == currencySelection.selectedCurrencyCode + newItem.selectedCurrency == selectedCurrency } } @@ -37,7 +34,7 @@ data class CurrencySelection( private val spinner: TextView = itemView.findViewById(R.id.subscription_currency_selection_spinner) override fun bind(model: Model) { - spinner.text = model.currencySelection.selectedCurrencyCode + spinner.text = model.selectedCurrency.currencyCode itemView.setOnClickListener { model.onClick() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt index 41d5db82f..108fce10a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt @@ -118,10 +118,13 @@ class SubscribeFragment : DSLSettingsFragment( customPref( CurrencySelection.Model( - currencySelection = state.currencySelection, + selectedCurrency = state.currencySelection, isEnabled = areFieldsEnabled && state.activeSubscription?.isActive != true, onClick = { - findNavController().navigate(SubscribeFragmentDirections.actionSubscribeFragmentToSetDonationCurrencyFragment(false)) + val selectableCurrencies = viewModel.getSelectableCurrencyCodes() + if (selectableCurrencies != null) { + findNavController().navigate(SubscribeFragmentDirections.actionSubscribeFragmentToSetDonationCurrencyFragment(false, selectableCurrencies.toTypedArray())) + } } ) ) @@ -138,7 +141,8 @@ class SubscribeFragment : DSLSettingsFragment( isActive = isActive, willRenew = isActive && state.activeSubscription?.activeSubscription?.willCancelAtPeriodEnd() ?: false, onClick = { viewModel.setSelectedSubscription(it) }, - renewalTimestamp = TimeUnit.SECONDS.toMillis(state.activeSubscription?.activeSubscription?.endOfCurrentPeriod ?: 0L) + renewalTimestamp = TimeUnit.SECONDS.toMillis(state.activeSubscription?.activeSubscription?.endOfCurrentPeriod ?: 0L), + selectedCurrency = state.currencySelection ) ) } @@ -154,7 +158,7 @@ class SubscribeFragment : DSLSettingsFragment( text = DSLSettingsText.from(R.string.SubscribeFragment__update_subscription), isEnabled = areFieldsEnabled && (!activeAndSameLevel || isExpiring), onClick = { - val price = viewModel.state.value?.selectedSubscription?.price ?: return@primaryButton + val price = viewModel.getPriceOfSelectedSubscription() ?: return@primaryButton val calendar = Calendar.getInstance() calendar.add(Calendar.MONTH, 1) @@ -222,7 +226,7 @@ class SubscribeFragment : DSLSettingsFragment( } private fun onGooglePayButtonClicked() { - viewModel.requestTokenFromGooglePay(requireContext()) + viewModel.requestTokenFromGooglePay() } private fun onPaymentConfirmed(badge: Badge) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt index 6ee78fffd..6d122e052 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt @@ -1,11 +1,11 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.subscribe -import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection import org.thoughtcrime.securesms.subscription.Subscription import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription +import java.util.Currency data class SubscribeState( - val currencySelection: CurrencySelection = CurrencySelection("USD"), + val currencySelection: Currency, val subscriptions: List = listOf(), val selectedSubscription: Subscription? = null, val activeSubscription: ActiveSubscription? = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt index 1f77c9459..89a657157 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.subscribe -import android.content.Context import android.content.Intent import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel @@ -8,21 +7,26 @@ import androidx.lifecycle.ViewModelProvider import com.google.android.gms.wallet.PaymentData import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.subjects.PublishSubject import org.signal.core.util.logging.Log +import org.signal.core.util.money.FiatMoney import org.signal.donations.GooglePayApi import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentRepository import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository -import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.subscription.LevelUpdate +import org.thoughtcrime.securesms.subscription.Subscriber import org.thoughtcrime.securesms.subscription.Subscription +import org.thoughtcrime.securesms.util.PlatformCurrencyUtil import org.thoughtcrime.securesms.util.livedata.Store import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription +import org.whispersystems.signalservice.api.subscriptions.SubscriberId import java.util.Currency class SubscribeViewModel( @@ -31,7 +35,7 @@ class SubscribeViewModel( private val fetchTokenRequestCode: Int ) : ViewModel() { - private val store = Store(SubscribeState()) + private val store = Store(SubscribeState(currencySelection = SignalStore.donationsValues().getSubscriptionCurrency())) private val eventPublisher: PublishSubject = PublishSubject.create() private val disposables = CompositeDisposable() @@ -45,11 +49,20 @@ class SubscribeViewModel( disposables.clear() } + fun getPriceOfSelectedSubscription(): FiatMoney? { + return store.state.selectedSubscription?.prices?.first { it.currency == store.state.currencySelection } + } + + fun getSelectableCurrencyCodes(): List? { + return store.state.subscriptions.firstOrNull()?.prices?.map { it.currency.currencyCode } + } + fun refresh() { disposables.clear() val currency: Observable = SignalStore.donationsValues().observableSubscriptionCurrency - val allSubscriptions: Observable> = currency.switchMapSingle { subscriptionsRepository.getSubscriptions(it) } + val allSubscriptions: Single> = subscriptionsRepository.getSubscriptions() + refreshActiveSubscription() disposables += LevelUpdate.isProcessing.subscribeBy { @@ -60,7 +73,25 @@ class SubscribeViewModel( } } - disposables += Observable.combineLatest(allSubscriptions, activeSubscriptionSubject, ::Pair).subscribeBy( + disposables += allSubscriptions.subscribeBy( + onSuccess = { subscriptions -> + if (subscriptions.isNotEmpty()) { + val priceCurrencies = subscriptions[0].prices.map { it.currency } + val selectedCurrency = SignalStore.donationsValues().getSubscriptionCurrency() + + if (selectedCurrency !in priceCurrencies) { + Log.w(TAG, "Unsupported currency selection. Defaulting to USD. $currency isn't supported.") + val usd = PlatformCurrencyUtil.USD + val newSubscriber = SignalStore.donationsValues().getSubscriber(usd) ?: Subscriber(SubscriberId.generate(), usd.currencyCode) + SignalStore.donationsValues().setSubscriber(newSubscriber) + StorageSyncHelper.scheduleSyncForDataChange() + } + } + }, + onError = {} + ) + + disposables += Observable.combineLatest(allSubscriptions.toObservable(), activeSubscriptionSubject, ::Pair).subscribeBy( onNext = { (subs, active) -> store.update { it.copy( @@ -79,7 +110,7 @@ class SubscribeViewModel( onError = { eventPublisher.onNext(DonationEvent.GooglePayUnavailableError(it)) } ) - disposables += currency.map { CurrencySelection(it.currencyCode) }.subscribe { selection -> + disposables += currency.subscribe { selection -> store.update { it.copy(currencySelection = selection) } } } @@ -190,7 +221,7 @@ class SubscribeViewModel( ) } - fun requestTokenFromGooglePay(context: Context) { + fun requestTokenFromGooglePay() { val snapshot = store.state if (snapshot.selectedSubscription == null) { return @@ -198,8 +229,10 @@ class SubscribeViewModel( store.update { it.copy(stage = SubscribeState.Stage.TOKEN_REQUEST) } + val selectedCurrency = snapshot.currencySelection + subscriptionToPurchase = snapshot.selectedSubscription - donationPaymentRepository.requestTokenFromGooglePay(snapshot.selectedSubscription.price, snapshot.selectedSubscription.name, fetchTokenRequestCode) + donationPaymentRepository.requestTokenFromGooglePay(snapshot.selectedSubscription.prices.first { it.currency == selectedCurrency }, snapshot.selectedSubscription.name, fetchTokenRequestCode) } fun setSelectedSubscription(subscription: Subscription) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/subscription/Subscription.kt b/app/src/main/java/org/thoughtcrime/securesms/subscription/Subscription.kt index 91834c0b0..77068d093 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/subscription/Subscription.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/subscription/Subscription.kt @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MappingAdapter import org.thoughtcrime.securesms.util.MappingViewHolder import org.thoughtcrime.securesms.util.visible +import java.util.Currency import java.util.Locale /** @@ -22,7 +23,7 @@ data class Subscription( val id: String, val name: String, val badge: Badge, - val price: FiatMoney, + val prices: Set, val level: Int, ) { @@ -39,7 +40,8 @@ data class Subscription( val willRenew: Boolean, override val isEnabled: Boolean, val onClick: () -> Unit, - val renewalTimestamp: Long + val renewalTimestamp: Long, + val selectedCurrency: Currency ) : PreferenceModel(isEnabled = isEnabled) { override fun areItemsTheSame(newItem: Model): Boolean { @@ -52,7 +54,8 @@ data class Subscription( newItem.isSelected == isSelected && newItem.isActive == isActive && newItem.renewalTimestamp == renewalTimestamp && - newItem.willRenew == willRenew + newItem.willRenew == willRenew && + newItem.selectedCurrency == selectedCurrency } override fun getChangePayload(newItem: Model): Any? { @@ -86,7 +89,7 @@ data class Subscription( val formattedPrice = FiatMoneyUtil.format( context.resources, - model.subscription.price, + model.subscription.prices.first { it.currency == model.selectedCurrency }, FiatMoneyUtil.formatOptions() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PlatformCurrencyUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/PlatformCurrencyUtil.kt new file mode 100644 index 000000000..4b096d4a2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/PlatformCurrencyUtil.kt @@ -0,0 +1,24 @@ +package org.thoughtcrime.securesms.util + +import java.util.Currency + +/** + * Utility methods for java.util.Currency + * + * This is prefixed with "Platform" as there are several different Currency classes + * available in the app, and this utility class is specifically for dealing with + * java.util.Currency + */ +object PlatformCurrencyUtil { + + val USD: Currency = Currency.getInstance("USD") + + /** + * Note: Adding this as an extension method of Currency causes some confusion in + * AndroidStudio due to a separate Currency class from the AndroidSDK having + * an extension method of the same signature. + */ + fun getAvailableCurrencyCodes(): Set { + return Currency.getAvailableCurrencies().map { it.currencyCode }.toSet() + } +} diff --git a/app/src/main/res/navigation/boosts.xml b/app/src/main/res/navigation/boosts.xml index e106180bf..b7d441e27 100644 --- a/app/src/main/res/navigation/boosts.xml +++ b/app/src/main/res/navigation/boosts.xml @@ -31,6 +31,10 @@ + + +